On this page
HTTP/2
HTTP/2 carries multiple concurrent streams over a single TCP connection—frame multiplexing, HPACK header compression, server push. Head-of-line blocking is eliminated at the HTTP layer but shifts to the TCP layer (packet loss blocks all streams), which is precisely why HTTP/3 moved to QUIC.
Overview
HTTP/2 (RFC 7540, 2015) does not change HTTP semantics (methods/status codes/headers), but completely rewrites the transport layer: text → binary framing, one TCP connection → multiplexed streams, header redundancy → HPACK compression. The goal is to solve HTTP/1.1 performance bottlenecks—head-of-line blocking and excessive header sizes. Although practical results are significant, TCP still suffers from head-of-line blocking (packet loss blocks all streams), leading HTTP/3 to adopt QUIC to address this issue. Server Push was removed from Chrome due to low usage.
HTTP/2 Goals
Address HTTP/1.1 performance bottlenecks:
- Head-of-Line Blocking: One slow request → all other requests on the same connection wait
- Header Redundancy: Each request repeats large header blocks (Cookie, User-Agent, Accept)
- Connection Count: Browsers open 6-8 TCP connections for parallelism (resource waste + non-deterministic ordering)
HTTP/2: Multiplexing + header compression + server push → one TCP connection efficiently handles all requests.
Binary Framing
HTTP/2 is a binary protocol (not text):
Frame format (9 bytes header + variable payload):
Length (3 bytes): payload length ≤ 2^14 (16384, default) or 2^24-1 (max frame size)
Type (1 byte): DATA(0x0), HEADERS(0x1), PRIORITY(0x2), RST_STREAM(0x3),
SETTINGS(0x4), PUSH_PROMISE(0x5), PING(0x6), GOAWAY(0x7),
WINDOW_UPDATE(0x8), CONTINUATION(0x9)
Flags (1 byte): depends on type
Reserved (1 bit) + Stream Identifier (31 bits): which stream
DATA frame:
flags: END_STREAM(0x1), PADDED(0x8)
HEADERS frame:
payload: HPACK-compressed header block fragment
flags: END_STREAM(0x1), END_HEADERS(0x4), PADDED(0x8), PRIORITY(0x20)
SETTINGS frame (connection-level):
Only sent on stream 0:
SETTINGS_HEADER_TABLE_SIZE (0x1): HPACK dynamic table max size (default 4096)
SETTINGS_ENABLE_PUSH (0x2): 0=disable server push (most servers do)
SETTINGS_MAX_CONCURRENT_STREAMS (0x3): max concurrent streams (recommend ≥100)
SETTINGS_INITIAL_WINDOW_SIZE (0x4): initial flow control window (65535)
SETTINGS_MAX_FRAME_SIZE (0x5): max frame size (16384-16777215)
SETTINGS_MAX_HEADER_LIST_SIZE (0x6): max header list size (bytes)
Stream Multiplexing
One TCP connection → multiple streams (client-initiated: odd, server-initiated: even):
Stream 0: Control frames only (SETTINGS, GOAWAY)
Stream 1: GET /index.html
Stream 3: GET /style.css ← Parallel! No need to wait for Stream 1 to complete
Stream 5: GET /script.js ← Also parallel
Each stream:
- Independent flow control (WINDOW_UPDATE per stream)
- Stream 1 packet loss → TCP retransmission → Stream 1 stalls
→ Impact on other streams: theoretically none (multiplexing at frame level)
→ In practice: TCP packet loss blocks all streams (this is the TCP HOL blocking that HTTP/3 aims to solve!)
HPACK
HPACK = Static table (61 entries) + Dynamic table (per-connection) + Huffman encoding
Static table (never changes):
Index 1: :authority
Index 2: :method GET
Index 3: :method POST
Index 4: :path /
Index 5: :path /index.html
Index 6: :scheme http
Index 7: :scheme https
Index 8: :status 200
Index 9: :status 204
...
Index 61: www-authenticate
Dynamic table (FIFO, max size = SETTINGS_HEADER_TABLE_SIZE):
After each HEADERS response → stored in dynamic table (if compressible)
→ Subsequent HEADERS requests → only reference index → highly efficient
Comparison with gzip compression (why not use gzip):
- CRIME attack: gzip compresses the entire header block → compression ratio leaks cookie content
- HPACK: per-header compression → no cross-header compression → secure
Server Push (Deprecated)
HTTP/2 Server Push:
Client: GET /index.html
Server: Returns index.html + PUSH_PROMISE (style.css) + push style.css data
→ Client receives style.css in advance → no need to wait for browser to parse + GET
Why deprecated:
- Caching issues: Client may already have style.css (cache) → server push wastes bandwidth
- Low actual usage: Chrome stats show <2% of pushed assets were used
- Complexity: Server doesn't know client's cache state
Server push was removed in Chrome 106+
References
- RFC: 7540, 7541, 9113 (revision)
- Tools:
curl --http2 -v,nghttp2, Chromechrome://net-internals/#http2
Keywords: HTTP/2, multiplexing, HPACK, binary framing, SETTINGS, stream, flow control, server push