5 min read #networking #HTTP
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:

  1. Head-of-Line Blocking: One slow request → all other requests on the same connection wait
  2. Header Redundancy: Each request repeats large header blocks (Cookie, User-Agent, Accept)
  3. 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, Chrome chrome://net-internals/#http2

Keywords: HTTP/2, multiplexing, HPACK, binary framing, SETTINGS, stream, flow control, server push