Why HTTP/2?
HTTP/1.1 (1997) was designed when a web page loaded a handful of resources. A modern page might request 100+ assets. HTTP/1.1's serial request model, text-based framing, and redundant headers create enormous overhead:
- Head-of-line blocking — later requests wait for earlier ones
- Repeated headers —
User-Agent,Cookie,Acceptsent identically on every request - 6-connection limit — browsers work around blocking with parallel connections
HTTP/2 (RFC 7540, 2015 — superseded by RFC 9113 in 2022) was developed from Google's SPDY protocol and addresses all three problems with a single TCP connection.
Binary Framing Layer
The most fundamental change in HTTP/2: the wire format is binary, not text. All communication happens through fixed-format frames.
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) |
+---------------------------------------------------------------+
Every HTTP/2 frame has a 9-byte fixed header: length (up to 16 MB), type, flags, and stream ID. The binary format eliminates the need to parse variable-length text lines — parsers become deterministic and faster.
Frame types:
| Type | Code | Purpose |
|---|---|---|
| DATA | 0x0 | Request/response body |
| HEADERS | 0x1 | Request/response headers |
| PRIORITY | 0x2 | Stream priority (deprecated in RFC 9113) |
| RST_STREAM | 0x3 | Terminate a stream immediately |
| SETTINGS | 0x4 | Connection parameters |
| PUSH_PROMISE | 0x5 | Server push announcement |
| PING | 0x6 | Connection keepalive |
| GOAWAY | 0x7 | Graceful connection shutdown |
| WINDOW_UPDATE | 0x8 | Flow control |
| CONTINUATION | 0x9 | Header continuation |
Streams and Multiplexing
A stream is an independent, bidirectional sequence of frames within a connection. Each request-response pair is one stream.
Connection:
Stream 1: GET /api/user ────► 200 OK + body
Stream 3: GET /api/orders ───► 200 OK + body
Stream 5: GET /api/prefs ────► 200 OK + body
(all interleaved on one TCP connection, no waiting)
Stream IDs are odd for client-initiated streams and even for server-push streams. Streams progress through states: idle → open → half-closed → closed. RST_STREAM can terminate any stream immediately without closing the connection.
Multiplexing eliminates head-of-line blocking at the HTTP layer. (Note: TCP HoL blocking still exists in HTTP/2; HTTP/3 solves this by replacing TCP with QUIC.)
Header Compression (HPACK)
RFC 7541 defines HPACK, the header compression format for HTTP/2. It reduces header overhead by 85–95% compared to HTTP/1.1.
HPACK uses three techniques:
- Static Table — 61 pre-defined header name/value pairs (e.g.,
method: GET,status: 200,content-type: text/html). Referenced by a single integer.
- Dynamic Table — a FIFO list of headers seen in the current connection. Subsequent requests can reference past headers by index instead of repeating them.
- Huffman Encoding — new header values are Huffman-compressed before transmission.
First request — full headers transmitted
Second request — User-Agent, Accept, Cookie referenced by index
Only changed headers (e.g., :path) sent as new strings
Server Push
HTTP/2 server push allows the server to proactively send resources the client will need, before the client requests them. The server sends a PUSH_PROMISE frame announcing the upcoming resource, followed by the resource on an even-numbered stream:
Client: GET /index.html (Stream 1)
Server: PUSH_PROMISE Stream 2 → /styles.css
Server: PUSH_PROMISE Stream 4 → /app.js
Server: HEADERS + DATA Stream 1 → index.html
Server: HEADERS + DATA Stream 2 → styles.css
Server: HEADERS + DATA Stream 4 → app.js
In practice, server push has seen limited adoption — browsers often already have resources cached, wasting bandwidth. The 103 Early Hints status code is now preferred for hinting at subresources.
Flow Control
HTTP/2 has two levels of flow control to prevent fast senders from overwhelming slow receivers:
- Connection-level — limits total DATA frame bytes in flight
- Stream-level — limits bytes per individual stream
Each side maintains a receive window and advertises it via WINDOW_UPDATE frames as it consumes data. Default initial window size: 65,535 bytes. For high-throughput APIs, increase it via SETTINGS:
SETTINGS_INITIAL_WINDOW_SIZE = 1048576 # 1 MB
HTTP/2 vs HTTP/3
| Attribute | HTTP/2 | HTTP/3 |
|---|---|---|
| Transport | TCP | QUIC (UDP-based) |
| HoL blocking | At TCP level | Eliminated |
| Connection setup | 1–2 RTT (TLS 1.3) | 0–1 RTT |
| Header compression | HPACK | QPACK |
| Connection migration | No | Yes (QUIC CID) |
| Spec | RFC 7540 / 9113 | RFC 9114 |
HTTP/3 eliminates TCP HoL blocking entirely by using QUIC's independent stream delivery. A lost UDP packet only delays the specific stream it belongs to, not the entire connection.