Module 1 · Foundations that hold everything up

Lesson 01 — HTTP in depth

Methods, status codes with intent, CORS, secure cookies, caching and TLS — verified with curl.

Published
In this lesson
  1. Objectives
  2. 1. Anatomy of an HTTP request
  3. 2. Headers you master or suffer
  4. 3. Keep-alive, HTTPS and TLS
  5. 4. HTTP in DRF: what the framework does for you (and shouldn't)
  6. Self-assessment (answer me in the chat)

Stack: Django + DRF · Project: TicketFlow Status: Taught — exercises pending submission Prerequisite: Lesson 00 — Environment and your first endpoint


Objectives

By the end of this lesson you will be able to:

  1. Describe the exact anatomy of an HTTP request and response.
  2. Choose the right method and status code with justification (interviewers this).
  3. Explain which headers control CORS, caching, cookies and compression, and verify them with curl.
  4. Reason about keep-alive, HTTPS/TLS and why they matter in production.
  5. Apply all of this in DRF: why Response picks status codes for you, and when not to let it.

1. Anatomy of an HTTP request

HTTP is a text protocol (binary since HTTP/2) over TCP, stateless: every request carries all the information needed. Structure:

http
POST /api/events/42/reserve HTTP/1.1      ← method + path + version
Host: ticketflow.example.com              ← mandatory in HTTP/1.1
Content-Type: application/json            ← what I'm sending
Authorization: Bearer eyJhbGci...         ← who I am
Idempotency-Key: 7f3c9a2e                 ← anti-duplicates (Lesson 14)
Content-Length: 58

{"seat_id": 1051, "quantity": 2}          ← body (optional)

And the response:

http
HTTP/1.1 201 Created
Location: /api/reservations/9172/         ← where the created thing lives
Content-Type: application/json
Cache-Control: no-store

{"id": 9172, "status": "pending_payment", "seats": [1051, 1052]}

Key interview concept: HTTP is stateless but can keep a session via cookies or tokens. Don't confuse protocol state with application state.

Methods: safe, idempotent, cacheable

MethodSafeIdempotentTypical use in TicketFlow
GETlist events, view availability
POSTcreate reservation, start checkout
PUTreplace a whole event
PATCH*edit an event's price
DELETEcancel a reservation

\* PATCH is idempotent only if the patch is absolute ({"price": 20}), not relative ({"price": "+2"}).

  • Safe = doesn't modify server state (the client can repeat it fearlessly).
  • Idempotent = repeating N times yields the same result as once. Crucial for payments and webhooks (Lesson 14).

Status codes: the ones you'll actually use

CodeMeaningTicketFlow example
200 OKgeneric successGET availability
201 Createdresource createdPOST reservation (with Location)
204 No Contentsuccess without bodyDELETE reservation
400 Bad Requestmalformed / invalid requestnegative seat count
401 Unauthorizednot authenticated (the name misleads)no token
403 Forbiddenauthenticated but not allowedbuyer tries to edit an event
404 Not Founddoesn't exist (or you don't want to admit it exists)—
409 Conflictstate clashseat already reserved
422 Unprocessablewell-formed but business-invalidevent already past
429 Too Many Requestsrate limita bot buying everything
500our bugour failure, not the client's!

Golden rule: 4xx are the client's fault; 5xx, the server's. Returning 500 for a failed validation is a bug in your API.

2. Headers you master or suffer

CORS (Cross-Origin Resource Sharing)

The browser blocks responses from another origin by default. CORS is how the server authorizes those origins. A preflight OPTIONS request with:

http
Access-Control-Allow-Origin: https://ticketflow.app
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type, Idempotency-Key

In DRF, django-cors-headers manages it (we install it in the exercises). CORS is enforced by the server; it is not a client-side security measure: if the public API has no CORS, it's because it doesn't want browsers from other origins; curl always ignores CORS.

Cookies and security attributes

http
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
  • HttpOnly → JavaScript can't read it (mitigates XSS stealing the session).
  • Secure → only travels over HTTPS.
  • SameSite=Lax|Strict|None → mitigates CSRF by restricting cross-site submissions.
  • Expiry: Expires/Max-Age; without them it's a session cookie (dies when the browser closes).

HTTP caching

http
Cache-Control: max-age=60, public          # strong policy (recommended)
ETag: "v17"                                # identifier of the resource version
  • The client repeats the request with If-None-Match: "v17" → if unchanged, the server answers 304 Not Modified with no body: bandwidth saved.
  • Cache-Control: no-store for sensitive data (payments). no-cache = "you may cache but always revalidate" (a misleading name).
  • Shared cache (CDN/proxy) vs private: private keeps a CDN from caching responses containing user data.

Negotiation and body

  • Content-Type: application/json (request) and response. DRF does content negotiation: with Accept: application/json vs text/html (the browser shows the browsable HTML of the APIView).
  • Accept-Language, Accept-Encoding: gzip, br → response compression (done by the server or the proxy, Lesson 02).

3. Keep-alive, HTTPS and TLS

Keep-alive and the cost of connections

HTTP/1.0 opened a TCP connection per request (expensive: TCP handshake + TLS). HTTP/1.1 introduced persistent connections (Connection: keep-alive by default): many requests reuse the connection. HTTP/2 goes further with multiplexing (several requests in parallel over one connection).

In production your Django doesn't talk to the world directly: Gunicorn/Uvicorn behind Nginx keeps keep-alive towards the client and efficient connections towards the app (Lesson 02).

HTTPS/TLS in 4 steps

  1. Handshake: client and server negotiate version and ciphers.
  2. Certificate: the server presents its certificate signed by a CA the client trusts.
  3. Keys: (classically) via Diffie-Hellman exchange, a symmetric session key is derived.
  4. Symmetric encryption for all data (fast).

This gives confidentiality (nobody reads), integrity (nobody alters) and authenticity (the server is who it claims to be). In 2026 the standard is TLS 1.3: 1 round-trip (0 on resumption) and modern ciphers.

HSTS: the Strict-Transport-Security header tells the browser "only speak HTTPS to me for X time", preventing downgrade attacks.

4. HTTP in DRF: what the framework does for you (and shouldn't)

python
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(["POST"])
def reserve(request):
    if request.method == "POST":
        # DRF: request.data already parses JSON according to Content-Type
        # Response without status= would return 200: to create a resource, say 201!
        return Response({"status": "pending_payment"}, status=status.HTTP_201_CREATED)

Points a senior controls:

  • Explicit codes: Response's silent default is 200. For a POST that creates → 201; for business actions → 200; for "accepted for queue processing" → 202 (Lesson 06).
  • request.data vs request.query_params: body vs query string. Confusing them is a bug factory.
  • Consistent errors: DRF returns {"detail":...} or {"field": [errors]}; we'll define our own centralized error format (Lesson 26).
  • Response headers: Response(headers={"Location":...}), Cache-Control on cacheable listings.
  • 401 vs 403 in DRF: NotAuthenticated (401) vs PermissionDenied (403). DRF resolves it, but you must be able to explain it.

Self-assessment (answer me in the chat)

  1. Why is POST /reserve not idempotent, and what are the consequences if the client retries after a timeout?
  2. The client sends valid credentials but their role doesn't allow that action. 401 or 403? Why?
  3. Which header(s) would you use so a public events listing is cached 60s on a CDN but payment data never is?
  4. What exactly happens when the browser does a preflight and the server doesn't allow the Idempotency-Key header?
  5. Why is returning 500 for a malformed JSON a backend error, not a client error?

Continue with the exercises. The solutions only after trying it yourself.