Networking & Protocols
HTTP/2: streams, frames, and HPACK
A browser loading a page with 50 sub-resources opens 6 TCP connections to the same origin, fills them with requests, and queues the rest. Each connection pays its own handshake cost. HTTP/2 collapses all that into one connection with 50 concurrent streams — but the fix comes with its own failure mode.
HTTP/1.1: pipelining and six connections
HTTP/1.1 (RFC 9112) sends one request per TCP connection and waits for the complete response before sending the next on that connection. To parallelise, browsers open 6–8 connections to the same origin. A browser loading 10 critical JavaScript files opens 6 connections, sends one request on each, then queues the remaining 4. Once a response completes, the connection is freed and fetches the next file.
Pipelining (RFC 7230) allowed clients to send multiple requests without waiting for responses — but it rarely worked. Proxies broke it, and if request #1 stalled waiting for a slow server response, request #2 would block behind it on the same connection anyway — head-of-line (HOL) blocking on the response side. Nearly all browsers disabled pipelining by default.
HTTP/2: the framing layer
HTTP/2 (RFC 9113) abandons the one-request-per-connection model. A single TCP connection carries many concurrent streams — each stream is an independent request-response pair.
Frames are the unit of transmission:
- HEADERS frame — opens a stream and carries request or response metadata (status, headers). A stream is created when the client sends a HEADERS frame with a new stream ID.
- DATA frames — carry body content for the stream.
- RST_STREAM — aborts a stream without closing the connection.
- SETTINGS — negotiates connection parameters (max concurrent streams, initial window size, header table size).
- WINDOW_UPDATE — flow control: tells the sender how many more bytes the receiver is willing to buffer.
The server can interleave responses from different streams: 100 bytes of stream #1’s response, then 100 bytes of stream #3’s, then more of stream #1 — all on the same underlying TCP connection. Order within a single stream is preserved; order between streams is up to the server.
- HTTP/2 frame header size
- 9 bytes (type, flags, length, stream ID)
- Stream ID space
- 31 bits (odd = client-initiated, even = server push)
- Default flow-control window
- 65,535 bytes per stream
- Common server setting: max concurrent streams
- 100 (RFC 9113 recommendation)
- SETTINGS frame negotiated
- at connection start, before first request
- RST_STREAM cost
- one frame — no TCP teardown needed
HPACK header compression
HTTP/1.1 sends full headers on every request — typically 400–800 bytes per request even if only the path changes. HTTP/2 uses HPACK (RFC 7541) to compress HEADERS frames:
- Static table — 61 pre-defined (name, value) pairs with common headers (
":method": "GET",":status": "200","content-type": "text/html", etc.). A single byte index replaces the full header. - Dynamic table — entries added per connection as new headers appear. If the first request sends
user-agent: Mozilla/5.0, the second request can reference it with a 1–2 byte index, sending only the delta.
Result: after the first request to a domain, subsequent requests reduce header overhead from ~400 bytes to ~20–50 bytes. On a page with 50 sub-resources, that saves ~18 KB of header data.
The catch: the dynamic table is ordered and shared across all streams. If index 60 is added to the table and a HEADERS frame referencing index 60 is lost, subsequent HEADERS frames cannot decode index 60 until retransmission arrives. On TCP this is invisible (TCP delivers in order anyway). On QUIC-based HTTP/3, this ordering dependency breaks stream independence — which is why HTTP/3 replaced HPACK with QPACK (covered in the next lesson).
HTTP/2 flow control
Each HTTP/2 connection has connection-level and stream-level flow control via WINDOW_UPDATE frames — the receiver tells the sender how many bytes it is willing to buffer. Defaults: 65,535 bytes per stream, sometimes raised to 16 MiB by modern servers for high-throughput APIs.
SETTINGS_MAX_CONCURRENT_STREAMS bounds how many streams a client can open simultaneously (typical: 100). Hitting the limit forces the client to queue requests, undoing some of the multiplexing benefit. Tuning: raise the window size on long-lived streams; raise max-concurrent-streams for chatty APIs.
Trace a modern browser loading a page over HTTP/2 from clean state.
Server Push: the feature that failed
HTTP/2 introduced Server Push — the server could send resources the browser had not yet requested. On receiving a GET /index.html, the server could send a PUSH_PROMISE for /style.css, betting the browser would need it.
In theory, this saves a request-response round-trip. In practice it failed on three counts:
- The server has no visibility into what the browser already cached. It blindly pushes /style.css even if the browser has a fresh cached copy, wasting bandwidth.
- Push only helps on the first navigation — on warm loads, the browser is already caching aggressively and pushed resources arrive unrequested.
- Middleboxes and CDNs didn’t handle push correctly, leading to duplicate transfers.
Chrome removed Server Push in 2022. RFC 9113 deprecates PUSH_PROMISE entirely. The modern replacement: 103 Early Hints + <link rel="preload"> — the server sends a 103 informational response pointing to critical sub-resources, letting the browser decide whether to fetch them. This gives the browser agency to check its cache first.
Why is Server Push effectively dead in 2026?
HTTP/2 multiplexing eliminates HOL blocking — true or false?
Order the events a modern browser does to fetch https://example.com over HTTP/2:
- 1 DNS resolution for example.com
- 2 TCP handshake
- 3 TLS 1.3 handshake (ALPN selects h2)
- 4 HTTP/2 SETTINGS frame exchanged
- 5 HEADERS frame sent for page request
- 6 DATA frames received with HTML body
- 7 50 more HEADERS frames sent for sub-resources
- 8 DATA frames interleaved from all 50 streams
Diagnose: why does this HTTP/2 API service have higher latency on mobile networks than HTTP/1.1?
- 01Explain why HTTP/2 multiplexing does NOT fully solve head-of-line blocking.
- 02What is HPACK and what is its main limitation?
- 03What replaced HTTP/2 Server Push and why does it work better?
HTTP/2 introduced stream multiplexing over a single TCP connection, replacing HTTP/1.1’s six-connection workaround. Frames (HEADERS, DATA, RST_STREAM, SETTINGS, WINDOW_UPDATE) are the unit of transfer; streams interleave freely on one connection. HPACK cuts header size by 80–90% using a static table and per-connection dynamic table. Flow control via WINDOW_UPDATE prevents buffer exhaustion. Server Push — the server proactively sending uncached resources — was removed from Chrome in 2022 because the server cannot know what the browser has cached; 103 Early Hints replaced it. The remaining open problem: a single lost TCP packet stalls every stream — TCP-layer HOL blocking that HTTP/3 solves with QUIC.
appears again in5
- MVCC: why readers and writers never wait for each otherjunior
- Act 7 in depth: sharding, co-location, and the seven-tier tradeoff cascademiddle
- Observability, anti-patterns, and production triagesenior
- Raft in the real world: partitions, slow disks, and client routingmiddle
- Raft in production: membership changes, Multi-Raft, and observabilitysenior