# HTTP Versions

## 1. HTTP/0.9 and HTTP/1.0 (Historical)

HTTP/0.9 (1991) was a single `GET /path` line, no headers, no status code, connection closed immediately after the response — enough to fetch a document and nothing else. HTTP/1.0 (1996) added headers, status codes, and other methods, but opened a **new TCP connection per request** by default, which is expensive (see the TCP handshake cost in the HTTP Fundamentals topic).

## 2. HTTP/1.1 (1997) — Still Widely Used

HTTP/1.1 made connections **persistent by default** (`Connection: keep-alive`) so multiple requests could reuse one TCP connection, and added chunked transfer encoding, `Host` header (required — enables name-based virtual hosting on the same IP), and conditional requests (`If-None-Match`, etc.).

**The unsolved problem: head-of-line (HOL) blocking on the connection.** HTTP/1.1 is fundamentally a *serial* protocol on a given connection — the server must send complete response N before response N+1 can begin, even if response N+1 finished processing first. Browsers work around this by opening multiple parallel TCP connections per host (historically 6) — which just shifts the cost to more handshakes and more contention, it doesn't remove the blocking.

```
Connection 1: [--- Request A ---][--- Response A ---][--- Request B ---][--- Response B ---]
              (B cannot start until A's response is fully sent, on THIS connection)
```

## 3. HTTP/2 (2015) — Binary Framing and Multiplexing

HTTP/2 keeps the same semantics (same methods, status codes, headers) but changes the wire format entirely: everything is split into small, length-prefixed, binary **frames**, and frames from many concurrent logical **streams** are interleaved over a *single* TCP connection.

```
One TCP connection:
  [stream 1 frame][stream 3 frame][stream 1 frame][stream 5 frame][stream 3 frame]...
```

Each stream carries one request/response pair and has an odd (client-initiated) or even (server-initiated, for now-deprecated Server Push) integer ID. The server can interleave frames from streams 1, 3, and 5 in any order — a large, slow response on stream 3 no longer blocks a small, fast response on stream 1 from being delivered first. Headers are additionally compressed with **HPACK**, which maintains a shared dynamic table between client and server so repeated header values (cookies, user-agent) across requests on the same connection are sent as small index references instead of full text.

![One HTTP/1.1 connection serializes responses; one HTTP/2 connection interleaves frames from multiple concurrent streams](diagrams/01-http2-multiplexing.svg)

## 4. HTTP/3 (2022) — QUIC over UDP

HTTP/2 solved HOL blocking *at the HTTP layer* but not underneath it: all streams still share one TCP connection, and TCP itself guarantees **in-order byte delivery** — if a single packet is lost, TCP holds up *every* stream's data behind it until that packet is retransmitted, even though the lost bytes might belong to only one HTTP/2 stream. This is **transport-level head-of-line blocking**, and no amount of HTTP-layer cleverness can fix it while running on TCP.

HTTP/3 solves this by dropping TCP entirely and running over **QUIC**, a transport protocol built on UDP. QUIC implements its own reliability and multiplexing, but critically, **each QUIC stream is independently ordered and independently recovers from loss** — a lost packet only blocks the one stream it belonged to, not the others sharing the connection. QUIC also folds the transport and TLS 1.3 handshakes together (often 1 round trip total, sometimes 0 for resumed connections), versus HTTP/2's separate TCP + TLS handshakes.

## 5. Version Comparison

| | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Format | text | binary frames | binary frames (QUIC) |
| Transport | TCP | TCP | UDP (QUIC) |
| Multiplexing | no (parallel connections instead) | yes, single connection | yes, single connection |
| HOL blocking | yes (per-connection, HTTP-level) | transport-level only (TCP) | effectively eliminated |
| Header compression | none | HPACK | QPACK |
| Handshake cost | TCP (+TLS if HTTPS) | TCP (+TLS if HTTPS) | QUIC handshake (combines transport+TLS) |

## Under the Hood: Frames, Streams, and Why QUIC Changes the Game

### HTTP/2 frame types are real, typed units

An HTTP/2 connection is a sequence of frames, each with a 9-byte header: length, type, flags, and a stream ID. Common types: `HEADERS` (start of a request/response's headers, HPACK-compressed), `DATA` (body bytes), `SETTINGS` (connection-wide parameters like max concurrent streams), `WINDOW_UPDATE` (flow control credit), `RST_STREAM` (cancel one stream without killing the connection — impossible in HTTP/1.1, where cancelling a request means closing and reopening the whole TCP connection). Stream IDs let an endpoint demultiplex incoming frames back into the right logical request/response, purely from a field in each frame header — no separate connections needed per request.

### Why TCP's guarantee becomes a liability

TCP's core promise — bytes arrive at the application in the exact order they were sent, with any gap causing everything after it to wait — is exactly right for a single stream of data, but actively wrong once you've multiplexed unrelated streams onto one TCP connection: from TCP's point of view there is only one byte stream, so a lost packet belonging to HTTP/2 stream 5 stalls delivery of already-arrived bytes belonging to stream 1 and 3 as well, because TCP won't hand any of it up to the application until the gap is filled. QUIC instead multiplexes at the transport layer itself — each QUIC stream has its own independent sequence numbering — so a lost packet only stalls the one stream whose data it carried.

### QUIC's handshake collapses two round trips into one (or zero)

A first-time HTTPS connection over TCP needs: 1 RTT for the TCP handshake, then 1–2 RTTs for the TLS handshake, before the first HTTP request byte can even be sent. QUIC integrates TLS 1.3 directly into its own handshake, typically completing connection setup **and** the cryptographic handshake in a single round trip; if the client has connected to that server recently, QUIC supports **0-RTT** resumption — sending application data in the very first packet, before the handshake even finishes (with a defined security tradeoff: 0-RTT data is replayable, so it's typically restricted to idempotent requests).

## Common Pitfalls

1. **Assuming HTTP/2 automatically makes an app faster** — it removes HTTP-level HOL blocking and reduces connection overhead, but a slow backend or a network with real packet loss still bottlenecks it (and packet loss specifically is where HTTP/2 still suffers from TCP's transport-level HOL blocking).
2. **Still domain-sharding assets under HTTP/2** — splitting assets across `img1.example.com`, `img2.example.com` was an HTTP/1.1 workaround for the 6-connections-per-host limit; under HTTP/2 it actively hurts, since it defeats multiplexing and forces multiple separate connections/handshakes instead of one.
3. **Confusing HTTP/2's HPACK header compression with body compression** — HPACK only compresses headers; response body compression (gzip/brotli) is a completely separate mechanism, covered in the Caching and Compression topic.
4. **Assuming HTTP/3 needs no fallback** — QUIC runs over UDP, which some corporate firewalls and older middleboxes block outright, so real deployments still need to advertise HTTP/3 (via the `Alt-Svc` header) and gracefully fall back to HTTP/2 over TCP.

## Quick Reference

```
HTTP/1.1: text, TCP, serial-per-connection, workaround = multiple connections
HTTP/2:   binary frames, TCP, multiplexed streams, HPACK headers
HTTP/3:   binary frames, QUIC/UDP, per-stream loss recovery, QPACK headers

HOL blocking fixed at HTTP layer:      HTTP/2, HTTP/3
HOL blocking fixed at transport layer: HTTP/3 only (needs UDP/QUIC, not TCP)
```
