HTTP
The application-layer protocol behind the web and most APIs: a stateless request–response protocol, textual in HTTP/1.x, binary in HTTP/2+, carried over TCP (HTTP/3: QUIC/UDP — see 02_http_versions.md). This note is the fundamentals; method/status/caching semantics live in 03_http_semantics_and_caching.md.
A request and a response, on the wire
GET /users/123?fields=name HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer eyJhbGci...
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 27
Cache-Control: private, max-age=60
{"id": 123, "name": "Ada"}
Anatomy, in order:
- Request line — method, request target (path + query), version. Status line on the response — version, status code, reason phrase.
- Headers —
Name: valuelines, case-insensitive names. - Blank line — the
\r\n\r\nseparator. - Optional body — length delimited by
Content-Length, or streamed withTransfer-Encoding: chunked(HTTP/1.1) when the size isn’t known upfront.
That framing answers a classic probe: how does the receiver know where a message ends? Content-Length, chunked encoding, or (HTTP/1.0 style) connection close.
URL anatomy
https://api.example.com:8443/v1/users/123?active=true&sort=name#profile
scheme host port path query fragment
- Default ports: 80 (http), 443 (https) — omitted in practice.
- The fragment never leaves the browser — servers don’t see
#profile. - Query strings are part of the request target: visible in logs and proxies (don’t put secrets there), and part of cache keys.
Headers worth knowing cold
| Header | Direction | Role |
|---|---|---|
Host |
request | which virtual host — mandatory since HTTP/1.1; how one IP serves many sites |
Content-Type |
both | media type of the body (application/json) |
Content-Length / Transfer-Encoding |
both | body framing (see above) |
Accept / Accept-Encoding |
request | content negotiation: format / compression (gzip, br) |
Authorization |
request | credentials (Bearer <token>, Basic <b64>) |
Cookie / Set-Cookie |
req / resp | the state mechanism (below) |
Location |
response | target of redirects (3xx) and created resources (201) |
Cache-Control, ETag |
response | caching contract — 03_http_semantics_and_caching.md |
X-Forwarded-For / Forwarded |
request | original client IP through proxies (../../28_networking/11_proxies_forward_reverse.md) |
Stateless — and how the web fakes state
HTTP itself remembers nothing between requests; each one must carry its own context. State is layered on top:
- Cookies: server sends
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Lax; the browser attachesCookie: session=abcto subsequent requests for that domain. Session data then lives server-side keyed by that ID, or in the token itself (JWT). - Tokens in headers:
Authorization: Bearer ...— the API-world equivalent, no browser magic involved.
Why statelessness is a feature, not a bug — scaling, retries, load balancing: ../../07_rest_apis/09_stateful_vs_stateless.md.
Connections
- HTTP/1.1 defaults to persistent connections (
keep-alive): many requests reuse one TCP connection, avoiding repeated TCP+TLS handshakes — but only one request at a time per connection (head-of-line blocking), which is why clients open connection pools. HTTP/2 multiplexes instead — the whole story is in 02_http_versions.md. - HTTPS is HTTP inside a TLS session — confidentiality + integrity + server authentication. Handshake and certificates: ../../28_networking/12_tls_https_certificates.md, ../../28_networking/16_https_handshake_keepalive.md.
Seeing it raw
curl -v https://api.example.com/users/123 # prints request/response lines + headers
import httpx
r = httpx.get("https://api.example.com/users/123",
headers={"Accept": "application/json"}, timeout=5.0)
r.status_code # 200
r.headers["content-type"]
r.json()
# httpx.Client() reuses connections (keep-alive pool) — always use one in services
Common pitfalls
- Forgetting
Hostmatters: it’s how reverse proxies and shared hosts route; curl to an IP with the wrongHostheader “mysteriously” 404s. - Secrets in query strings — logged by every proxy and server on the path; use headers or body.
- Assuming one request = one TCP connection — pools and keep-alive mean server-side “per-connection” state leaks across requests.
- Treating HTTP as reliable delivery of semantics: a timeout doesn’t mean the server didn’t process the request — the reason idempotency matters (../../07_rest_apis/01_idempotency.md).
Interview angle
- “What happens when you type a URL and hit enter?” — DNS → TCP → TLS → HTTP request → response → render; this file is the HTTP chapter of that answer.
- “How does the server know where the request body ends?” — Content-Length vs chunked; a question that filters people who’ve only used frameworks.
- “HTTP is stateless — so how do sessions work?” — Cookies/tokens carry identity per request; state lives at the edges.
- “Why HTTPS everywhere?” — TLS gives confidentiality/integrity/authentication; mention that HTTP/2 in browsers is HTTPS-only.