HTTP/3
**HTTP/3 不是“直接跑在 UDP 上的 HTTP”,而是“把 HTTP 语义映射到 QUIC 上”,而 QUIC 本身是一个基于 UDP 的、多路复用且默认安全的传输层协议。** RFC 9114 把 HTTP/3 定义为 HTTP 语义到 QUIC 的映射;RFC 9000 把 QUIC 定义为 UDP-based multiplexed and secure transport;RFC 9001 说明 QUIC 用 TLS 来完成安全握手和加密保护。
[!info] related notes
- 所属 MOC: http-and-frontend-networking-moc
- 语义总览: http
- 对比: http1-http2
HTTP/3
可以把 HTTP/3 先记成一句话:
HTTP/3 不是“直接跑在 UDP 上的 HTTP”,而是“把 HTTP 语义映射到 QUIC 上”,而 QUIC 本身是一个基于 UDP 的、多路复用且默认安全的传输层协议。 RFC 9114 把 HTTP/3 定义为 HTTP 语义到 QUIC 的映射;RFC 9000 把 QUIC 定义为 UDP-based multiplexed and secure transport;RFC 9001 说明 QUIC 用 TLS 来完成安全握手和加密保护。(IETF Datatracker)
1. 为什么会有 HTTP/3
HTTP/2 已经把 HTTP 做成了二进制分帧和多路复用,但它仍然跑在 TCP 上。RFC 9114 直接点出 HTTP/2 的根问题:由于 HTTP/2 的多路复用对 TCP 的丢包恢复机制不可见,一个丢失或乱序的数据包会让所有活跃事务一起停顿,哪怕只有其中一个流真正受影响。QUIC 把多路复用和每流流控下沉到传输层,并提供按流可靠交付,所以一个流的丢包不再天然卡住别的流。(IETF Datatracker)
这就是 HTTP/3 的核心动机:HTTP/2 解决了应用层队头阻塞,但没解决 TCP 层队头阻塞;HTTP/3 借 QUIC,把“流”提升到传输层,让不同请求真正彼此独立得多。 RFC 9114 明说,HTTP/3 连接里每个请求-响应对占用一个 QUIC 流,流彼此独立,所以一个流被阻塞或丢包,不会阻止其他流前进。(IETF Datatracker)
2. 它和 HTTP/2 的根本区别,不只是“换成 UDP”
更准确的分层应该是:
HTTP 语义 → HTTP/3 分帧 → QUIC 传输 → QUIC 的包保护/握手 → UDP。
RFC 9001 特别强调,QUIC 不是“把 TLS record 再套在 UDP 上”;相反,TLS 握手消息是直接承载在 QUIC 里,而 QUIC 自己接管了原来 TLS record layer 和一部分传输层要做的事。也就是说,HTTP/3 的安全层次和 HTTPS over TCP 的堆叠方式并不一样。(IETF Datatracker)
所以,HTTP/3 既不是“HTTP over UDP 直接裸传”,也不是“HTTP/2 换个套接字类型”。它是 HTTP over QUIC,而 QUIC 又是安全的、带流的、带拥塞控制和丢包恢复的传输协议。(IETF Datatracker)
3. HTTP/3 是怎么建立起来的
一个 HTTP/3 连接,通常先要让客户端知道这个 origin 支持 HTTP/3。RFC 9114 规定,origin 可以通过 Alt-Svc 宣告一个等价的 HTTP/3 端点,例如 Alt-Svc: h3=":50781"。客户端拿到这个信息后,可以尝试向对应的 UDP 端点建立 QUIC 连接。建立过程中,HTTP/3 通过 TLS 握手里的 ALPN token h3 来表明“我要说的是 HTTP/3”。同时,HTTP/3 依赖 QUIC v1 作为底层传输,而 QUIC v1 使用 TLS 1.3 或更高版本作为握手协议。(IETF Datatracker)
连接一旦建立,HTTP/3 层并不是立刻就“自由发所有帧”。规范要求:每一端都要建立一个控制流,并把 SETTINGS 作为该控制流上的第一帧发出去。 SETTINGS 适用于整个 HTTP/3 连接,不适用于某个单独流,而且在 HTTP/3 里它只能在连接开始时发一次,之后不能再改。(IETF Datatracker)
4. 它怎么承载一个请求和响应
HTTP/3 里,所有客户端发起的双向 QUIC 流都用来承载 HTTP 请求/响应。也就是说,一个请求-响应对对应一条 client-initiated bidirectional stream。RFC 9114 还明确说:HTTP 不需要再像 HTTP/2 那样自己做一层额外的多路复用,因为一个 QUIC 流里的数据天然就对应某个 HTTP 事务,或者对应整个连接上下文。(IETF Datatracker)
从消息结构看,HTTP/3 里的一个 HTTP 消息仍然非常像你已经熟悉的 HTTP 抽象:
先是 header section,用一个 HEADERS 帧发送;
如果有正文,用一串 DATA 帧发送;
如果有 trailer,再用一个 HEADERS 帧发送。
而且规范明确规定:DATA 不能出现在第一个 HEADERS 之前,trailing HEADERS 之后也不能再出现 HEADERS 或 DATA。(IETF Datatracker)
这里一个和 HTTP/1.1 非常值得对照的点是:HTTP/3 不定义 transfer coding,Transfer-Encoding 头字段禁止使用。 也就是说,HTTP/1.1 那套 chunked 传输编码在 HTTP/3 里不存在;正文边界由帧和流结束来表达,而不是靠文本分块编码。(IETF Datatracker)
5. HTTP/3 里除了请求流,还有哪些流
HTTP/3 不只有“请求流”这一种。RFC 9114 规定了几类关键流:
客户端发起的双向流用于请求/响应;
而单向流则用于连接级控制和扩展用途。
每一端都必须建立一个 control stream;QPACK 还需要额外两条单向流;另外服务器推送也会用到 push stream。规范甚至要求两端的传输参数至少允许对端创建三条单向流,就是为了控制流和 QPACK 流能正常建立。(IETF Datatracker)
控制流在 HTTP/3 里非常关键。它的 stream type 是 0x00,每一端只能有一条;它的第一帧必须是 SETTINGS;如果控制流被提前关掉,属于连接级错误。这个设计和 HTTP/2 的“stream 0 + 各类连接级帧”思路有点像,但在 HTTP/3 里它被放到了专门的单向控制流上。(IETF Datatracker)
6. QPACK 为什么取代 HPACK
HTTP/2 的头压缩是 HPACK,但 RFC 9114 解释得很直白:HPACK 依赖压缩后字段段的按序传输,而 QUIC 不给你“全连接统一有序”这个保证,所以 HTTP/3 必须换掉它。于是 HTTP/3 使用 QPACK,并且把表状态的更新与确认放到独立的单向流里完成。(IETF Datatracker)
QPACK 可以理解成“为 QUIC 环境重新设计的 HPACK”。RFC 9204 说它依然有静态表和动态表,但它专门追求降低头阻塞:静态表引用和字面量表示永远不会带来队头阻塞;而动态表引用只有在编码器还没收到“该表项已被解码端确认可用”的信号时,才会有阻塞风险。也就是说,QPACK 不是“绝对没有任何阻塞可能”,而是把阻塞风险从 HPACK 那种天然耦合的形式,改造成更可控的形式。(IETF Datatracker)
7. 为什么说 HTTP/3 更适合有丢包、移动网络的场景
QUIC 把可靠性做成了每流有序、跨流独立。RFC 9114 明确说,QUIC 流本身提供可靠的、按序的字节交付,但不会保证不同流之间的交付顺序。这正是 HTTP/3 想要的:单个响应内部仍然有序,但不同请求之间不互相拖累。(IETF Datatracker)
除此之外,QUIC 还有一个 HTTP/1.1 和 HTTP/2 都没有的强特性:connection migration。RFC 9000 规定,连接可以在不同的 IP 地址和端口之间迁移,并通过 path validation 确认新路径可用。结合 connection ID,这意味着连接不再死绑某个四元组;在某些网络切换场景下,连接有机会继续活着,而不是像 TCP 那样通常直接断掉重来。(IETF Datatracker)
8. 建连延迟为什么通常更低
RFC 9000 和 RFC 9001 都把 low-latency connection establishment 列为 QUIC 的核心能力。对新连接,TLS 1.3 + QUIC 通常能在 1-RTT 内把连接建立并保护起来;对于和同一服务器的后续连接,客户端还可能使用 0-RTT,也就是在握手一开始就发送应用数据。(IETF Datatracker)
但 0-RTT 有一个必须牢记的代价:重放攻击风险。RFC 9001 明确说,0-RTT 应用数据可能被攻击者重放,因此不适合携带那类“一旦重复执行就会产生不希望副作用”的指令;RFC 9114 也明确说,HTTP/3 使用 0-RTT 时必须应用抗重放措施。换句话说,0-RTT 不是“白捡的一次加速”,它总是和 replay 风险绑在一起。(IETF Datatracker)
9. HTTP/3 把 HTTP/2 的哪些事情交给了 QUIC
这是理解 HTTP/3 的关键。
在 HTTP/2 里,很多连接级能力是 HTTP/2 自己的帧层在做;但在 HTTP/3 里,其中很大一部分被 QUIC 接管了。RFC 9114 的附录直接列出了一些变化:
RST_STREAM 不再存在,因为 QUIC 自己管理流生命周期;
PING 不再存在,因为 QUIC 已经提供等价能力;
WINDOW_UPDATE 不再存在,因为 QUIC 自己负责流控;
CONTINUATION 不再存在,因为 HTTP/3 允许更大的 HEADERS / PUSH_PROMISE 帧;
而 SETTINGS 只在连接开始时发送一次。(IETF Datatracker)
所以你会发现,HTTP/3 的一个重要设计哲学是:HTTP 只保留 HTTP 真正关心的语义和分帧,把传输问题尽量交给 QUIC。 这也是它看起来比 HTTP/2 更“干净”的原因之一。(IETF Datatracker)
10. GOAWAY 在 HTTP/3 里怎么用
HTTP/3 仍然有 GOAWAY,但它的意义是优雅关闭连接,不是“立刻把连接掐断”。RFC 9114 明确写道:GOAWAY 用来告诉对端“别再开新请求/新 push 了”,同时允许已接收的请求继续处理;GOAWAY 本身不会关闭连接。(IETF Datatracker)
这对重试语义很重要。服务器发出 GOAWAY 后,客户端就能判断哪些请求不会再被处理,并把这些未处理请求安全地放到别的连接上重试;而没有 GOAWAY 就直接断连接时,客户端往往只能保守地认为某些在途请求“可能已经处理过,也可能没有”。(IETF Datatracker)
11. 服务器推送在 HTTP/3 里还在,但机制变了
HTTP/3 标准里仍然有 server push。RFC 9114 规定,服务器推送不再像 HTTP/2 那样直接靠原来的流关联方式,而是通过 push ID、PUSH_PROMISE、MAX_PUSH_ID、push stream 这套机制管理。推送流本身是单向流,服务器用它去兑现之前承诺的 pushed response。(IETF Datatracker)
所以如果只从协议层说,HTTP/3 没有把“推送”删掉;它只是把推送的承诺、额度和实际响应分离得更清楚。(IETF Datatracker)
12. 把一次典型 HTTP/3 交互串起来
把前面所有点串起来,一个典型的 HTTP/3 过程可以概括成:
客户端先通过 Alt-Svc 知道某个 origin 支持 h3;
然后向对应 UDP 端点建立 QUIC 连接,在 TLS 握手里通过 ALPN h3 协商应用协议;
双方各自打开一条 control stream,并把 SETTINGS 作为第一帧发出去;
客户端随后打开一个请求流,发送 HEADERS,如果有请求体再发 DATA;
服务器在同一条请求流上返回 HEADERS 和 DATA;
整个过程中别的请求在别的 QUIC 流上并发进行,单个流的丢包不会把所有请求一起卡死;
连接结束前,任一端都可以用 GOAWAY 做优雅收尾。(IETF Datatracker)
13. 真正吃透 HTTP/3,要抓住这几个关键词
HTTP 语义不变,传输底座换成 QUIC;多路复用下沉到传输层;跨流不再被 TCP 丢包一起拖住;TLS 1.3 融进 QUIC;头压缩从 HPACK 变成 QPACK;连接可以 1-RTT/0-RTT 建立;连接 ID 和路径验证带来迁移能力;很多 HTTP/2 的连接级帧被 QUIC 吸收。 这几个点抓住了,HTTP/3 的骨架就基本完整了。(IETF Datatracker)
下一条我就把 一次真实的 GET 请求分别按 HTTP/1.1、HTTP/2、HTTP/3 三种方式并排拆开,看它们在线上的差别。