HTTP 1.1、HTTP 2 与 HTTP 3:把三次协议演进串起来
写这篇是因为最近又在排查一个网络问题,顺手把 HTTP 这几个版本的演进重新过了一遍。HTTP/1.1 已经用了二十多年,HTTP/2 是 2015 年标准化的,HTTP/3 这两年才在主流站点铺开。它们不是简单加几个字段,而是底层传输模型在变。
这篇把 HTTP/1.1、HTTP/2、HTTP/3 (QUIC) 放一起讲,沿着"为什么不够用 → 怎么改 → 改完还剩什么问题"这条线写下来。
相关笔记
- [[笔记/web/计算机网络/TCP 重传、滑动窗口、流量控制与拥塞控制]]
- [[笔记/web/计算机网络/计算机网络框架]]
- [[笔记/web/计算机网络/HTTP 1.1 与 HTTP 2 协议对比]]
1. HTTP/1.0 简史
HTTP 是 Tim Berners-Lee 在 1989 年搞出来的,最初的需求很简单:让分布在全世界的物理学家能互相引用文档。每个文档就是一个文件,浏览器把文件名发给服务器,服务器把内容返回回来。
第一个正式版本 HTTP/1.0(RFC 1945,1996 年)核心就一条:一次请求一次响应,然后断开 TCP。
浏览器 ── GET /index.html ──> 服务器
浏览器 <─ 200 OK + HTML ── 服务器
浏览器 ── GET /logo.png ──> 服务器(再建一条 TCP)
浏览器 <─ 200 OK + PNG ── 服务器
一个页面里 10 张图片,就要建 10 次 TCP。TCP 握手 1.5 个 RTT,加上 TLS 又是几个 RTT。在拨号上网那个年代,握手是大头。
HTTP/1.0 没解决这个问题,但它定了一套可扩展的协议骨架:方法、状态码、首部字段、Body。这套骨架后来没变过——HTTP/2 改的是"怎么把这些东西传过去",不是"这些东西是什么意思"。
3. 浏览器为什么要开 6 个连接
既然 HTTP/1.1 的连接是串行的,要并发就只能多开几条 TCP。
Chrome 对同一个域名默认并发 6 条,Firefox 也是 6 条左右。这不是 bug,是 HTTP/1.1 时代的必要妥协。
浏览器 → 域名 example.com
连接 #1: GET /
连接 #2: GET /a.js
连接 #3: GET /b.css
连接 #4: GET /1.png
连接 #5: GET /2.png
连接 #6: GET /3.png
副作用:
- 每条连接独立跑 TCP 拥塞控制,互相不协调;
- 每条连接都吃内存、文件描述符、端口;
- 前端工程师为了"减少请求数"发明了雪碧图、CSS/JS 合并、域名分片(sharding)这些奇技淫巧。
这些技巧在 HTTP/2 之后都不再需要了。
5. 二进制分帧
HTTP/1.x 的报文是文本:
GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
\r\n
文本协议的好处是可读、调试方便。代价是解析慢、歧义多(换行到底是 \n、\r\n、还是别的)。
HTTP/2 换成二进制。每个帧有 9 字节固定头:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
Length:帧负载字节数Type:帧类型(HEADERS、DATA、PRIORITY 等)Flags:标志位(ENDSTREAM、ENDHEADERS)Stream Identifier:流 ID
常见帧类型:
| Type | 作用 |
|---|---|
| HEADERS | 携带请求/响应首部 |
| DATA | 携带 Body |
| PRIORITY | 声明流优先级 |
| RST_STREAM | 取消一个流 |
| SETTINGS | 协商连接参数 |
| WINDOW_UPDATE | 流量控制 |
| PING | 心跳与 RTT 测量 |
| GOAWAY | 通知要关连接了 |
| PUSH_PROMISE | 服务器推送预告 |
注意 HEADERS 和 DATA 是分开的帧。所以一个请求可以先发 HEADERS,再分多个 DATA 帧发 Body,每个 DATA 帧任意长度。
7. HPACK 头部压缩
HTTP 头部是 Web 性能的老油条问题:
- 普通请求的头部大约 600-800 字节;
Cookie、User-Agent、Accept、Referer这些字段每个请求几乎一样;- HTTP/1.x 不压缩头部(gzip 是压 body 的);
- 一个页面 50 个请求,光头部就是 30-40 KB。
HTTP/2 引入了 HPACK(RFC 7541)。
静态表与动态表
核心是两张表:
- 静态表:规范写死的 61 个常见 header 名值对,比如
:method GET、:scheme https、:authority example.com。 - 动态表:连接双方各自维护的、可变化的表。
发送 header 时:
- 在静态表里 → 只发 1 字节索引;
- 在动态表里 → 发索引 + 匹配方式;
- 都不在 → 发字面值,可选地插入动态表。
第一次发:
:method = GET → 索引 2
:scheme = https → 索引 7
:authority = example.com → 字面值,插入动态表 #62
:path = /api/users → 字面值,插入动态表 #63
user-agent = curl/7.0 → 字面值
第二次同域名请求:
:method = GET → 索引 2(1 字节)
:scheme = https → 索引 7(1 字节)
:authority = example.com → 索引 62(动态表,1 字节)
:path = /api/posts → 字面值
user-agent = curl/7.0 → 字面值
Huffman 编码
字面值还会用一套专门为 HTTP 头部字符频率设计的 Huffman 编码再压一遍。解码是 O(n) 位运算,性能高。
为什么 HPACK 比 gzip 更适合 HTTPS
有人会问:gzip 压一下不就完了?
关键是安全:
- gzip 是基于上下文的压缩,靠重复模式;
- 攻击者可以通过观察压缩长度推断明文——这就是 CRIME 和 BREACH 攻击;
- HPACK 是基于字典索引的预定义编码,攻击者无法通过长度推断明文。
HPACK 是为了"在 HTTPS 下既压缩又安全"专门设计出来的。
9. HTTP/2 没解决的:TCP 层的队头阻塞
HTTP/2 解决了应用层的队头阻塞,没解决传输层的。
为什么?HTTP/2 的多路复用是逻辑层面的,底层还是一条 TCP。TCP 把数据看作有序字节流,协议规定:
任何一个包丢失,TCP 必须缓存所有后续包,直到丢失的包被重传并交付,才能交给上层应用。
所以一旦某个 TCP 包丢了,整条连接的所有 HTTP/2 流都卡住——即使其他流的帧早就到了。
Stream 1: HEADERS → DATA → DATA → [包丢失] → 等重传
Stream 3: HEADERS → DATA → DATA → [包到了] → 但被 TCP 缓存住
Stream 5: HEADERS → DATA → DATA → [包到了] → 但被 TCP 缓存住
在丢包率低的有线网络这不是问题。但移动网络、卫星网络、地铁车厢这些场景,HTTP/2 的优势会被严重削弱。
还有两个 HTTP/2 没解决的:
- 握手开销:TCP 三次握手 + TLS 1.2 至少 3-RTT 才能传第一个字节;
- 连接迁移:TCP 连接绑四元组(IP + 端口),切换网络(Wi-Fi → 4G)就断了。
为了解决这些,Google 干脆把 TCP 也换了,基于 UDP 做了 QUIC。这就是 HTTP/3。
11. 握手对比:为什么 HTTP/3 是 0-RTT
这是 HTTP/3 最容易被低估的改进。先看对比:
HTTP/1.1 + TLS 1.2
TCP 握手: SYN → SYN+ACK → ACK (1 RTT)
TLS 握手: ClientHello → ServerHello+Cert+... → Finished (2 RTT)
HTTP 请求: GET / (3 RTT 才能传第一个字节)
HTTP/2 + TLS 1.2
跟 HTTP/1.1 一样的握手开销,只是 ALPN 协商时声明 h2。
HTTP/2 + TLS 1.3
TCP 握手: SYN → SYN+ACK → ACK (1 RTT)
TLS 1.3: ClientHello → ServerHello+Finished+Cert (1 RTT)
HTTP 请求: GET / (2 RTT)
TLS 1.3 把握手从 2-RTT 降到 1-RTT。
HTTP/3 + QUIC
QUIC 握手(内置 TLS 1.3): ClientHello → ServerHello+Cert+Finished (1 RTT)
HTTP 请求: GET / (1 RTT)
如果是恢复连接(之前连过):ClientHello 直接带 Early Data
↓
(0 RTT 就能传应用数据)
QUIC 把 TCP 握手和 TLS 握手合并了。1-RTT 握手 + 0-RTT 恢复。
0-RTT 不是没有代价——存在重放攻击风险,所以一般只用于幂等请求(GET、HEAD)。涉及金钱的操作(POST、转账)不能用 0-RTT。
13. 落地:Nginx 怎么开 HTTP/2 和 HTTP/3
HTTP/2
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
# 性能调优
http2_max_field_size 16k;
http2_max_header_size 32k;
http2_max_requests 1000;
http2_body_buffer_size 16k;
location / {
root /var/www/html;
}
}
http2 是 listen 的参数,不是单独的配置项。必须搭配 HTTPS——浏览器不支持明文 HTTP/2。
HTTP/3
Nginx 1.25.0+ 原生支持 QUIC(之前是实验模块):
server {
# 监听 UDP 443(不是 TCP)
listen 443 quic reuseport;
# 同时监听 TCP 443 兜底
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
# 告诉客户端 HTTP/3 可用
add_header Alt-Svc 'h3=":443"; ma=86400';
location / {
root /var/www/html;
}
}
关键点:
- 443 端口要同时监听 TCP 和 UDP——HTTP/3 走 UDP,老客户端走 TCP/HTTP/2;
add_header Alt-Svc让浏览器知道这个站点支持 HTTP/3;reuseport让多个 worker 进程共享同一个 UDP 端口;- 防火墙必须放行 UDP 443(很多公司内网只放 TCP 443,HTTP/3 在这种网络下不可用)。
验证
# HTTP/2
curl -I --http2 https://example.com
# 输出:HTTP/2 200
# HTTP/3
curl -I --http3 https://example.com
# 需要 curl 编译时带 quiche 或 ngtcp2
# 浏览器
# Chrome DevTools → Network → Protocol 列
# 看到 h2 表示 HTTP/2,h3 表示 HTTP/3
15. 参考
[1] RFC 1945 - HTTP/1.0. https://datatracker.ietf.org/doc/html/rfc1945
[2] RFC 7230 - HTTP/1.1: Message Syntax and Routing. https://datatracker.ietf.org/doc/html/rfc7230
[3] RFC 7540 - HTTP/2. https://datatracker.ietf.org/doc/html/rfc7540
[4] RFC 7541 - HPACK. https://datatracker.ietf.org/doc/html/rfc7541
[5] RFC 8446 - TLS 1.3. https://datatracker.ietf.org/doc/html/rfc8446
[6] RFC 9000 - QUIC. https://datatracker.ietf.org/doc/html/rfc9000
[7] RFC 9114 - HTTP/3. https://datatracker.ietf.org/doc/html/rfc9114
[8] High Performance Browser Networking. Ilya Grigorik. https://hpbn.co/
[9] HTTP/2 简介. Google Developers. https://developers.google.com/web/fundamentals/performance/http2
[10] Chrome Status - HTTP/2 Server Push 移除. https://chromestatus.com/feature/5762410658996224
[11] Nginx HTTP/2 模块. https://nginx.org/en/docs/http/ngx_http_v2_module.html
[12] 图解网络. 小林. https://xiaolincoding.com/network/