本页目录

HTTP/2

HTTP/2 用一条 TCP 连接承载多个并发 stream——帧复用、HPACK 头部压缩、服务器推送。队头阻塞从 HTTP 层消掉了,但转移到了 TCP 层(丢包阻塞所有 stream),这正是 HTTP/3 转向 QUIC 的原因。

概述

HTTP/2(RFC 7540, 2015)不改 HTTP 语义(methods/status codes/headers),但完全重写了传输层:文本 → 二进制分帧、一个 TCP 连接 → 多路复用 stream、header 冗余 → HPACK 压缩。目标是解决 HTTP/1.1 的性能瓶颈——队头阻塞和 header 暴增。尽管实践效果显著,TCP 层仍存在队头阻塞(丢包阻塞所有 stream),HTTP/3 继而转向 QUIC 解决此问题。Server Push 因使用率低已被 Chrome 移除。

HTTP/2 的目标

解决 HTTP/1.1 的性能瓶颈:

  1. Head-of-Line Blocking: 一个 request 慢 → 同连接所有 requests 等
  2. Header 冗余: 每个 request 重复大段 headers (Cookie, User-Agent, Accept)
  3. 连接数: browser 开 6-8 个 TCP 连接做并行 (resource waste + non-deterministic ordering)

HTTP/2: 多路复用 + header 压缩 + server push → 一个 TCP 连接高效处理所有请求。

Binary Framing

HTTP/2 是二进制协议 (不是文本):

Frame format (9 bytes header + variable payload):

Length (3 bytes): payload 长度 ≤ 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): 哪个 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):
  只在 stream 0 发送:
    SETTINGS_HEADER_TABLE_SIZE (0x1): HPACK 动态表最大大小 (default 4096)
    SETTINGS_ENABLE_PUSH (0x2): 0=disable server push (most servers do)
    SETTINGS_MAX_CONCURRENT_STREAMS (0x3): 最大并发 stream 数 (recommend ≥100)
    SETTINGS_INITIAL_WINDOW_SIZE (0x4): 初始 flow control window (65535)
    SETTINGS_MAX_FRAME_SIZE (0x5): 最大 frame size (16384-16777215)
    SETTINGS_MAX_HEADER_LIST_SIZE (0x6): 最大 header列表大小 (bytes)

Stream Multiplexing

一个 TCP 连接 → 多个 stream (client-initiated: odd, server-initiated: even):

Stream 0: 控制帧 only (SETTINGS, GOAWAY)
Stream 1: GET /index.html
Stream 3: GET /style.css   ← 并行! 不需要等 Stream 1 完成
Stream 5: GET /script.js    ← 也并行

每个 stream:
  - 独立的 flow control (WINDOW_UPDATE per stream)
  - Stream 1 丢包 → TCP 重传 → stream 1 卡住
    → 对其他 streams 的影响: 理论上没有 (multiplexing at frame level)
    → 实际上: TCP 丢包阻塞所有 stream (这就是 HTTP/3 要解决的 TCP HOL blocking!)

HPACK

HPACK = 静态表 (61 entries) + 动态表 (per-connection) + Huffman encoding

静态表 (永不改变):
  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

动态表 (FIFO, max size = SETTINGS_HEADER_TABLE_SIZE):
  每个 HEADERS response 结束后 → 存入动态表 (if compressible)
  → 后续 HEADERS request → 只引用 index → 极俭省

与 gzip 压缩对比 (为什么不用 gzip):
  - CRIME 攻击: gzip 压缩整个 header block → 压缩率泄露 cookie 内容
  - HPACK: per-header compression → 无跨-header 压缩 → 安全

Server Push (已废弃)

HTTP/2 Server Push:
  Client: GET /index.html
  Server: 返回 index.html + PUSH_PROMISE (style.css) + push style.css data
  → 客户端预先收到 style.css → 不需要等 browser parse + GET

为什么废弃:
  - 缓存问题: client 可能已经有 style.css (cache) → server push 浪费带宽
  - 实际使用率低: Chrome 统计 <2% pushed assets were used
  - 复杂性: server 不知道 client 的 cache state

Chrome 106+ 已移除 server push

参考

  • RFC: 7540, 7541, 9113 (revision)
  • 工具⁠: curl --http2 -v, nghttp2, Chrome chrome://net-internals/#http2

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