| Internet 工程任务组 (IETF) | M. Bishop,编辑 |
| 请求评论:9114 | Akamai |
| 类别:标准轨道 | 2022年6月 |
| ISSN:2070-1721 |
HTTP/3
摘要
QUIC 传输协议具备若干在 HTTP 传输中理想的特性,例如流复用、每流流量控制以及低延迟的连接建立。本文档描述了在 QUIC 上映射 HTTP 语义的方法。本文档还指出被 QUIC 吸收的 HTTP/2 特性,并描述如何将 HTTP/2 的扩展移植到 HTTP/3。
本备忘录的状态
这是一个互联网标准轨道文档。
本文档是 Internet 工程任务组 (IETF) 的产物,代表 IETF 社区的共识。它已接受公开审查,并已获 Internet Engineering Steering Group (IESG) 批准发布。有关互联网标准的更多信息,请参见 RFC 7841 的第 2 节。
有关本文档当前状态、任何勘误以及如何提供反馈的信息,可在 https://www.rfc-editor.org/info/rfc9114 获取。
Copyright Notice
Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.
1. 引言
HTTP 语义 ([HTTP]) 被用于互联网上的广泛服务。这些语义最常与 HTTP/1.1 和 HTTP/2 一起使用。HTTP/1.1 已在多种传输和会话层上使用,而 HTTP/2 主要在基于 TCP 的 TLS 上使用。HTTP/3 在一个新的传输协议 QUIC 上支持相同的语义。
1.1. HTTP 的先前版本
HTTP/1.1 ([HTTP/1.1]) 使用以空白分隔的文本字段来传递 HTTP 消息。尽管这些交换是可读的,但使用空白进行消息格式化会导致解析复杂性并对变体行为过度容忍。
由于 HTTP/1.1 不包含复用层,通常使用多个 TCP 连接并行处理请求。然而,这对拥塞控制和网络效率有负面影响,因为 TCP 不会在多个连接之间共享拥塞控制。
HTTP/2 ([HTTP/2]) 引入了二进制分帧和复用层,以在不修改传输层的情况下改善延迟。然而,由于 HTTP/2 的复用并行性对于 TCP 的丢包恢复机制不可见,丢失或重排序的数据包会导致所有活动事务出现停顿,而不管该事务是否直接受到该丢失数据包的影响。
1.2. 委托给 QUIC
QUIC 传输协议整合了流复用和每流流量控制,类似于 HTTP/2 分帧层提供的功能。通过在流级别提供可靠性并在整个连接上提供拥塞控制,QUIC 有能力相比于基于 TCP 的映射提升 HTTP 的性能。QUIC 还在传输层整合了 TLS 1.3 ([TLS]),提供了与在 TCP 上运行 TLS 相当的机密性和完整性,同时具有比 TCP Fast Open ([TFO]) 更低的连接建立延迟。
本文档定义了 HTTP/3:即在 QUIC 传输协议上映射 HTTP 语义,设计上大量借鉴了 HTTP/2。HTTP/3 依赖 QUIC 来提供数据的机密性和完整性保护;对等方认证;以及可靠的、按序的、按流交付。在将流生命周期和流量控制问题委托给 QUIC 的同时,在每个流上使用类似于 HTTP/2 的二进制分帧。某些 HTTP/2 特性被 QUIC 吸收,而其他特性则在 QUIC 之上实现。
QUIC 在 [QUIC-TRANSPORT] 中描述。有关 HTTP/2 的完整描述,请参见 [HTTP/2]。
2. HTTP/3 协议概述
HTTP/3 使用 QUIC 传输协议和类似于 HTTP/2 的内部分帧层,为 HTTP 语义提供传输。
一旦客户端知道某个端点存在 HTTP/3 服务器,它就会打开一个 QUIC 连接。QUIC 提供协议协商、基于流的复用以及流量控制。HTTP/3 端点的发现在 第 3.1 节 中描述。
在每个流内,HTTP/3 通信的基本单位是帧(第 7.2 节)。每种帧类型都有不同的用途。例如,HEADERS 和 DATA 帧构成了 HTTP 请求和响应的基础(第 4.1 节)。适用于整个连接的帧通过专用的 控制流 进行传输。
请求的复用通过 QUIC 的流抽象执行,详见 第 2 节 的 [QUIC-TRANSPORT]。每一对请求-响应消耗一个单独的 QUIC 流。流彼此独立,因此一个流被阻塞或遭遇数据包丢失不会阻止其他流的进展。
服务器推送是 HTTP/2 ([HTTP/2]) 引入的一种交互模式,允许服务器在预期客户端将发出指定请求时将一个请求-响应交换推送给客户端。这在网络使用与潜在的延迟收益之间进行权衡。若干 HTTP/3 帧用于管理服务器推送,例如 PUSH_PROMISE、MAX_PUSH_ID 和 CANCEL_PUSH。
与 HTTP/2 一样,请求和响应字段在传输时会被压缩。由于 HPACK ([HPACK]) 依赖于压缩字段节的按序传输(这是 QUIC 不保证的),HTTP/3 用 QPACK ([QPACK]) 取代了 HPACK。QPACK 使用单独的单向流来修改和跟踪字段表状态,而编码的字段节在引用表状态时不修改表。
2.1. 文档组织
下面的各节提供了 HTTP/3 连接生命周期的详细概述:
线协议细节和与传输的交互在后续各节中描述:
附录部分提供了其他资源:
- "HTTP/3 的扩展" (第 9 节) 描述了如何在将来的文档中添加新功能。
- 有关 HTTP/2 与 HTTP/3 之间更详细的比较,请参见 附录 A。
2.2. 约定与术语
本文档中关键词 "MUST"、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、"NOT RECOMMENDED"、"MAY" 和 "OPTIONAL" 应按 BCP 14 中的描述进行解释(仅当且仅当这些词以全大写形式出现,如此处所示)。[RFC2119] [RFC8174]
本文档使用来自 [QUIC-TRANSPORT] 的可变长度整数编码。
下列术语将被使用:
- abort:
-
连接或流的突然终止,可能由于错误条件引起。
- client:
-
发起 HTTP/3 连接的端点。客户端发送 HTTP 请求并接收 HTTP 响应。
- connection:
-
使用 QUIC 作为传输协议的两个端点之间的传输层连接。
- connection error :
-
影响整个 HTTP/3 连接的错误。
- endpoint:
-
连接的任一端:客户端或服务器。
- frame:
-
HTTP/3 中流上通信的最小单位,由一个头和根据帧类型结构化的可变长度字节序列组成。在本文档和 [QUIC-TRANSPORT] 中均存在称为“帧”的协议元素。当引用来自 [QUIC-TRANSPORT] 的帧时,帧名称前会加上 "QUIC" 前缀。例如,“QUIC CONNECTION_CLOSE frames”。未加此前缀的引用指的是在 第 7.2 节 中定义的帧。
- HTTP/3 connection:
-
协商的应用层协议为 HTTP/3 的 QUIC 连接。
- peer:
-
一个端点。当讨论特定端点时,“peer” 指相对于主要讨论主体的远端端点。
- receiver:
-
接收帧的端点。
- sender:
-
发送帧的端点。
- server:
-
接受 HTTP/3 连接的端点。服务器接收 HTTP 请求并发送 HTTP 响应。
- stream:
-
由 QUIC 传输提供的双向或单向字节流。HTTP/3 连接内的所有流都可视为“HTTP/3 流”,但在 HTTP/3 中定义了多种流类型。
- stream error :
-
单个流上的应用层错误。
术语 “content” 在 RFC 9110 的第 6.4 节 中定义,参见 [HTTP]。
最后,术语“resource”、“message”、“user agent”、“origin server”、“gateway”、“intermediary”、“proxy” 和 “tunnel” 在 RFC 9110 的第 3 节 中定义,参见 [HTTP]。
本文档中的数据包图使用 RFC 9000 第 1.3 节 中定义的格式来说明字段的顺序和大小,参见 [QUIC-TRANSPORT]。
3. 连接建立与管理
3.1. 发现 HTTP/3 端点
HTTP 依赖权威响应的概念:也就是在响应消息生成时(由 origin server 或在其指示下)已确定为对该请求最合适的响应,基于目标资源的状态。定位 HTTP URI 的权威服务器在 RFC 9110 的第 4.3 节 中讨论,参见 [HTTP]。
“https” 方案将权威性与客户端认为对 URI 的 authority 组件中标识的主机可被信任的证书的拥有权相关联。在 TLS 握手中接收到服务器证书后,客户端 MUST 使用 RFC 9110 第 4.3.4 节 中描述的过程验证该证书是否可接受地匹配 URI 的 origin server。如果证书无法相对于 URI 的 origin server 进行验证,客户端 MUST NOT 将该服务器视为该 origin 的权威服务器。
客户端 MAY 通过将主机标识符解析为 IP 地址、在指示的端口上与该地址建立 QUIC 连接(包括按上文所述验证服务器证书),并在该受保护连接上向服务器发送针对该 URI 的 HTTP/3 请求消息,来尝试访问带有 “https” URI 的资源。除非使用其他机制选择 HTTP/3,否则在 TLS 握手期间的应用层协议协商(ALPN;见 [RFC7301])扩展中使用令牌 "h3"。
连接性问题(例如,UDP 被阻塞)可能导致无法建立 QUIC 连接;在这种情况下,客户端 SHOULD 尝试使用基于 TCP 的 HTTP 版本。
服务器 MAY 在任何 UDP 端口上提供 HTTP/3;备用服务通告总是包含显式端口,并且 URI 包含显式端口或与该方案相关联的默认端口。
3.1.1. HTTP 替代服务
HTTP origin 可以通过 Alt-Svc HTTP 响应首部字段或 HTTP/2 的 ALTSVC 帧 ([ALTSVC]) 使用 "h3" ALPN 令牌来通告等效的 HTTP/3 端点的可用性。
例如,一个 origin 可以在 HTTP 响应中包含如下首部字段,指示在相同主机名的 UDP 端口 50781 上提供 HTTP/3:
Alt-Svc: h3=":50781"
在收到指示 HTTP/3 支持的 Alt-Svc 记录时,客户端 MAY 尝试与指示的主机和端口建立 QUIC 连接;如果该连接成功,客户端即可使用本文档中描述的映射发送 HTTP 请求。
3.1.2. 其他方案
尽管 HTTP 与传输协议无关,但 "http" 方案将权威性与能够在所示端口上接受 TCP 连接的能力相关联,因为该端口属于 authority 组件中标识的主机。因为 HTTP/3 不使用 TCP,HTTP/3 不能用于直接访问由 "http" URI 标识的资源的权威服务器。然而,诸如 [ALTSVC] 之类的协议扩展允许权威服务器识别其他同样具有权威性的服务,这些服务可能可通过 HTTP/3 到达。
在对方案不是 "https" 的 origin 发起请求之前,客户端 MUST 确保服务器愿意为该方案提供服务。对于方案为 "http" 的 origin,一种实验性方法在 [RFC8164] 中有所描述。将来可能会为各种方案定义其他机制。
3.2. 连接建立
HTTP/3 依赖于 QUIC 版本 1 作为底层传输。未来规范 MAY 定义其他 QUIC 传输版本与 HTTP/3 的配合使用。
QUIC 版本 1 使用 TLS 1.3 或更高版本作为其握手协议。HTTP/3 客户端 MUST 支持在 TLS 握手期间向服务器指示目标主机的机制。如果服务器由域名标识(见 [DNS-TERMS]),客户端 MUST 发送 Server Name Indication (SNI;[RFC6066]) TLS 扩展,除非使用了指示目标主机的替代机制。
QUIC 连接按 [QUIC-TRANSPORT] 中描述的方式建立。在连接建立期间,通过在 TLS 握手中选择 ALPN 令牌 "h3" 来指示对 HTTP/3 的支持。握手中也 MAY 提供对其他应用层协议的支持。
3.3. 连接重用
HTTP/3 连接在多个请求间保持持久。为了获得最佳性能,期望客户端不会在确定与服务器不再需要进一步通信之前关闭连接(例如,当用户离开某个网页时),或者直到服务器关闭连接。
一旦与服务器端点的连接存在,该连接 MAY 被重用于具有多个不同 URI authority 组件的请求。要将现有连接用于新的 origin,客户端 MUST 使用 RFC 9110 第 4.3.4 节 中描述的过程验证服务器为新 origin 提供的证书。这意味着客户端需要保留服务器证书以及验证该证书所需的任何附加信息;不这样做的客户端将无法重用连接以用于其他 origin。
如果证书在任何方面对新 origin 不可接受,则该连接 MUST NOT 被重用,并且应为新 origin 建立一个新连接(SHOULD)。如果证书无法验证的原因可能也适用于已与该连接关联的其他 origin,客户端 SHOULD 重新验证这些 origin 的服务器证书。例如,如果证书验证失败是由于证书已过期或被吊销,这可能会用于使所有其他使用该证书建立权威性的 origin 失效。
客户端 SHOULD NOT 打开多个到给定 IP 地址和 UDP 端口的 HTTP/3 连接,该 IP 地址和端口可能来自 URI、选定的备用服务 ([ALTSVC])、配置的代理,或对上述任何项的名称解析。客户端 MAY 使用不同的传输或 TLS 配置向相同的 IP 地址和 UDP 端口打开多个 HTTP/3 连接,但 SHOULD 避免使用相同配置创建多个连接。
鼓励服务器尽可能长时间保持打开的 HTTP/3 连接,但在必要时允许终止空闲连接。当任一端点选择关闭 HTTP/3 连接时,终止端点 SHOULD 首先发送一个 GOAWAY 帧(见 第 5.2 节),以便双方能够可靠地确定先前发送的帧是否已被处理并优雅地完成或终止任何必要的剩余任务。
不希望客户端为特定 origin 重用 HTTP/3 连接的服务器可以通过在响应请求时发送 421(Misdirected Request,错误定向请求)状态码来表明其对该请求不具权威性;参见 RFC 9110 的第 7.4 节。
4. 在 HTTP/3 中表达 HTTP 语义
4.1. HTTP 消息分帧
客户端在 请求流 上发送 HTTP 请求, 该流是由客户端发起的双向 QUIC 流;参见 第 6.1 节。客户端 MUST 只在给定流上发送单个请求。服务器在与请求相同的流上发送零个或多个中间 HTTP 响应,随后发送单个最终 HTTP 响应,具体如下所述。关于中间和最终 HTTP 响应的描述,请参见 第 15 节 的 [HTTP]。
被推送的响应在服务器发起的单向 QUIC 流上发送;参见 第 6.2.2 节。服务器按标准响应的相同方式发送零个或多个中间 HTTP 响应,随后发送单个最终 HTTP 响应。有关推送的更多细节,请参见 第 4.6 节。
在给定流上,接收多个请求或在最终 HTTP 响应之后再接收额外的 HTTP 响应 MUST 被视为 格式错误。
一个 HTTP 消息(请求或响应)由下列部分组成:
接收无效的帧序列 MUST 被视为一种 连接错误,类型为 H3_FRAME_UNEXPECTED。 特别地,在任何 HEADERS 帧之前出现 DATA 帧,或在尾部 HEADERS 帧之后出现 HEADERS 或 DATA 帧,均被视为无效。其他帧类型,尤其是未知帧类型,可能在遵守其自身规则的前提下被允许;参见 第 9 节。
服务器 MAY 在响应消息的帧之前、之后或与之交错地发送一个或多个 PUSH_PROMISE 帧。这些 PUSH_PROMISE 帧并非响应的一部分;详见 第 4.6 节。在 推送流 上不允许出现 PUSH_PROMISE 帧;如果推送的响应包含此类帧, 则 MUST 将其视为类型为 H3_FRAME_UNEXPECTED 的 连接错误。
HEADERS 和 PUSH_PROMISE 帧可能引用对 QPACK 动态表的更新。尽管这些更新不直接属于消息交换的一部分,但它们必须在消息可被消费之前被接收和处理。详见 第 4.2 节。
传输编码(参见 RFC 9112 第 7 节)未在 HTTP/3 中定义;不得使用 Transfer-Encoding 头字段(MUST NOT)。
当且仅当一个或多个中间响应(1xx;参见 RFC 9110 第 15.2 节)在对同一请求的最终响应之前出现时,响应 MAY 由多个消息组成。中间响应不包含内容或尾部节。
一次 HTTP 请求/响应交换完全占用一个客户端发起的双向 QUIC 流。发送请求后,客户端 MUST 关闭该流的发送方向。除非使用 CONNECT 方法(参见 第 4.4 节),客户端 MUST NOT 在收到响应之前将流关闭与接收响应相依。服务器在发送最终响应后 MUST 关闭流的发送方向。此时,QUIC 流完全关闭。
当流被关闭时,表示最终 HTTP 消息的结束。因为有些消息很大或长度不确定,端点 SHOULD 一旦接收到足够多的消息以便进行处理就开始处理部分 HTTP 消息。如果客户端发起的流在未接收到足够以提供完整响应的 HTTP 消息时终止,服务器 SHOULD 使用错误码 H3_REQUEST_INCOMPLETE 中止其响应流。
如果响应不依赖于请求中尚未发送和接收的任何部分,服务器可以在客户端发送完整请求之前发送完整响应。当服务器不需要接收请求的其余部分时,服务器 MAY 中止读取 请求流, 发送完整响应,并干净地关闭流的发送部分。在请求客户端停止在 请求流 上发送时,请求使用 H3_NO_ERROR 错误码 SHOULD。客户端 MUST NOT 因其请求被突然终止而丢弃完整响应,尽管客户端可因其他原因自行决定丢弃响应。如果服务器发送部分或完整响应但并未中止读取请求,客户端 SHOULD 继续发送请求内容并正常关闭流。
4.1.1. 请求取消与拒绝
一旦打开了 请求流,任一端点 MAY 取消该请求。客户端在不再感兴趣时取消请求;服务器在无法或选择不响应时取消请求。若可能,建议服务器发送带适当状态码的 HTTP 响应,而不是取消已开始处理的请求(RECOMMENDED)。
实现 SHOULD 通过突然终止仍然打开的流方向来取消请求。为此,实现会重置流的发送部分并中止读取流的接收部分;参见 RFC 9000 第 2.4 节 的描述。
当服务器在未进行任何应用处理的情况下取消请求时,该请求被视为“被拒绝”。服务器 SHOULD 使用错误码 H3_REQUEST_REJECTED 中止其响应流。在此上下文中,“已处理”意为流中的某些数据已被传递到可能基于该数据采取某些操作的更高层软件。客户端可以将服务器拒绝的请求视为从未发送,从而允许稍后重试这些请求。
服务器 MUST NOT 对部分或全部已处理的请求使用 H3_REQUEST_REJECTED 错误码。当服务器在部分处理之后放弃响应时,SHOULD 使用错误码 H3_REQUEST_CANCELLED 中止其响应流。
客户端 SHOULD 使用错误码 H3_REQUEST_CANCELLED 来取消请求。收到该错误码后,服务器 MAY 如果未执行任何处理则使用错误码 H3_REQUEST_REJECTED 突然终止响应。客户端 MUST NOT 使用 H3_REQUEST_REJECTED 错误码,除非服务器已使用该错误码请求关闭 请求流。
如果在接收完整响应后流被取消,客户端 MAY 忽略取消并使用该响应。但是,如果在接收部分响应后流被取消,则 SHOULD NOT 使用该响应。只有幂等操作(例如 GET、PUT 或 DELETE)可以安全重试;客户端 SHOULD NOT 自动重试非幂等方法的请求,除非它有某种方式确定请求语义独立于方法是幂等的,或有某种方式检测原始请求从未被应用。有关更多细节,请参见 RFC 9110 第 9.2.2 节。
4.1.2. 格式错误的请求与响应
格式错误的请求或响应是指帧序列在语法上可能有效但由于下列原因而无效的情况:
- 存在被禁止的字段或伪首部字段,
- 缺少强制的伪首部字段,
- 伪首部字段具有无效的值,
- 伪首部字段出现在常规字段之后,
- HTTP 消息的序列无效,
- 包含大写的字段名,或
- 字段名或值中包含无效字符。
当请求或响应在包含 Content-Length 头字段时被定义为具有内容(参见 RFC 9110 第 8.6 节)且所接收的 DATA 帧长度之和不等于 Content-Length 头字段值,则该请求或响应为格式错误。被定义为永远不包含内容的响应,即使存在 Content-Length,也可以具有非零的 Content-Length 头字段,尽管没有在 DATA 帧中包含内容。
处理 HTTP 请求或响应的中介(即不作为隧道的任何中介) MUST NOT 转发格式错误的请求或响应。检测到的格式错误请求或响应 MUST 被视为类型为 H3_MESSAGE_ERROR 的 流错误。
对于格式错误的请求,服务器 MAY 在关闭或重置流之前发送一个指示错误的 HTTP 响应。客户端 MUST NOT 接受格式错误的响应。请注意,这些要求旨在防护针对 HTTP 的几类常见攻击;它们刻意严格,因为宽松的处理可能使实现面临这些漏洞的风险。
4.2. HTTP 字段
HTTP 消息将元数据作为一系列称为“HTTP 字段”的键值对来承载;参见 第 6.3 节 和 第 6.5 节 的 [HTTP]。有关已注册 HTTP 字段的列表,请参见由 IANA 维护的“Hypertext Transfer Protocol (HTTP) Field Name Registry”(<https://www.iana.org/assignments/http-fields/>)。与 HTTP/2 类似,HTTP/3 在字段名中字符的使用、Connection 头字段以及伪首部字段的使用上有额外的考虑。
字段名是包含 ASCII 子集字符的字符串。HTTP 字段名和字段值的属性在 RFC 9110 第 5.1 节 中有更详细讨论。字段名中的字符 MUST 在编码之前转换为小写。包含大写字符的请求或响应头字段名 MUST 被视为 格式错误。
HTTP/3 不使用 Connection 头字段来指示连接特定字段;在此协议中,连接特定的元数据通过其他方式传递。端点 MUST NOT 生成包含连接特定字段的 HTTP/3 字段节;任何包含连接特定字段的消息 MUST 被视为 格式错误。
唯一的例外是 TE 头字段,它 MAY 出现在 HTTP/3 请求头中;当存在时,MUST NOT 包含除 "trailers" 之外的任何值。
将 HTTP/1.x 消息转换为 HTTP/3 的中介 MUST 删除连接特定的头字段,如 RFC 9110 第 7.6.1 节 所讨论的,否则它们的消息将被其他 HTTP/3 端点视为 格式错误。
4.2.1. 字段压缩
[QPACK] 描述了 HPACK 的一种变体,使编码器可以在引起多少首部阻塞方面拥有一定控制权。这允许编码器在压缩效率和延迟之间进行权衡。HTTP/3 使用 QPACK 来压缩头部和尾部节,包括头部节中存在的控制数据。
为提高压缩效率,Cookie 头字段([COOKIES])MAY 在压缩之前拆分为单独的字段行,每行包含一个或多个 cookie-pair。如果解压后的字段节包含多个 cookie 字段行,则在将其传入除 HTTP/2 或 HTTP/3 之外的上下文(例如 HTTP/1.1 连接或通用 HTTP 服务器应用程序)之前,MUST 使用两字节定界符 "; "(ASCII 0x3b, 0x20)将它们连接成单个字节串。
4.2.2. 首部大小约束
HTTP/3 实现 MAY 对其在单个 HTTP 消息上接受的最大消息头大小施加限制。接收比其愿意处理的更大头部节的服务器可以发送 HTTP 431(Request Header Fields Too Large)状态码(参见 [RFC6585])。客户端可以丢弃无法处理的响应。字段列表的大小基于字段的未压缩大小计算,包括名称和值的字节长度加上每个字段 32 字节的开销。
如果实现希望告知其对等方该限制,它可以作为字节数通过 SETTINGS_MAX_FIELD_SECTION_SIZE 参数传达。接收此参数的实现 SHOULD NOT 发送超过所指示大小的 HTTP 消息头,因为对等方很可能拒绝处理它。然而,HTTP 消息可能在到达原始服务器之前经过一个或多个中介;参见 RFC 9110 第 3.7 节。由于该限制由处理消息的每个实现分别应用,低于此限制的消息并不保证会被接受。
4.3. HTTP 控制数据
与 HTTP/2 类似,HTTP/3 使用一系列伪首部字段,其字段名以字符 :(ASCII 0x3a)开头。这些伪首部字段传递消息控制数据;参见 RFC 9110 第 6.2 节。
伪首部字段不是 HTTP 字段。端点 MUST NOT 生成本文件未定义的伪首部字段。但是,扩展可以协商修改此限制;参见 第 9 节。
伪首部字段仅在其定义的上下文中有效。为请求定义的伪首部字段 MUST NOT 出现在响应中;为响应定义的伪首部字段 MUST NOT 出现在请求中。伪首部字段 MUST NOT 出现在尾部节中。端点 MUST 将包含未定义或无效伪首部字段的请求或响应视为 格式错误。
所有伪首部字段 MUST 在头部节中出现在常规头字段之前。任何包含在常规头字段之后出现的伪首部字段的请求或响应 MUST 被视为 格式错误。
4.3.1. 请求伪首部字段
下列伪首部字段为请求所定义:
- ":method":
-
包含 HTTP 方法(参见 RFC 9110 第 9 节)
- ":scheme":
-
包含目标 URI 的 scheme 部分(参见 RFC 3986 第 3.1 节)。
-
:scheme 伪首部不限于 "http" 和 "https" 的 URI。代理或网关可以为非 HTTP 的方案翻译请求,从而使 HTTP 能与非 HTTP 服务交互。
-
关于使用非 "https" 方案的指导,请参见 第 3.1.2 节。
- ":authority":
-
包含目标 URI 的 authority 部分(参见 RFC 3986 第 3.2 节)。对于 "http" 或 "https" 方案的 URI,authority MUST NOT 包含已弃用的 userinfo 子组件。
-
为确保能准确重现 HTTP/1.1 请求行,当从具有方法特定形式请求目标的 HTTP/1.1 请求转换时,该伪首部字段 MUST 被省略;参见 RFC 9110 第 7.1 节。直接生成 HTTP/3 请求的客户端 SHOULD 使用 :authority 伪首部字段而非 Host 头字段。将 HTTP/3 请求转换为 HTTP/1.1 的中介 MUST 在请求中没有 Host 字段时通过复制 :authority 伪首部字段的值来创建 Host 字段。
- ":path":
-
包含目标 URI 的路径和查询部分("path-absolute" 生成式及可选的一个 ? 字符(ASCII 0x3f)后跟 "query" 生成式;参见 RFC 3986 的第 3.3 和第 3.4 节)。
-
对于 "http" 或 "https" URI,该伪首部字段 MUST NOT 为空;对于不含路径组件的 "http" 或 "https" URI,MUST 包含值 /(ASCII 0x2f)。不包含路径组件的 OPTIONS 请求在 :path 伪首部字段中包含值 *(ASCII 0x2a);参见 RFC 9110 第 7.1 节。
所有 HTTP/3 请求 MUST 对 :method、:scheme 和 :path 伪首部字段各包含且仅包含一个值,除非请求为 CONNECT 请求;参见 第 4.4 节。
如果 :scheme 伪首部字段标识了具有强制 authority 组件的方案(包括 "http" 和 "https"),则请求 MUST 包含 :authority 伪首部字段或 Host 头字段之一。若这些字段存在,则 MUST NOT 为空。如果两者都存在,则 MUST 包含相同的值。如果该方案不具有强制 authority 组件且请求目标未提供 authority,则请求 MUST NOT 包含 :authority 伪首部或 Host 头字段。
省略强制伪首部字段或包含无效伪首部字段值的 HTTP 请求为 格式错误。
HTTP/3 未定义携带包含在 HTTP/1.1 请求行中的版本标识的方法。HTTP/3 请求隐含协议版本为 "3.0"。
4.3.2. 响应伪首部字段
对于响应,定义了单个 ":status" 伪首部字段,用于承载 HTTP 状态码;参见 RFC 9110 第 15 节。该伪首部字段 MUST 包含在所有响应中;否则响应为 格式错误(参见 第 4.1.2 节)。
HTTP/3 未定义携带包含在 HTTP/1.1 状态行中的版本或原因短语的方法。HTTP/3 响应隐含协议版本为 "3.0"。
4.4. CONNECT 方法
CONNECT 方法请求接收方建立到请求目标所标识的目标原点服务器的隧道;参见 RFC 9110 第 9.3.6 节。它主要与 HTTP 代理一起使用,以便为与原点服务器建立 TLS 会话从而与 "https" 资源交互。
在 HTTP/1.x 中,CONNECT 用于将整个 HTTP 连接转换为到远程主机的隧道。在 HTTP/2 和 HTTP/3 中,CONNECT 方法用于在单个流上建立隧道。
CONNECT 请求 MUST 按如下方式构造:
- :method 伪首部字段设置为 "CONNECT"
- :scheme 和 :path 伪首部字段被省略
- :authority 伪首部字段包含要连接的主机和端口(等同于 CONNECT 请求目标的 authority-form;参见 RFC 9110 第 7.1 节)。
支持 CONNECT 的代理建立到 :authority 伪首部字段所标识服务器的 TCP 连接(参见 [RFC0793])。一旦该连接成功建立,代理向客户端发送包含 2xx 系列状态码的 HEADERS 帧,如 RFC 9110 第 15.3 节 所定义。
流上的所有 DATA 帧对应于在 TCP 连接上发送或接收的数据。由客户端发送的任何 DATA 帧的载荷由代理转发到 TCP 服务器;从 TCP 服务器接收的数据由代理打包到 DATA 帧中发送给客户端。请注意,TCP 段的大小和数量不保证可预测地映射到 HTTP DATA 或 QUIC STREAM 帧的大小和数量。
一旦 CONNECT 方法完成,只允许在该流上发送 DATA 帧。如果扩展的定义明确允许,可使用扩展帧(MAY)。接收任何其他已知帧类型 MUST 被视为类型为 H3_FRAME_UNEXPECTED 的 连接错误。
TCP 连接可由任一对等方关闭。当客户端结束 请求流(即代理的接收流进入 "Data Recvd" 状态)时,代理将对其与 TCP 服务器的连接设置 FIN 位。当代理接收到带 FIN 位的数据包时,它将关闭其发送给客户端的发送流。TCP 连接在单方向保持半关闭并非无效,但服务器通常对此处理不佳,因此客户端 SHOULD NOT 在仍期望从 CONNECT 目标接收数据时关闭发送方向的流。
TCP 连接错误 由突然终止流来表示。代理将 TCP 连接中的任何错误(包括接收到带 RST 位的 TCP 段)视为类型为 H3_CONNECT_ERROR 的 流错误。
相应地,如果代理检测到流或 QUIC 连接的错误,它 MUST 关闭 TCP 连接。如果代理检测到客户端已重置流或中止读取流,它 MUST 关闭 TCP 连接。如果流被客户端重置或读取被中止,代理 SHOULD 在另一方向上执行相同操作以确保流的两个方向都被取消。在所有这些情况下,如果底层 TCP 实现允许,代理 SHOULD 发送带 RST 位的 TCP 段。
由于 CONNECT 会创建到任意服务器的隧道,支持 CONNECT 的代理 SHOULD 将其使用限制在一组已知端口或一列安全请求目标上;更多细节请参见 RFC 9110 第 9.3.6 节。
4.5. HTTP 升级
HTTP/3 不支持 HTTP Upgrade 机制(参见 RFC 9110 第 7.8 节)或 101(Switching Protocols)信息性状态码(参见 RFC 9110 第 15.2.2 节)。
4.6. 服务器推送
服务器推送是一种交互模式,允许服务器在预期客户端将发出指定请求时将一个请求-响应交换推送给客户端。这在网络使用与潜在延迟收益之间进行权衡。HTTP/3 的服务器推送与 RFC 9113 第 8.2 节 中描述的类似,但使用了不同的机制。
每个服务器推送由服务器分配一个唯一的 push ID。push ID 在整个 HTTP/3 连接的生命周期内在各种上下文中用于引用该推送。
push ID 空间从零开始,到由 MAX_PUSH_ID 帧设置的最大值结束。特别地,服务器在客户端发送 MAX_PUSH_ID 帧之前无法进行推送。客户端通过发送 MAX_PUSH_ID 帧来控制服务器可以承诺的推送数量。服务器 SHOULD 顺序使用 push ID,从零开始。客户端 MUST 在未发送任何 MAX_PUSH_ID 帧或当流引用的 push ID 大于最大 push ID 时,将接收到的 推送流 视为类型为 H3_ID_ERROR 的 连接错误。
push ID 用于一个或多个承载请求消息控制数据和头字段的 PUSH_PROMISE 帧。这些帧在产生推送的 请求流 上发送。这样可以将服务器推送与客户端请求关联。当同一 push ID 在多个 请求流 上被承诺时,解压后的请求字段节 MUST 包含相同的字段且顺序相同,且每个字段的名称和值均 MUST 完全相同。
随后,push ID 会包含在最终实现这些承诺的 push stream 中。该 push stream 标识它所实现的承诺的 push ID,然后按照 第 4.1 节 所述包含对承诺请求的响应。
最后,push ID 可以用于 CANCEL_PUSH 帧;参见 第 7.2.3 节。客户端使用该帧表示它不希望接收承诺的资源。服务器使用该帧表示它不会实现先前的承诺。
并非所有请求都可以被推送。服务器 MAY 推送具有以下属性的请求:
- 可缓存;参见 RFC 9110 第 9.2.3 节
- 安全(safe);参见 RFC 9110 第 9.2.1 节
- 不包含请求内容或尾部节
服务器 MUST 在 :authority 伪首部字段中包含一个其具有权威性的值。如果客户端尚未为被推送请求所指示的原点验证该连接,它 MUST 在在连接上为该原点发送请求之前执行相同的验证过程;参见 第 3.3 节。如果该验证失败,客户端 MUST NOT 将服务器视为该原点的权威服务器。
客户端 SHOULD 在收到承载不可缓存、未知为安全、指示存在请求内容的请求,或客户端不认为服务器对该请求具有权威性的 PUSH_PROMISE 帧时发送 CANCEL_PUSH 帧。任何相应的响应 MUST NOT 被使用或缓存。
每个被推送的响应与一个或多个客户端请求相关联。该推送与在其上收到 PUSH_PROMISE 帧的 请求流 相关联。同一服务器推送可以通过在多个 请求流 上使用相同 push ID 的 PUSH_PROMISE 帧与额外的客户端请求相关联。这些关联不影响协议的运行,但用户代理在决定如何使用被推送资源时 MAY 考虑这些关联。
PUSH_PROMISE 帧相对于响应某些部分的排序很重要。服务器 SHOULD 在发送引用承诺响应的 HEADERS 或 DATA 帧之前发送 PUSH_PROMISE 帧。这样可以降低客户端请求将被服务器推送资源所覆盖的可能性。
由于重排序,推送流 的数据可能在对应的 PUSH_PROMISE 帧之前到达。当客户端收到带尚未知 push ID 的新 推送流 时,关联的客户端请求和被推送的请求头字段均是未知的。客户端可以缓冲该流的数据以等待匹配的 PUSH_PROMISE 帧。客户端可以使用流流量控制(参见 RFC 9000 第 4.1 节)来限制服务器可对被推送流提交的数据量。如果在合理时间内未处理任何对应的 PUSH_PROMISE 帧,客户端 SHOULD 中止读取并丢弃已从 推送流 读取的数据。
推送流数据也可能在客户端已取消推送之后到达。在这种情况下,客户端可以使用错误码 H3_REQUEST_CANCELLED 中止读取该流。此操作请求服务器不要传输额外数据,并表明接收到的数据将被丢弃。
可缓存的被推送响应(参见 RFC 9111 第 3 节)可以由实现了 HTTP 缓存的客户端存储。被推送的响应在接收时被视为已在原点服务器上成功验证(例如,如果存在 "no-cache" 缓存响应指令;参见 RFC 9111 第 5.2.2.4 节)。
不可缓存的被推送响应 MUST NOT 被任何 HTTP 缓存存储。它们 MAY 单独向应用程序提供。
5. 连接关闭
一旦建立,HTTP/3 连接可以随时间用于多个请求和响应,直到连接被关闭。连接关闭可以通过几种不同的方式发生。
5.1. 空闲连接
每个 QUIC 端点在握手期间声明一个空闲超时。如果 QUIC 连接在该时长内保持空闲(未接收数据包),对等方将假定连接已被关闭。如果现有连接的空闲时间超过在 QUIC 握手期间协商的空闲超时,HTTP/3 实现在发起新请求时需要为新的请求打开一个新的 HTTP/3 连接,并且在接近空闲超时时 SHOULD 这样做;参见 RFC 9000 第 10.1 节。
当有请求的响应或服务器推送尚未完成时(如 RFC 9000 第 10.1.2 节 所述),HTTP 客户端应请求传输层保持连接打开。如果客户端不期望来自服务器的响应,则允许空闲连接超时优于维持可能不需要的连接以节省资源。网关 MAY 为了预期需要而维持连接,以避免建立连接的延迟成本。服务器 SHOULD NOT 主动保持连接打开。
5.2. 连接终止
即使连接未空闲,任一端点也可以决定停止使用该连接并发起优雅的连接关闭。端点通过发送一个 GOAWAY 帧来启动 HTTP/3 连接的优雅关闭。GOAWAY 帧包含一个标识符,用以指示接收方该连接上已处理或可能已处理的请求或推送的范围。服务器发送客户端发起的双向流 ID;客户端发送一个 push ID。具有该标识符或更大的请求或推送将被发送方拒绝(参见 第 4.1.1 节)。如果未处理任何请求或推送,此标识符 MAY 为零。
GOAWAY 帧中的信息使客户端和服务器能够就哪些请求或推送在 HTTP/3 连接关闭前被接受达成一致。在发送 GOAWAY 帧后,端点 SHOULD 明确取消(见第 4.1.1 节 和 7.2.3 节)任何标识符大于等于所示值的请求或推送,以便为受影响的流清理传输状态。随着更多请求或推送到达,端点 SHOULD 继续这样做。
在收到对端发送的 GOAWAY 帧后,端点 MUST NOT 在该连接上发起新的请求或承诺新的推送。客户端 MAY 建立新的连接以发送额外的请求。
有些请求或推送可能已经在传输中:
当已知连接将被关闭时,即便提前通知时间很短,服务器 SHOULD 发送 GOAWAY 帧,以便远端对等方能知道某个请求是否已被部分处理。例如,如果 HTTP 客户端在服务器关闭 QUIC 连接的同时发送了 POST 请求,如果服务器未发送 GOAWAY 帧来指示它可能已作用的流,客户端无法知道服务器是否开始处理该 POST 请求。
端点 MAY 发送多个表示不同标识符的 GOAWAY 帧,但每个帧中的标识符 MUST NOT 大于任何先前帧中的标识符,因为客户端可能已经在另一个 HTTP 连接上重试了未处理的请求。接收包含大于此前接收的标识符的 GOAWAY 应被视为类型为 H3_ID_ERROR 的 连接错误。
试图优雅关闭连接的端点可以发送一个值设置为最大可能值的 GOAWAY 帧(服务器为 262-4,客户端为 262-1)。这可确保对等方停止创建新的请求或推送。在允许足够时间以便在途请求或推送到达后,端点可以发送另一个 GOAWAY 帧以指明在连接结束前它可能接受哪些请求或推送。这样可以在不丢失请求的情况下干净地关闭连接。
客户端在其发送的 GOAWAY 中为 Push ID 字段选择值时具有更大的灵活性。值 262-1 表示服务器可以继续完成已经承诺的推送。较小的值表示客户端将拒绝具有大于或等于该值的推送。像服务器一样,客户端 MAY 发送后续的 GOAWAY 帧,只要所指示的 push ID 不大于先前发送的任何值。
即使 GOAWAY 指示给定请求或推送在接收时不会被处理或接受,底层传输资源仍然存在。发起这些请求的端点可以取消它们以清理传输状态。
一旦所有被接受的请求和推送都被处理完毕,端点可以允许连接变为空闲,或者它 MAY 发起立即关闭连接的操作。完成优雅关闭的端点 SHOULD 在关闭连接时使用 H3_NO_ERROR 错误码。
如果客户端已使用完可用的双向流 ID 来发起请求,则服务器无需发送 GOAWAY 帧,因为客户端无法发起更多请求。
5.3. 立即的应用关闭
HTTP/3 实现可以在任何时候立即关闭 QUIC 连接。这将向对等方发送一个 QUIC CONNECTION_CLOSE 帧,表明应用层已经终止连接。该帧中的应用错误码向对等方指示为何连接被关闭。有关在 HTTP/3 中关闭连接时可使用的错误码,请参见 第 8 节。
如果存在未显式关闭的打开流,则在连接关闭时这些流将被隐式关闭;参见 RFC 9000 第 10.2 节。
5.4. 传输关闭
由于各种原因,QUIC 传输层可能会向应用层指示连接已终止。这可能是由于对等方的明确关闭、传输级别错误,或中断连通性的网络拓扑变化导致的。
如果连接在没有发送 GOAWAY 帧的情况下终止,客户端 MUST 假定任何已发送的请求(无论全部还是部分)都可能已经被处理。
6. 流映射与使用
QUIC 流提供可靠的按序字节传递,但不对其他流上的字节传递顺序作出保证。在 QUIC 的第 1 版中,包含 HTTP 帧的流数据由 QUIC STREAM 帧承载,但该分层对 HTTP 分帧层是不可见的。传输层对接收到的流数据进行缓冲和排序,向应用暴露可靠的字节流。尽管 QUIC 允许在流内无序交付,但 HTTP/3 并不使用该特性。
QUIC 流可以是单向的(仅从发起方到接收方传输数据)或双向的(在两个方向传输数据)。流可以由客户端或服务器发起。有关 QUIC 流的更多细节,参见 RFC 9000 第 2 节。
当 HTTP 字段和数据在 QUIC 上传输时,QUIC 层处理大部分流管理。当使用 QUIC 时,HTTP 无需做任何额外的复用:发送到某个 QUIC 流的数据始终映射到特定的 HTTP 事务或整个 HTTP/3 连接上下文。
6.1. 双向流
所有客户端发起的双向流都用于 HTTP 请求和响应。双向流确保响应可以方便地与请求相关联。这些流称为请求流。
这意味着客户端的第一个请求发生在 QUIC 流 0 上,后续请求发生在流 4、8 等上。为了允许这些流被打开,HTTP/3 服务器 SHOULD 为允许的流数和初始流量控制窗口配置非零的最小值。为了不不必要地限制并行性,至少应允许同时存在 100 个请求流(SHOULD)。
HTTP/3 不使用服务器发起的双向流,尽管扩展可以为这些流定义用途。除非已协商此类扩展,客户端 MUST 将接收服务器发起的双向流视为类型为 H3_STREAM_CREATION_ERROR 的 连接错误。
6.2. 单向流
单向流(任一方向)用于多种用途。用途由流类型指示,该类型作为可变长度整数在流开始处发送。随后数据的格式和结构由该流类型决定。
Unidirectional Stream Header {
Stream Type (i),
}
图 1: 单向流头
本文档中定义了两种流类型:control streams(见第 6.2.1 节)和 push streams(见第 6.2.2 节)。[QPACK] 定义了另外两种流类型。其他流类型可以通过 HTTP/3 扩展定义;有关详细信息,参见 第 9 节。某些流类型被保留(参见 第 6.2.3 节)。
HTTP/3 连接在生命周期早期阶段的性能对单向流的创建和数据交换非常敏感。过度限制流的数量或这些流的流量控制窗口的端点会增加远端对等方过早达到限制并被阻塞的概率。具体而言,实现应考虑到远端对等方可能希望对其允许使用的一些单向流行使保留流行为(参见 第 6.2.3 节)。
每个端点需要至少为 HTTP control stream 创建一个单向流。QPACK 需要另外两个单向流,其他扩展可能需要更多流。因此,客户端和服务器发送的传输参数 MUST 允许对等方创建至少三个单向流。这些传输参数 SHOULD 还为每个单向流提供至少 1,024 字节的流量控制额度。
请注意,如果其对等方在创建关键单向流之前消耗了所有初始额度,端点并不需要授予额外额度以创建更多单向流。端点 SHOULD 先创建 HTTP control stream 以及必需扩展(例如 QPACK 编码器和解码器流)所需的单向流,然后在对等方允许时创建其他流。
如果流头指示的流类型不被接收方支持,则该流的其余部分无法被消费,因为语义未知。接收到未知流类型的接收方 MUST 要么中止读取该流,要么丢弃到达的数据而不作进一步处理。如果中止读取,接收方 SHOULD 使用 H3_STREAM_CREATION_ERROR 错误码或保留错误码(见 第 8.1 节)。接收方 MUST NOT 将未知流类型视为任何形式的 连接错误。
由于某些流类型可能影响连接状态,接收方 SHOULD NOT 在读取流类型之前丢弃来自传入单向流的数据。
实现 MAY 在尚不确定对等方是否支持它们的情况下发送流类型。然而,可能修改现有协议组件状态或语义的流类型,包括 QPACK 或其他扩展,MUST NOT 在确认对等方支持之前发送。
除非另有规定,发送方可以关闭或重置单向流。接收方 MUST 容忍在接收到单向流头之前该流被关闭或重置。
6.2.1. 控制流
控制流由流类型 0x00 指示。该流上的数据由 HTTP/3 帧组成,如 第 7.2 节 定义。
每一方 MUST 在连接开始时发起单个控制流,并在该流上将其 SETTINGS 帧作为该流的第一个帧发送。如果控制流的第一个帧是任何其他帧类型,则此情形 MUST 被视为类型为 H3_MISSING_SETTINGS 的 连接错误。每个对等方仅允许一个控制流;接收第二个宣称为控制流的流 MUST 被视为类型为 H3_STREAM_CREATION_ERROR 的 连接错误。发送方 MUST NOT 关闭控制流,接收方 MUST NOT 请求发送方关闭控制流。如果任一控制流在任何时间被关闭,此情形 MUST 被视为类型为 H3_CLOSED_CRITICAL_STREAM 的 连接错误。连接错误在 第 8 节 中有描述。
因为控制流的内容被用来管理其他流的行为,端点 SHOULD 提供足够的流量控制额度,以防止对等方的控制流被阻塞。
使用一对单向流而不是单个双向流。这允许任一对等方在可发送数据时立即发送数据。根据 QUIC 连接是否可用 0-RTT,客户端或服务器中的任一方可能先发送流数据。
6.2.2. 推送流
服务器推送是 HTTP/2 引入的可选特性,允许服务器在未收到请求前发起响应。有关更多细节,见 第 4.6 节。
推送流由流类型 0x01 指示,其后跟随以可变长度整数编码的满足承诺的 push ID。该流上剩余的数据由 HTTP/3 帧组成(见 第 7.2 节),并通过零个或多个中间 HTTP 响应随后一个最终 HTTP 响应来满足被承诺的服务器推送(见 第 4.1 节)。服务器推送和 push ID 在 第 4.6 节 中有说明。
只有服务器可以进行推送;如果服务器接收到客户端发起的推送流,此情形 MUST 被视为类型为 H3_STREAM_CREATION_ERROR 的 连接错误。
Push Stream Header {
Stream Type (i) = 0x01,
Push ID (i),
}
图 2: 推送流头
客户端 SHOULD NOT 在读取推送流头之前中止对推送流的读取,因为这可能导致客户端和服务器之间就哪些 push ID 已被消耗产生分歧。
每个 push ID MUST 仅在推送流头中使用一次。如果客户端检测到推送流头中包含曾在另一个推送流头中使用过的 push ID,客户端 MUST 将其视为类型为 H3_ID_ERROR 的 连接错误。
6.2.3. 保留的流类型
格式为 0x1f * N + 0x21 的流类型(N 为非负整数)被保留以行使对未知类型应被忽略要求的检验。这些流没有语义,可在需要应用层填充时发送。它们 MAY 也可在当前没有数据传输的连接上发送。端点 MUST NOT 将这些流视为接收时具有任何含义。
流的有效载荷和长度由发送实现以任意方式选择。发送保留流类型时,实现 MAY 干净地终止该流或重置它。重置流时,应使用 H3_NO_ERROR 错误码或保留错误码(见 第 8.1 节)。
7. HTTP 分帧层
HTTP 帧在 QUIC 流上承载,如 第 6 节 所述。HTTP/3 定义了三种流类型:control stream、request stream 和 push stream。本节描述 HTTP/3 帧格式及其允许的流类型;参见 表 1 以获取概览。附录 A.2 提供了 HTTP/2 与 HTTP/3 帧的比较。
| Frame | Control Stream | Request Stream | Push Stream | Section |
|---|---|---|---|---|
| DATA | No | Yes | Yes | 第 7.2.1 节 |
| HEADERS | No | Yes | Yes | 第 7.2.2 节 |
| CANCEL_PUSH | Yes | No | No | 第 7.2.3 节 |
| SETTINGS | Yes (1) | No | No | 第 7.2.4 节 |
| PUSH_PROMISE | No | Yes | No | 第 7.2.5 节 |
| GOAWAY | Yes | No | No | 第 7.2.6 节 |
| MAX_PUSH_ID | Yes | No | No | 第 7.2.7 节 |
| Reserved | Yes | Yes | Yes | 第 7.2.8 节 |
注意,与 QUIC 帧不同,HTTP/3 帧可以跨越多个数据包。
7.1. 帧布局
所有帧具有以下格式:
HTTP/3 Frame Format {
Type (i),
Length (i),
Frame Payload (..),
}
图 3: HTTP/3 帧格式
帧包含下列字段:
- Type:
-
标识帧类型的可变长度整数。
- Length:
-
描述帧负载字节长度的可变长度整数。
- Frame Payload:
-
由 Type 字段决定语义的负载。
每个帧的负载 MUST 精确包含其描述中标识的字段。包含额外字节的帧负载或在标识字段结束之前终止的帧负载 MUST 被视为类型为 H3_FRAME_ERROR 的 连接错误。特别地,冗余的长度编码 MUST 被验证为自洽;参见 第 10.8 节。
当流被干净地终止时,如果该流上的最后一个帧被截断,则此情形 MUST 被视为类型为 H3_FRAME_ERROR 的 连接错误。在任意点突然终止的流可能在帧的任何位置被重置。
7.2. 帧定义
7.2.1. DATA
DATA 帧(type=0x00)传输与 HTTP 请求或响应内容关联的任意可变长度字节序列。
DATA 帧 MUST 与 HTTP 请求或响应关联。如果在 control stream 上接收到 DATA 帧,接收方 MUST 使用类型为 H3_FRAME_UNEXPECTED 的 连接错误 响应。
DATA Frame {
Type (i) = 0x00,
Length (i),
Data (..),
}
图 4: DATA 帧
7.2.2. HEADERS
HEADERS 帧(type=0x01)用于承载使用 QPACK 编码的 HTTP 字段节。详见 [QPACK]。
HEADERS Frame {
Type (i) = 0x01,
Length (i),
Encoded Field Section (..),
}
图 5: HEADERS 帧
HEADERS 帧只能在 request streams 或 push streams 上发送。如果在 control stream 上接收到 HEADERS 帧,接收方 MUST 使用类型为 H3_FRAME_UNEXPECTED 的 连接错误 响应。
7.2.3. CANCEL_PUSH
CANCEL_PUSH 帧(type=0x03)用于在接收到 push stream 之前请求取消服务器推送。CANCEL_PUSH 帧通过以可变长度整数编码的 push ID 来标识服务器推送(见第 4.6 节)。
当客户端发送 CANCEL_PUSH 帧时,表示它不希望接收所承诺的资源。服务器 SHOULD 中止发送该资源,但具体机制取决于相应 push stream 的状态。如果服务器尚未创建 push stream,则它不会创建。如果 push stream 已打开,服务器 SHOULD 突然终止该流。如果该 push stream 已经结束,服务器 MAY 仍可突然终止该流或 MAY 不采取任何操作。
服务器发送 CANCEL_PUSH 帧以表明它将不会实现先前发送的承诺。除非客户端已收到并处理了被承诺的响应,否则客户端不能期望相应的承诺会被实现。无论是否已打开 push stream,一旦服务器确定某个承诺不会被实现,服务器 SHOULD 发送 CANCEL_PUSH 帧。如果流已被打开,服务器可以使用错误码 H3_REQUEST_CANCELLED 中止该流的发送。
发送 CANCEL_PUSH 帧对现有的 push streams 状态没有直接影响。当客户端已经接收到相应的 push stream 时,客户端 SHOULD NOT 发送 CANCEL_PUSH 帧。由于服务器可能尚未处理 CANCEL_PUSH,push stream 仍可能在客户端发送 CANCEL_PUSH 帧之后到达。客户端 SHOULD 使用错误码 H3_REQUEST_CANCELLED 中止读取该流。
CANCEL_PUSH 帧在 control stream 上发送。在控制流以外的流上接收到 CANCEL_PUSH 帧 MUST 被视为类型为 H3_FRAME_UNEXPECTED 的 连接错误。
CANCEL_PUSH Frame {
Type (i) = 0x03,
Length (i),
Push ID (i),
}
图 6: CANCEL_PUSH 帧
CANCEL_PUSH 帧携带以可变长度整数编码的 push ID。Push ID 字段标识被取消的服务器推送;见 第 4.6 节。如果收到的 CANCEL_PUSH 帧引用了在连接上当前允许范围之外的 push ID,则此情形 MUST 被视为类型为 H3_ID_ERROR 的 连接错误。
如果客户端收到 CANCEL_PUSH 帧,该帧可能指示一个由于重排序而尚未由 PUSH_PROMISE 帧提及的 push ID。如果服务器收到对尚未由 PUSH_PROMISE 帧提及的 push ID 的 CANCEL_PUSH 帧,则此情形 MUST 被视为类型为 H3_ID_ERROR 的 连接错误。
7.2.4. SETTINGS
SETTINGS 帧(type=0x04)传达影响端点通信方式的配置参数,例如偏好和对等方行为的约束。单独地,SETTINGS 参数也可被称为“setting”;每个设置参数的标识符和值可分别称为“setting identifier”和“setting value”。
SETTINGS 帧总是适用于整个 HTTP/3 连接,而不是单个流。每一端点 MUST 在其 control stream 的第一个帧上发送一个 SETTINGS 帧(见第 6.2.1 节),并且 MUST NOT 随后再次发送。如果端点在控制流上收到第二个 SETTINGS 帧,端点 MUST 对等方发送类型为 H3_FRAME_UNEXPECTED 的 连接错误。
SETTINGS 帧 MUST NOT 在除 control stream 之外的任何流上发送。如果端点在不同流上收到 SETTINGS 帧,端点 MUST 向对等方发送类型为 H3_FRAME_UNEXPECTED 的 连接错误。
SETTINGS 参数不是协商的;它们描述发送端点的特征,接收端点可以利用这些特征。但可以通过 SETTINGS 的使用隐含协商:每个对等方使用 SETTINGS 来通告一组支持的值。设置的定义会描述每个对等方如何组合这两组值以得出将被使用的选项。SETTINGS 不提供识别该选项何时生效的机制。
每个对等方可以为同一参数通告不同的值。例如,客户端可能愿意消费非常大的响应字段节,而服务器对请求大小更为谨慎。
同一设置标识符 MUST NOT 在 SETTINGS 帧中出现多次。接收方 MAY 将重复设置标识符的存在视为类型为 H3_SETTINGS_ERROR 的 连接错误。
SETTINGS 帧的负载由零个或多个参数组成。每个参数包含一个设置标识符和一个值,两者均以 QUIC 可变长度整数编码。
Setting {
Identifier (i),
Value (i),
}
SETTINGS Frame {
Type (i) = 0x04,
Length (i),
Setting (..) ...,
}
图 7: SETTINGS 帧
实现 MUST 忽略任何其不理解标识符的参数。
7.2.4.1. 定义的 SETTINGS 参数
HTTP/3 中定义了下列设置:
- SETTINGS_MAX_FIELD_SECTION_SIZE (0x06):
-
默认值为无限。使用请参见 第 4.2.2 节。
格式为 0x1f * N + 0x21(N 为非负整数)的设置标识符被保留以行使对未知标识符应被忽略的要求。这类设置没有定义的含义。端点 SHOULD 在其 SETTINGS 帧中至少包含一个此类设置。端点 MUST NOT 将此类设置在接收时视为具有任何含义。
由于该设置没有定义的含义,设置的值可以是实现选择的任意值。
那些在 [HTTP/2] 中定义但在 HTTP/3 中没有对应设置的设置标识符也已被保留(见 第 11.2.2 节)。这些被保留的设置 MUST NOT 被发送,且其被接收时 MUST 被视为类型为 H3_SETTINGS_ERROR 的 连接错误。
其他设置可以通过 HTTP/3 扩展定义;有关详细信息,参见 第 9 节。
7.2.4.2. 初始化
HTTP 实现 MUST NOT 发送基于其当前对对等方设置理解而无效的帧或请求。
所有设置从初始值开始。每个端点 SHOULD 在对等方的 SETTINGS 帧到达之前使用这些初始值发送消息,因为携带设置的数据包可能丢失或延迟。当 SETTINGS 帧到达时,任何设置被更改为其新值。
这消除了在发送消息之前等待 SETTINGS 帧的需要。端点 MUST NOT 在发送 SETTINGS 帧之前要求先接收对等方的任何数据;设置 MUST 在传输准备好发送数据时尽快发送。
对于服务器,客户端设置的初始值为默认值。
对于使用 1-RTT QUIC 连接的客户端,服务器设置的初始值为默认值。即使服务器立即发送 SETTINGS,1-RTT 密钥也总会在处理包含 SETTINGS 的数据包之前可用。客户端 SHOULD NOT 无限制地等待 SETTINGS 到达再发送请求,但它们 SHOULD 按顺序处理接收到的数据报以增加在发送第一个请求之前处理 SETTINGS 的可能性。
在使用 0-RTT QUIC 连接时,服务器设置的初始值为上次会话中使用的值。客户端 SHOULD 存储服务器在提供恢复信息的 HTTP/3 连接中提供的设置,但在某些情况下(例如会话票据在 SETTINGS 帧之前收到)它们 MAY 选择不存储设置。客户端 MUST 在尝试 0-RTT 时遵守存储的设置——如果未存储值则遵守默认值。一旦服务器提供了新的设置,客户端 MUST 遵从这些值。
服务器可以记住它通告的设置或在票据中存储经完整性保护的值副本,并在接受 0-RTT 数据时恢复这些信息。服务器在决定是否接受 0-RTT 数据时使用 HTTP/3 设置值。如果服务器无法确定客户端记住的设置与其当前设置兼容,则它 MUST NOT 接受 0-RTT 数据。若客户端遵守这些记住的设置不会违反服务器当前设置,则这些记住的设置被视为兼容。
服务器 MAY 接受 0-RTT 并随后在其 SETTINGS 帧中提供不同的设置。如果服务器接受了 0-RTT 而后发送了会导致客户端使用其 0-RTT 数据违反设置的设置,则此情形 MUST 被视为类型为 H3_SETTINGS_ERROR 的 连接错误。如果服务器接受 0-RTT 但随后发送的 SETTINGS 帧遗漏了客户端已理解并且先前被指定为非默认值的设置(除保留标识符外),则此情形 MUST 被视为类型为 H3_SETTINGS_ERROR 的 连接错误。
7.2.5. PUSH_PROMISE
PUSH_PROMISE 帧(type=0x05)用于在 request stream 上从服务器向客户端传送被承诺的请求头节。
PUSH_PROMISE Frame {
Type (i) = 0x05,
Length (i),
Push ID (i),
Encoded Field Section (..),
}
图 8: PUSH_PROMISE 帧
其负载由下列部分组成:
- Push ID:
- Encoded Field Section:
-
用于被承诺响应的 QPACK 编码请求头字段。详见 [QPACK]。
服务器 MUST NOT 在没有客户端在 MAX_PUSH_ID 帧中提供的最大值范围内使用 push ID(见第 7.2.7 节)。客户端 MUST 将收到的包含大于其通告的 push ID 的 PUSH_PROMISE 帧视为类型为 H3_ID_ERROR 的 连接错误。
服务器 MAY 在多个 PUSH_PROMISE 帧中使用相同的 push ID。如果如此,解压后的请求头集 MUST 包含相同字段且顺序相同,且每个字段的名称和值 MUST 完全匹配。客户端 SHOULD 比较多次承诺同一资源的请求头节。如果客户端接收到已经被承诺的 push ID 并发现不匹配,则 MUST 使用类型为 H3_GENERAL_PROTOCOL_ERROR 的 连接错误 响应。如果解压后的字段节完全匹配,客户端 SHOULD 将被推送的内容与在其上收到 PUSH_PROMISE 帧的每个流关联。
允许对同一 push ID 的重复引用主要是为了减少并发请求导致的重复。服务器 SHOULD 避免长时间重用同一 push ID。客户端可能会消费服务器推送的响应并不会长期保留以供重用。看到使用其已消费并丢弃的 push ID 的 PUSH_PROMISE 帧的客户端将被迫忽略该承诺。
如果在 control stream 上接收到 PUSH_PROMISE 帧,客户端 MUST 使用类型为 H3_FRAME_UNEXPECTED 的 连接错误 响应。
客户端 MUST NOT 发送 PUSH_PROMISE 帧。服务器 MUST 将接收到 PUSH_PROMISE 帧视为类型为 H3_FRAME_UNEXPECTED 的 连接错误。
有关整体服务器推送机制的描述,请参见 第 4.6 节。
7.2.6. GOAWAY
GOAWAY 帧(type=0x07)用于由任一端点启动 HTTP/3 连接的优雅关闭。GOAWAY 允许端点停止接受新请求或推送,同时仍完成对先前接收的请求和推送的处理。这支持诸如服务器维护之类的管理操作。仅发送 GOAWAY 本身并不会关闭连接。
GOAWAY Frame {
Type (i) = 0x07,
Length (i),
Stream ID/Push ID (i),
}
图 9: GOAWAY 帧
GOAWAY 帧总是在 control stream 上发送。在服务器到客户端方向,它携带一个以可变长度整数编码的客户端发起的双向 QUIC 流 ID。客户端 MUST 将包含任何其他类型流 ID 的 GOAWAY 帧视为类型为 H3_ID_ERROR 的 连接错误。
在客户端到服务器方向,GOAWAY 帧携带一个以可变长度整数编码的 push ID。
GOAWAY 帧适用于整个连接,而不是特定流。客户端 MUST 将在非 control stream 上的 GOAWAY 帧视为类型为 H3_FRAME_UNEXPECTED 的 连接错误。
关于 GOAWAY 帧的更多信息,请参见 第 5.2 节。
7.2.7. MAX_PUSH_ID
MAX_PUSH_ID 帧(type=0x0d)由客户端用于控制服务器可以发起的服务器推送数量。它设置服务器可在 PUSH_PROMISE 和 CANCEL_PUSH 帧中使用的最大 push ID。因此,这也限制了服务器在 QUIC 传输维持的限制之外可以启动的 push streams 数量。
MAX_PUSH_ID 帧总是在 control stream 上发送。在任何其他流上接收到 MAX_PUSH_ID 帧 MUST 被视为类型为 H3_FRAME_UNEXPECTED 的 连接错误。
服务器 MUST NOT 发送 MAX_PUSH_ID 帧。客户端 MUST 将接收到 MAX_PUSH_ID 帧视为类型为 H3_FRAME_UNEXPECTED 的 连接错误。
创建 HTTP/3 连接时,最大 push ID 未设置,这意味着服务器在接收到 MAX_PUSH_ID 帧之前无法进行推送。希望管理承诺服务器推送数量的客户端可以在服务器完成或取消服务器推送时通过发送 MAX_PUSH_ID 帧来增加服务器可使用的最大 push ID。
MAX_PUSH_ID Frame {
Type (i) = 0x0d,
Length (i),
Push ID (i),
}
图 10: MAX_PUSH_ID 帧
MAX_PUSH_ID 帧携带一个可变长度整数,该整数标识服务器可使用的最大 push ID;见 第 4.6 节。MAX_PUSH_ID 帧不能减少最大 push ID;接收到包含比先前接收值更小值的 MAX_PUSH_ID 帧 MUST 被视为类型为 H3_ID_ERROR 的 连接错误。
7.2.8. 保留的帧类型
格式为 0x1f * N + 0x21(N 为非负整数)的帧类型被保留以行使对未知类型应被忽略的要求(参见 第 9 节)。这些帧没有语义,且它们 MAY 在允许发送帧的任何流上发送。这使其可用于应用层填充。端点 MUST NOT 将这些帧在接收时视为具有任何含义。
帧的有效载荷和长度由实现以任意方式选择。
那些在 HTTP/2 中使用但在 HTTP/3 中没有对应帧的帧类型也被保留(见 第 11.2.1 节)。这些帧类型 MUST NOT 被发送,其接收 MUST 被视为类型为 H3_FRAME_UNEXPECTED 的 连接错误。
8. 错误处理
当某个流无法成功完成时,QUIC 允许应用程序突然终止(重置)该流并传达原因;参见 Section 2.4 的 [QUIC-TRANSPORT]。这称为“流错误”。HTTP/3 实现可以决定关闭 QUIC 流并传达错误类型。错误代码的线编码在 第 8.1 节 中定义。流错误不同于表示错误情况的 HTTP 状态码。流错误表示发送方未传输或消费完整的请求或响应,而 HTTP 状态码表示已成功接收请求后的处理结果。
如果需要终止整个连接,QUIC 同样提供了传达原因的机制;参见 Section 5.3 的 [QUIC-TRANSPORT]。这称为“连接错误”。与流错误类似,HTTP/3 实现可以终止 QUIC 连接并使用来自 第 8.1 节 的错误代码来传达原因。
尽管关闭流和连接的原因被称为“错误”,但这些操作不一定表示连接或任一实现存在问题。例如,当所请求的资源不再需要时,可以重置流。
在某些情况下,端点 MAY 选择将流错误视为连接错误,在单个流出现问题时关闭整个连接。实现需要在做出此选择之前考虑对未完成请求的影响。
因为可以在无需协商的情况下定义新的错误代码(参见 第 9 节),在意外上下文中使用某个错误代码或收到未知错误代码时,MUST 将其视为等同于 H3_NO_ERROR。不过,无论错误代码为何,关闭流可能仍会产生其它影响;例如,参见 第 4.1 节。
8.1. HTTP/3 错误代码
下列错误代码被定义用于在突然终止流、终止读取流或立即关闭 HTTP/3 连接时使用。
- H3_NO_ERROR (0x0100):
-
无错误。当需要关闭连接或流但无需报告错误时使用。
- H3_GENERAL_PROTOCOL_ERROR (0x0101):
-
对等方以不符合更具体错误代码的方式违反了协议要求,或端点拒绝使用更具体的错误代码。
- H3_INTERNAL_ERROR (0x0102):
-
HTTP 堆栈中发生了内部错误。
- H3_STREAM_CREATION_ERROR (0x0103):
-
端点检测到其对端创建了一个其不会接受的流。
- H3_CLOSED_CRITICAL_STREAM (0x0104):
-
HTTP/3 连接所需的某个流被关闭或重置。
- H3_FRAME_UNEXPECTED (0x0105):
-
在当前状态或当前流上接收到了不被允许的帧。
- H3_FRAME_ERROR (0x0106):
-
接收到不满足布局要求或大小无效的帧。
- H3_EXCESSIVE_LOAD (0x0107):
-
端点检测到其对端表现出可能引起过度负载的行为。
- H3_ID_ERROR (0x0108):
-
流 ID 或 push ID 被错误使用,例如超出限制、减少限制或被重用。
- H3_SETTINGS_ERROR (0x0109):
-
端点检测到 SETTINGS 帧的负载有错误。
- H3_MISSING_SETTINGS (0x010a):
-
在 control stream 的开始处未收到 SETTINGS 帧。
- H3_REQUEST_REJECTED (0x010b):
-
服务器在未执行任何应用处理的情况下拒绝了请求。
- H3_REQUEST_CANCELLED (0x010c):
-
请求或其响应(包括被推送的响应)被取消。
- H3_REQUEST_INCOMPLETE (0x010d):
-
客户端的流在未包含完整请求的情况下终止。
- H3_MESSAGE_ERROR (0x010e):
-
HTTP 消息为 格式错误,无法被处理。
- H3_CONNECT_ERROR (0x010f):
-
对 CONNECT 请求建立的 TCP 连接被重置或异常关闭。
- H3_VERSION_FALLBACK (0x0110):
-
请求的操作不能通过 HTTP/3 提供服务。对端应重试使用 HTTP/1.1。
格式为 0x1f * N + 0x21(N 为非负整数)的错误代码空间被保留,用以检验对未知错误代码应被视为等同于 H3_NO_ERROR 的要求(参见 第 9 节)。实现 SHOULD 在本应发送 H3_NO_ERROR 的情况下,以一定概率从该空间中选择一个错误代码。
9. HTTP/3 的扩展
HTTP/3 允许对协议进行扩展。在本节所述的限制范围内,协议扩展可用于提供附加服务或改变协议的任何方面。扩展仅在单个 HTTP/3 连接的范围内生效。
这适用于本文档中定义的协议元素。这并不影响现有的扩展 HTTP 的选项,例如定义新的方法、状态码或字段。
扩展可以使用新的帧类型(第 7.2 节)、新的设置(第 7.2.4.1 节)、新的错误代码(第 8 节)或新的单向流类型(第 6.2 节)。为管理这些扩展点建立了注册表:帧类型(第 11.2.1 节)、设置(第 11.2.2 节)、错误代码(第 11.2.3 节)和流类型(第 11.2.4 节)。
实现 MUST 忽略所有可扩展协议元素中未知或不受支持的值。实现 MUST 丢弃具有未知或不受支持类型的单向流上的数据或中止读取。这意味着可以在无需事先协商的情况下安全地使用这些扩展点。不过,当已知的帧类型被要求位于特定位置(例如 SETTINGS 帧作为 control stream 的第一个帧,见 第 6.2.1 节)时,未知帧类型不能满足该要求,且 SHOULD 被视为错误。
可能改变已有协议组件语义的扩展 MUST 在使用前进行协商。例如,改变 HEADERS 帧布局的扩展在对端给出正面信号之前不得使用。协调何时使修订的布局生效可能很复杂。因此,为现有协议元素的新定义分配新的标识符通常更有效。
本文档未规定协商扩展使用的具体方法,但指出可以使用一个设置(第 7.2.4.1 节)来实现该目的。如果双方都设置了表示愿意使用扩展的值,则可使用该扩展。如果使用设置来协商扩展,则默认值 MUST 以在省略该设置时禁用扩展的方式定义。
10. 安全性考虑
HTTP/3 的安全性考虑应与使用 TLS 的 HTTP/2 相当。然而,许多来自 RFC 9113 第 10 节 的考虑同样适用于 [QUIC-TRANSPORT],并在该文档中讨论。
10.2. 跨协议攻击
在 TLS 和 QUIC 握手中使用 ALPN 在处理应用层字节之前确定目标应用协议。这确保了端点有强有力的保证,表明对端正在使用相同的协议。
这并不能保证免受所有跨协议攻击。RFC 9000 第 21.5 节 描述了明文 QUIC 数据包如何被用来对不使用认证传输的端点进行请求伪造的若干方式。
10.3. 中介封装攻击
HTTP/3 的字段编码允许表达在 HTTP 语法中并非有效字段名的名称(参见 RFC 9110 第 5.1 节)。包含无效字段名的请求或响应 MUST 被视为 格式错误。因此,中介不能将包含无效字段名的 HTTP/3 请求或响应转换为 HTTP/1.1 消息。
同样,HTTP/3 可以传输并非总是有效的字段值。尽管大多数可编码的值不会改变字段解析,但回车(ASCII 0x0d)、换行(ASCII 0x0a)和空字符(ASCII 0x00)在被逐字翻译时可能会被攻击者利用。如果请求或响应包含字段值中不允许的字符,该请求或响应 MUST 被视为 格式错误。有效字符由 RFC 9110 第 5.5 节 中的 "field-content" ABNF 规则定义。
10.4. 被推送响应的可缓存性
被推送的响应没有来自客户端的显式请求;请求由服务器在 PUSH_PROMISE 帧中提供。
是否缓存被推送的响应可以依据原点服务器在 Cache-Control 头字段中提供的指导来决定。然而,如果单个服务器托管多个租户,这可能引发问题。例如,服务器可能为多个用户各自提供其 URI 空间的一小部分。
当多个租户共享同一服务器空间时,该服务器 MUST 确保租户不能推送其无权代表的资源的表示。未能强制执行此类限制将允许某一租户提供一个表示并使其被缓存,从而覆盖权威租户提供的实际表示。
客户端必须拒绝对其原点服务器不具权威性的被推送响应;参见 第 4.6 节。
10.5. 拒绝服务(DoS)考虑
HTTP/3 连接所需的资源承诺可能比 HTTP/1.1 或 HTTP/2 更大。字段压缩和流量控制的使用依赖于为存储更多状态而作出的资源承诺。针对这些特性的设置确保了对这些特性所需内存的严格界定。
PUSH_PROMISE 帧的数量也以类似方式受限。接受服务器推送的客户端 SHOULD 限制其一次发出的 push ID 数量。
处理能力不能像状态容量那样有效地受到保护。
发送对等方被要求忽略的未定义协议元素的能力可能被滥用以使对等方消耗额外处理时间。这可能通过设置多个未定义的 SETTINGS 参数、未知帧类型或未知流类型来实现。但需注意,有些用途是完全合法的,例如可选理解的扩展和用于增加对流量分析抗性的填充。
字段节的压缩也提供了浪费处理资源的机会;有关潜在滥用的更多细节,请参见 RFC 9204 第 7 节(QPACK)。
所有这些特性——即服务器推送、未知协议元素、字段压缩——都有合法用途。只有在不必要或过度使用时,这些特性才成为负担。
不监控此类行为的端点会暴露于拒绝服务攻击的风险之中。实现 SHOULD 跟踪这些特性的使用并对其使用设置限制。端点 MAY 将可疑活动视为类型为 H3_EXCESSIVE_LOAD 的 连接错误,但误报会导致中断合法连接和请求。
10.5.1. 字段节大小的限制
大的字段节(参见 第 4.1 节)可能导致实现提交大量状态。用于路由的关键首部字段可能出现在首部节的末尾,这会阻止将首部节流式传输到最终目的地。出于该排序以及其它原因(比如确保缓存正确性),端点很可能需要缓冲整个首部节。由于字段节大小没有硬上限,有些端点可能被迫为首部字段占用大量可用内存。
端点可以使用 SETTINGS_MAX_FIELD_SECTION_SIZE(见 第 4.2.2 节)设置来告知对等方可能适用的字段节大小限制。该设置仅为建议性的,因此端点 MAY 选择发送超过该限制的字段节,并承担该请求或响应被视为 格式错误 的风险。该设置特定于某个 HTTP/3 连接,因此任何请求或响应都可能遇到具有更低未知限制的跃点。中介可以尝试通过传递不同对等方提出的值来避免该问题,但它们没有义务这样做。
接收比其愿意处理的更大字段节的服务器可以发送 HTTP 431(Request Header Fields Too Large)状态码(参见 [RFC6585])。客户端可以丢弃无法处理的响应。
10.5.2. CONNECT 的问题
CONNECT 方法可能对代理造成不成比例的负载,因为与创建和维护 TCP 连接相比,创建流的开销相对较小。因此,支持 CONNECT 的代理在接受的并发请求数量上可能会更为保守。
代理也可能在携带 CONNECT 请求的流关闭之后为 TCP 连接保留某些资源,因为外发的 TCP 连接可能处于 TIME_WAIT 状态。为此,代理可能会在 TCP 连接终止后一段时间延迟增加 QUIC 流限制。
10.6. 压缩的使用
当秘密数据与攻击者可控的数据在相同上下文中被压缩时,压缩可能使攻击者恢复秘密数据。HTTP/3 支持字段压缩(参见 第 4.2 节);下列关注点也适用于 HTTP 压缩内容编码的使用;参见 RFC 9110 第 8.4.1 节。
针对压缩的可验证攻击利用了 Web 的特性(例如 [BREACH])。攻击者诱导包含不同明文的多次请求,通过观察每次产生的密文长度,当对秘密的猜测正确时会显现出更短的长度。
在安全通道上通信的实现 MUST NOT 压缩同时包含机密数据和攻击者可控数据的内容,除非对每个数据来源使用独立的压缩上下文。如果无法可靠确定数据来源,则 MUST NOT 使用压缩。
关于字段节压缩的更多考虑见 [QPACK]。
10.7. 填充与流量分析
填充可用于模糊帧内容的精确大小,并用于缓解 HTTP 内的特定攻击,例如当压缩内容同时包含攻击者控制的明文和秘密数据时的攻击(例如 [BREACH])。
当 HTTP/2 使用 PADDING 帧和其它帧中的 Padding 字段来增强对流量分析的抗性时,HTTP/3 可以依赖传输层填充或采用第 7.2.8 节 和 6.2.3 节 中讨论的保留帧和流类型。这些填充方法在填充的粒度、填充与被保护信息的排列、在丢包情况下是否应用填充以及实现如何控制填充等方面产生不同的结果。
保留流类型可用于在连接空闲时制造看似存在的流量。由于 HTTP 流量通常呈突发性出现,表面流量可用于模糊这种突发的时序或持续时间,甚至看起来像持续发送数据的恒定流。然而,由于此类流仍受接收方的流量控制,未能及时清除这些流并提供额外的流量控制额度会限制发送方发送真实流量的能力。
为缓解依赖压缩的攻击,禁用或限制压缩可能比填充更合适作为对策。
使用填充可能并不比表面上看起来的更能提供保护。冗余填充甚至可能适得其反。充其量,填充只是通过增加攻击者必须观察的帧数量来使攻击者更难推断长度信息。错误实现的填充方案容易被攻破。特别是,可预测分布的随机填充提供的保护很少;同样,将有效载荷填充到固定尺寸会在有效载荷跨越固定边界时泄露信息,如果攻击者能控制明文,则可能利用这一点。
10.8. 帧解析
若干协议元素包含嵌套长度元素,通常表现为包含可变长度整数的显式长度的帧。这可能对粗心的实现者构成安全风险。实现 MUST 确保帧的长度与其所包含字段的长度精确匹配。
10.9. 早期数据
在 HTTP/3 中使用 0-RTT 会带来重放攻击的风险。在使用 HTTP/3 的 0-RTT 时,必须采用 [HTTP-REPLAY] 中的防重放缓解措施。将 [HTTP-REPLAY] 应用于 HTTP/3 时,对 TLS 层的引用指代在 QUIC 中执行的握手,而对应用数据的所有引用指代流的内容。
10.10. 迁移
某些 HTTP 实现使用客户端地址进行日志记录或访问控制。由于 QUIC 客户端的地址可能在连接期间发生变化(未来版本可能支持同时使用多个地址),此类实现需要在相关时刻主动检索客户端的当前地址或地址列表,或明确接受原始地址可能会改变的事实。
10.11. 隐私考虑
HTTP/3 的若干特性为观察者提供了随时间关联单一客户端或服务器动作的机会。这些包括设置的值、对刺激的响应时序以及对任何由设置控制的特性的处理方式。
只要这些在行为上产生可观察的差异,它们就可能被用来对特定客户端进行指纹识别。
HTTP/3 倾向于对单一 QUIC 连接的重用允许对用户在站点上的活动进行关联。为不同原点重用连接允许跨这些原点关联活动。
QUIC 的若干特性会立即触发响应,端点可用于测量与其对端的延迟;在某些场景下,这可能具有隐私影响。
11. IANA 注意事项
本文档在 ALPN 协议标识符注册表中登记了一个新的 ALPN 协议 ID(第 11.1 节),并创建了若干用于管理 HTTP/3 中码点分配的新注册表。
11.1. 注册 HTTP/3 标识字符串
本文档在由 [RFC7301] 建立的“TLS 应用层协议协商 (ALPN) 协议 ID”注册表中创建了一个用于标识 HTTP/3 的新注册。
字符串 "h3" 用于标识 HTTP/3:
- Protocol:
-
HTTP/3
- Identification Sequence:
-
0x68 0x33 ("h3")
- Specification:
-
本文件
11.2. 新注册表
本文档中创建的新注册表根据 RFC 9000 第 22.1 节 中记录的 QUIC 注册策略运行。这些注册表均包含 RFC 9000 第 22.1.1 节 所列的通用字段集合。这些注册表汇总在“Hypertext Transfer Protocol version 3 (HTTP/3)”标题下。
这些注册表中的初始分配均被赋予永久状态,并列出 IETF 作为变更控制者以及 HTTP 工作组(ietf-http-wg@w3.org)作为联系人。
11.2.1. 帧类型
本文档为 HTTP/3 帧类型码建立了一个注册表。“HTTP/3 帧类型”注册表管理一个 62 位的空间。该注册表遵循 QUIC 的注册策略;参见 第 11.2 节。此注册表中的永久注册使用 Specification Required 策略分配(参见 [RFC8126]),但对于 0x00 到 0x3f(含)之间的值,则按 RFC 8126 第 4.9 节 和 第 4.10 节 中定义的 Standards Action 或 IESG 批准方式分配。
虽然此注册表与在 [HTTP/2] 中定义的 “HTTP/2 Frame Type” 注册表是分离的,但在重叠的编码空间中,最好使分配相互对应。如果某个注册条目仅存在于一个注册表中,应尽一切努力避免将相应值分配给不相关的操作。专家审查者 MAY 拒绝会与对应注册表中相同值冲突的不相关注册。
除了在 第 11.2 节 中描述的通用字段外,该注册表中的永久注册 MUST 包含下列字段:
- Frame Type:
-
帧类型的名称或标签。
帧类型的规范 MUST 包含对帧布局及其语义的描述,包括任何有条件存在的帧部分。
表 2 中的条目由本文档注册。
| 帧类型 | 值 | 规范 |
|---|---|---|
| DATA | 0x00 | 第 7.2.1 节 |
| HEADERS | 0x01 | 第 7.2.2 节 |
| Reserved | 0x02 | 本文件 |
| CANCEL_PUSH | 0x03 | 第 7.2.3 节 |
| SETTINGS | 0x04 | 第 7.2.4 节 |
| PUSH_PROMISE | 0x05 | 第 7.2.5 节 |
| Reserved | 0x06 | 本文件 |
| GOAWAY | 0x07 | 第 7.2.6 节 |
| Reserved | 0x08 | 本文件 |
| Reserved | 0x09 | 本文件 |
| MAX_PUSH_ID | 0x0d | 第 7.2.7 节 |
格式为 0x1f * N + 0x21(N 为非负整数)的每个码值(即 0x21、0x40、……直到 0x3ffffffffffffffe)MUST NOT 由 IANA 分配,且 MUST NOT 出现在分配值列表中。
11.2.2. 设置参数
本文档为 HTTP/3 设置建立了一个注册表。“HTTP/3 设置”注册表管理一个 62 位的空间。该注册表遵循 QUIC 的注册策略;参见 第 11.2 节。此注册表中的永久注册使用 Specification Required 策略分配(参见 [RFC8126]),但对于 0x00 到 0x3f(含)之间的值,则按 RFC 8126 第 4.9 节 和 第 4.10 节 中定义的 Standards Action 或 IESG 批准方式分配。
虽然该注册表与在 [HTTP/2] 中定义的“HTTP/2 Settings”注册表是分离的,但最好使分配相互对应。如果某个条目仅存在于一个注册表中,应尽一切努力避免将相应值分配给不相关的操作。专家审查者 MAY 拒绝会与对应注册表中相同值冲突的不相关注册。
除了在 第 11.2 节 中描述的通用字段外,该注册表中的永久注册 MUST 包含下列字段:
- Setting Name:
-
设置的符号名称。指定设置名称为可选。
- Default:
-
除非另有指示,该设置的默认值。默认值 SHOULD 取最保守(限制性最大)的可能值。
表 3 中的条目由本文档注册。
| 设置名称 | 值 | 规范 | 默认值 |
|---|---|---|---|
| Reserved | 0x00 | 本文件 | N/A |
| Reserved | 0x02 | 本文件 | N/A |
| Reserved | 0x03 | 本文件 | N/A |
| Reserved | 0x04 | 本文件 | N/A |
| Reserved | 0x05 | 本文件 | N/A |
| MAX_FIELD_SECTION_SIZE | 0x06 | 第 7.2.4.1 节 | 无限制 |
出于格式考虑,设置名称可以通过移除 'SETTINGS_' 前缀来缩写。
格式为 0x1f * N + 0x21(N 为非负整数)的每个码值(即 0x21、0x40、……直到 0x3ffffffffffffffe)MUST NOT 由 IANA 分配,且 MUST NOT 出现在分配值列表中。
11.2.3. 错误代码
本文档为 HTTP/3 错误代码建立了一个注册表。“HTTP/3 错误代码”注册表管理一个 62 位的空间。该注册表遵循 QUIC 的注册策略;参见 第 11.2 节。此注册表中的永久注册使用 Specification Required 策略分配(参见 [RFC8126]),但对于 0x00 到 0x3f(含)之间的值,则按 RFC 8126 第 4.9 节 和 第 4.10 节 中定义的 Standards Action 或 IESG 批准方式分配。
错误代码的注册要求包含该错误代码的描述。专家审查者应审查新注册以查找与现有错误代码的可能重复。鼓励使用现有注册,但不强制。建议避免使用在“HTTP/2 Error Code”注册表中已注册的值,专家审查者 MAY 拒绝此类注册。
除了在 第 11.2 节 中描述的通用字段外,该注册表包含两个附加字段。此注册表中的永久注册 MUST 包含下列字段:
- Name:
-
错误代码的名称。
- Description:
-
错误代码语义的简要描述。
表 4 中的条目由本文档注册。为避免与 HTTP/2 错误代码冲突,这些错误代码从采用 Specification Required 策略的范围中选取。
| 名称 | 值 | 描述 | 规范 |
|---|---|---|---|
| H3_NO_ERROR | 0x0100 | 无错误 | 第 8.1 节 |
| H3_GENERAL_PROTOCOL_ERROR | 0x0101 | 一般协议错误 | 第 8.1 节 |
| H3_INTERNAL_ERROR | 0x0102 | 内部错误 | 第 8.1 节 |
| H3_STREAM_CREATION_ERROR | 0x0103 | 流创建错误 | 第 8.1 节 |
| H3_CLOSED_CRITICAL_STREAM | 0x0104 | 关键流已关闭 | 第 8.1 节 |
| H3_FRAME_UNEXPECTED | 0x0105 | 当前状态下不允许的帧 | 第 8.1 节 |
| H3_FRAME_ERROR | 0x0106 | 帧违反布局或大小规则 | 第 8.1 节 |
| H3_EXCESSIVE_LOAD | 0x0107 | 对端产生过度负载 | 第 8.1 节 |
| H3_ID_ERROR | 0x0108 | 标识符被错误使用 | 第 8.1 节 |
| H3_SETTINGS_ERROR | 0x0109 | SETTINGS 帧包含无效值 | 第 8.1 节 |
| H3_MISSING_SETTINGS | 0x010a | 未接收到 SETTINGS 帧 | 第 8.1 节 |
| H3_REQUEST_REJECTED | 0x010b | 请求未处理 | 第 8.1 节 |
| H3_REQUEST_CANCELLED | 0x010c | 数据不再需要 | 第 8.1 节 |
| H3_REQUEST_INCOMPLETE | 0x010d | 流提前终止 | 第 8.1 节 |
| H3_MESSAGE_ERROR | 0x010e | 消息格式错误 | 第 8.1 节 |
| H3_CONNECT_ERROR | 0x010f | CONNECT 请求上的 TCP 重置或错误 | 第 8.1 节 |
| H3_VERSION_FALLBACK | 0x0110 | 请改为通过 HTTP/1.1 重试 | 第 8.1 节 |
格式为 0x1f * N + 0x21(N 为非负整数)的每个码值(即 0x21、0x40、……直到 0x3ffffffffffffffe)MUST NOT 由 IANA 分配,且 MUST NOT 出现在分配值列表中。
11.2.4. 流类型
本文档为 HTTP/3 单向流类型建立了一个注册表。“HTTP/3 Stream Types” 注册表管理一个 62 位的空间。该注册表遵循 QUIC 的注册策略;参见 第 11.2 节。此注册表中的永久注册使用 Specification Required 策略分配(参见 [RFC8126]),但对于 0x00 到 0x3f(含)之间的值,则按 RFC 8126 第 4.9 节 和 第 4.10 节 中定义的 Standards Action 或 IESG 批准方式分配。
除了在 第 11.2 节 中描述的通用字段外,该注册表中的永久注册 MUST 包含下列字段:
- Stream Type:
-
流类型的名称或标签。
- Sender:
-
哪个端点可在 HTTP/3 连接上启动此类型的流。取值为 "Client"、"Server" 或 "Both"。
永久注册的规范 MUST 包含对流类型的描述,包括流内容的布局和语义。
表 5 中的条目由本文档注册。
格式为 0x1f * N + 0x21(N 为非负整数)的每个码值(即 0x21、0x40、……直到 0x3ffffffffffffffe)MUST NOT 由 IANA 分配,且 MUST NOT 出现在分配值列表中。
12. 参考资料
12.1. 规范性引用
- [ALTSVC]
- Nottingham, M., McManus, P., and J. Reschke, “HTTP Alternative Services”, RFC 7838, DOI 10.17487/RFC7838, 2016年4月, <https://www.rfc-editor.org/info/rfc7838>.
- [COOKIES]
- Barth, A., “HTTP State Management Mechanism”, RFC 6265, DOI 10.17487/RFC6265, 2011年4月, <https://www.rfc-editor.org/info/rfc6265>.
- [HTTP-CACHING]
- Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., “HTTP Caching”, STD 98, RFC 9111, DOI 10.17487/RFC9111, 2022年6月, <https://www.rfc-editor.org/info/rfc9111>.
- [HTTP-REPLAY]
- Thomson, M., Nottingham, M., and W. Tarreau, “Using Early Data in HTTP”, RFC 8470, DOI 10.17487/RFC8470, 2018年9月, <https://www.rfc-editor.org/info/rfc8470>.
- [HTTP]
- Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., “HTTP Semantics”, STD 97, RFC 9110, DOI 10.17487/RFC9110, 2022年6月, <https://www.rfc-editor.org/info/rfc9110>.
- [QPACK]
- Krasic, C., Bishop, M., and A. Frindell, Ed., “QPACK: Field Compression for HTTP/3”, RFC 9204, DOI 10.17487/RFC9204, 2022年6月, <https://www.rfc-editor.org/info/rfc9204>.
- [QUIC-TRANSPORT]
- Iyengar, J., Ed. and M. Thomson, Ed., “QUIC: A UDP-Based Multiplexed and Secure Transport”, RFC 9000, DOI 10.17487/RFC9000, 2021年5月, <https://www.rfc-editor.org/info/rfc9000>.
- [RFC0793]
- Postel, J., “Transmission Control Protocol”, STD 7, RFC 793, DOI 10.17487/RFC0793, 1981年9月, <https://www.rfc-editor.org/info/rfc793>.
- [RFC2119]
- Bradner, S., “Key words for use in RFCs to Indicate Requirement Levels”, BCP 14, RFC 2119, DOI 10.17487/RFC2119, 1997年3月, <https://www.rfc-editor.org/info/rfc2119>.
- [RFC6066]
- Eastlake 3rd, D., “Transport Layer Security (TLS) Extensions: Extension Definitions”, RFC 6066, DOI 10.17487/RFC6066, 2011年1月, <https://www.rfc-editor.org/info/rfc6066>.
- [RFC7301]
- Friedl, S., Popov, A., Langley, A., and E. Stephan, “Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension”, RFC 7301, DOI 10.17487/RFC7301, 2014年7月, <https://www.rfc-editor.org/info/rfc7301>.
- [RFC8126]
- Cotton, M., Leiba, B., and T. Narten, “Guidelines for Writing an IANA Considerations Section in RFCs”, BCP 26, RFC 8126, DOI 10.17487/RFC8126, 2017年6月, <https://www.rfc-editor.org/info/rfc8126>.
- [RFC8174]
- Leiba, B., “Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words”, BCP 14, RFC 8174, DOI 10.17487/RFC8174, 2017年5月, <https://www.rfc-editor.org/info/rfc8174>.
- [URI]
- Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax”, STD 66, RFC 3986, DOI 10.17487/RFC3986, 2005年1月, <https://www.rfc-editor.org/info/rfc3986>.
12.2. 信息性引用
- [BREACH]
- Gluck, Y., Harris, N., and A. Prado, “BREACH: Reviving the CRIME Attack”, 2013年7月, <http://breachattack.com/resources/BREACH%20-%20SSL,%20gone%20in%2030%20seconds.pdf>.
- [DNS-TERMS]
- Hoffman, P., Sullivan, A., and K. Fujiwara, “DNS Terminology”, BCP 219, RFC 8499, DOI 10.17487/RFC8499, 2019年1月, <https://www.rfc-editor.org/info/rfc8499>.
- [HPACK]
- Peon, R. and H. Ruellan, “HPACK: Header Compression for HTTP/2”, RFC 7541, DOI 10.17487/RFC7541, 2015年5月, <https://www.rfc-editor.org/info/rfc7541>.
- [HTTP/1.1]
- Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., “HTTP/1.1”, STD 99, RFC 9112, DOI 10.17487/RFC9112, 2022年6月, <https://www.rfc-editor.org/info/rfc9112>.
- [HTTP/2]
- Thomson, M., Ed. and C. Benfield, Ed., “HTTP/2”, RFC 9113, DOI 10.17487/RFC9113, 2022年6月, <https://www.rfc-editor.org/info/rfc9113>.
- [RFC6585]
- Nottingham, M. and R. Fielding, “Additional HTTP Status Codes”, RFC 6585, DOI 10.17487/RFC6585, 2012年4月, <https://www.rfc-editor.org/info/rfc6585>.
- [RFC8164]
- Nottingham, M. and M. Thomson, “Opportunistic Security for HTTP/2”, RFC 8164, DOI 10.17487/RFC8164, 2017年5月, <https://www.rfc-editor.org/info/rfc8164>.
- [TFO]
- Cheng, Y., Chu, J., Radhakrishnan, S., and A. Jain, “TCP Fast Open”, RFC 7413, DOI 10.17487/RFC7413, 2014年12月, <https://www.rfc-editor.org/info/rfc7413>.
- [TLS]
- Rescorla, E., “The Transport Layer Security (TLS) Protocol Version 1.3”, RFC 8446, DOI 10.17487/RFC8446, 2018年8月, <https://www.rfc-editor.org/info/rfc8446>.
Appendix A. 从 HTTP/2 过渡的考虑
HTTP/3 在很大程度上受 HTTP/2 的启发,并且有许多相似之处。本节描述了 HTTP/3 的设计方法,指出与 HTTP/2 的重要差异,并说明如何将 HTTP/2 扩展映射到 HTTP/3。
HTTP/3 的出发点是与 HTTP/2 的相似性是可取的,但并非强制要求。当 QUIC 与 TCP 有差异时,HTTP/3 会偏离 HTTP/2,既为了利用 QUIC 的特性(如流),也为了解决重要的缺陷(例如缺乏全局排序)。虽然 HTTP/3 在关键方面与 HTTP/2 相似,例如请求与响应与流的关系,但 HTTP/3 的设计细节与 HTTP/2 有实质性差别。
本节指出了一些重要的不同之处。
A.1. 流
HTTP/3 允许使用比 HTTP/2 更大的流数量(262-1)。与流标识符空间耗尽相关的相同考虑仍然适用,但空间要大得多,因此更可能先遇到 QUIC 的其他限制,例如连接流量控制窗口的限制。
与 HTTP/2 相比,HTTP/3 中的流并发由 QUIC 管理。QUIC 在接收了所有数据并且发送的数据被对端确认后将流视为已关闭。HTTP/2 在包含 END_STREAM 位的帧被提交到传输时将流视为已关闭。因此,对于等价的交换,HTTP/3 中的流可能保持“活动”状态更长时间。HTTP/3 服务器可能会选择允许更多的客户端发起的并发双向流以达到与 HTTP/2 相当的并发性,具体取决于预期的使用模式。
在 HTTP/2 中,只有请求和响应主体(DATA 帧的负载)受流量控制。所有 HTTP/3 帧都在 QUIC 流上发送,因此在 HTTP/3 中所有流上的所有帧都受流量控制。
由于存在其它单向流类型,HTTP/3 不再仅依赖并发单向流的数量来控制并发正在进行的推送数。相反,HTTP/3 客户端使用 MAX_PUSH_ID 帧来控制从服务器接收的推送数量。
A.2. HTTP 帧类型
许多来自 HTTP/2 的分帧概念在 QUIC 上可以省略,因为传输层已经处理了这些问题。因为帧已经位于某个流上,它们可以省略流编号。因为帧不会阻塞复用(QUIC 的复用在本层之下),可以移除对可变最大长度包的支持。因为流终止由 QUIC 处理,所以不需要 END_STREAM 标志。这允许从通用帧布局中移除 Flags 字段。
帧负载大多借鉴自 [HTTP/2]。然而,QUIC 包含许多特性(例如流量控制)也出现在 HTTP/2 中。在这些情况下,HTTP 映射不会重新实现它们。因此,若干 HTTP/2 帧类型在 HTTP/3 中不再需要。对于不再使用的 HTTP/2 定义的帧,其帧 ID 已被保留以最大化 HTTP/2 与 HTTP/3 实现之间的可移植性。但即使在两者映射都存在的帧类型,其语义也不完全相同。
许多差异源于这样一个事实:HTTP/2 在所有流之间提供绝对的帧顺序,而 QUIC 仅在每个流内提供此保证。因此,如果某个帧类型假定来自不同流的帧仍将以发送顺序接收,HTTP/3 将会破坏该假定。
下面描述了一些特性适配的示例,以及将 HTTP/2 扩展转换为 HTTP/3 时对扩展帧实现者的一般指导。
A.2.1. 优先级差异
HTTP/2 在 PRIORITY 帧中以及(可选地)在 HEADERS 帧中指定优先级分配。HTTP/3 不提供信号优先级的方式。
注意,尽管没有显式的优先级信号,但这并不意味着优先级对实现良好性能不重要。
A.2.2. 字段压缩差异
HPACK 的设计假定按序交付。编码的字段节序列必须以编码时的相同顺序到达(并在端点处解码),这确保了两端的动态状态保持同步。
由于 QUIC 不提供这种全局排序,HTTP/3 使用 HPACK 的修改版本,称为 QPACK。QPACK 使用单个单向流来对动态表的所有修改进行排序,确保更新的全序。所有包含编码字段的帧仅引用特定时间的表状态而不修改它。
[QPACK] 提供了更多细节。
A.2.3. 流量控制差异
HTTP/2 指定了流的流量控制机制。尽管所有 HTTP/2 帧都是在流上传送,但只有 DATA 帧的负载受流量控制。QUIC 为流数据提供流量控制,而本文档定义的所有 HTTP/3 帧类型都在流上发送。因此,所有帧头和负载都受流量控制。
A.2.4. 新帧类型定义的指导
HTTP/3 中的帧类型定义通常使用 QUIC 的可变长度整数编码。特别地,流 ID 使用该编码,这允许比 HTTP/2 使用的编码更大的值范围。HTTP/3 中的一些帧使用除了流 ID 之外的标识符(例如 push ID)。如果扩展帧类型的编码包含流 ID,则可能需要重新定义扩展帧类型编码。
由于通用 HTTP/3 帧中没有 Flags 字段,依赖标志存在的那些帧需要在其帧负载中为标志分配空间。
除这些问题外,HTTP/2 的帧类型扩展通常可以通过在 HTTP/3 中将 HTTP/2 中的流 0 替换为 control stream 来简单移植。HTTP/3 扩展不会假定顺序,但不会因有序而受到损害,并预计可以移植到 HTTP/2。
A.2.5. HTTP/2 与 HTTP/3 帧类型的比较
- DATA (0x00) :
-
在 HTTP/3 帧中未定义填充。参见 第 7.2.1 节。
- HEADERS (0x01):
- PRIORITY (0x02):
-
如 附录 A.2.1 所述,HTTP/3 不提供优先级信号的手段。
- RST_STREAM (0x03):
-
HTTP/3 中不存在 RST_STREAM 帧,因为 QUIC 提供了流生命周期管理。相同的码位用于 CANCEL_PUSH 帧(参见 第 7.2.3 节)。
- SETTINGS (0x04):
- PUSH_PROMISE (0x05):
- PING (0x06):
-
HTTP/3 中不存在 PING 帧,因为 QUIC 提供了等价的功能。
- GOAWAY (0x07):
- WINDOW_UPDATE (0x08):
-
WINDOW_UPDATE 帧在 HTTP/3 中不存在,因为 QUIC 提供了流量控制。
- CONTINUATION (0x09):
-
CONTINUATION 帧在 HTTP/3 中不存在;相反,允许比 HTTP/2 更大的 HEADERS/ PUSH_PROMISE 帧。
由 HTTP/2 扩展定义的帧类型如果仍适用,需要为 HTTP/3 单独注册。为简便起见,HTTP/2 中定义的帧 ID 已被保留。注意 HTTP/3 的帧类型空间大得多(62 位对比 8 位),因此许多 HTTP/3 帧类型在 HTTP/2 中没有等价的码点。参见 第 11.2.1 节。
A.3. HTTP/2 SETTINGS 参数
与 HTTP/2 的一个重要区别是设置在作为 control stream 的第一个帧中发送一次,之后不能更改。这消除了许多关于变更同步的边缘情况。
HTTP/2 通过 SETTINGS 帧指定的一些传输级选项在 HTTP/3 中被 QUIC 传输参数取代。保留在 HTTP/3 中的 HTTP 级设置与 HTTP/2 中的值相同。被取代的设置被保留,其接收被视为错误。有关保留和值的讨论,见 第 7.2.4.1 节。
下面列出每个 HTTP/2 SETTINGS 参数的映射:
- SETTINGS_HEADER_TABLE_SIZE (0x01):
-
参见 [QPACK]。
- SETTINGS_ENABLE_PUSH (0x02):
-
此项已被替换为 MAX_PUSH_ID 帧,后者提供了对服务器推送的更细粒度控制。在 HTTP/3 的 SETTINGS 帧中指定标识符为 0x02 的设置(对应于 SETTINGS_ENABLE_PUSH 参数)是错误的。
- SETTINGS_MAX_CONCURRENT_STREAMS (0x03):
-
QUIC 在其流量控制逻辑中控制最大的打开流 ID。在 HTTP/3 的 SETTINGS 帧中指定标识符为 0x03(对应于 SETTINGS_MAX_CONCURRENT_STREAMS 参数)的设置是错误的。
- SETTINGS_INITIAL_WINDOW_SIZE (0x04):
-
QUIC 要求在初始传输握手中指定流和连接的流量控制窗口大小。在 HTTP/3 的 SETTINGS 帧中指定标识符为 0x04(对应于 SETTINGS_INITIAL_WINDOW_SIZE 参数)的设置是错误的。
- SETTINGS_MAX_FRAME_SIZE (0x05):
-
此设置在 HTTP/3 中没有等价项。在 HTTP/3 的 SETTINGS 帧中指定标识符为 0x05(对应于 SETTINGS_MAX_FRAME_SIZE 参数)的设置是错误的。
- SETTINGS_MAX_HEADER_LIST_SIZE (0x06):
-
此设置标识符已重命名为 SETTINGS_MAX_FIELD_SECTION_SIZE。
在 HTTP/3 中,设置值为可变长度整数(6、14、30 或 62 位),而不是 HTTP/2 中的固定 32 位字段。这通常会产生更短的编码,但对于使用完整 32 位空间的设置可能产生更长的编码。从 HTTP/2 移植的设置可以选择重新定义其值以限制为 30 位以获得更高效的编码,或者如果需要超过 30 位可以利用 62 位空间。
需要为 HTTP/2 和 HTTP/3 分别定义设置。为简便起见,在 [HTTP/2] 中定义的设置标识符已被保留。注意 HTTP/3 的设置标识符空间明显更大(62 位对比 16 位),因此许多 HTTP/3 设置在 HTTP/2 中没有等价的码点。参见 第 11.2.2 节。
由于 QUIC 流可能无序到达,建议端点不要在等待对等方的设置到达后再对其它流做出响应。参见 第 7.2.4.2 节。
A.4. HTTP/2 错误代码
QUIC 拥有与 HTTP/2 相同的“流”和“连接”错误的概念。然而,HTTP/2 与 HTTP/3 之间的差异意味着错误代码不能直接在版本之间移植。
在 RFC 9113 第 7 节 中定义的 HTTP/2 错误代码在逻辑上与 HTTP/3 错误代码的映射如下:
- NO_ERROR (0x00):
- PROTOCOL_ERROR (0x01):
-
映射为 H3_GENERAL_PROTOCOL_ERROR,除非已定义更具体的错误代码。例如包括 H3_FRAME_UNEXPECTED、H3_MESSAGE_ERROR 和 H3_CLOSED_CRITICAL_STREAM,这些都在 第 8.1 节 中定义。
- INTERNAL_ERROR (0x02):
- FLOW_CONTROL_ERROR (0x03):
-
不适用,因为 QUIC 处理流量控制。
- SETTINGS_TIMEOUT (0x04):
-
不适用,因为没有定义对 SETTINGS 的确认。
- STREAM_CLOSED (0x05):
-
不适用,因为 QUIC 处理流管理。
- FRAME_SIZE_ERROR (0x06):
- REFUSED_STREAM (0x07):
-
H3_REQUEST_REJECTED(见 第 8.1 节)用于指示请求未被处理。其他情况不适用,因为 QUIC 处理流管理。
- CANCEL (0x08):
- COMPRESSION_ERROR (0x09):
-
多个错误代码在 [QPACK] 中定义。
- CONNECT_ERROR (0x0a):
- ENHANCE_YOUR_CALM (0x0b):
- INADEQUATE_SECURITY (0x0c):
-
不适用,因为假设 QUIC 在所有连接上提供足够的安全性。
- HTTP_1_1_REQUIRED (0x0d):
需要为 HTTP/2 和 HTTP/3 分别定义错误代码。参见 第 11.2.3 节。
A.4.1. HTTP/2 与 HTTP/3 错误之间的映射
在 HTTP/2 与 HTTP/3 之间转换的中介可能会遇到来自任一上游的错误条件。向下游传达错误的发生是有用的,但错误代码在很大程度上反映了连接本地的问题,通常不适合传播。
遇到来自上游源的错误的中介可以通过发送 HTTP 状态码(例如 502 Bad Gateway)来表示这种情况,这对一类广泛的错误是合适的。
在某些罕见情况下,将错误映射为接收方的最接近匹配错误类型并传播它是有益的。例如,中介从源接收到 HTTP/2 的 REFUSED_STREAM 类型的 流错误 时,能明确地得到请求未被处理并且可以安全重试的信号。将该错误条件传播给客户端,作为 HTTP/3 类型为 H3_REQUEST_REJECTED 的 流错误,允许客户端采取其认为最合适的操作。反方向上,中介也可能认为传递客户端通过以 H3_REQUEST_CANCELLED 终止流所示的取消请求是有益的;参见 第 4.1.1 节。
致谢
Robbie Shade 和 Mike Warres 是 draft-shade-quic-http2-mapping 草案的作者,该草案是本文档的先驱。
IETF QUIC 工作组得到了许多人的大量支持。下列人员(按姓氏字母顺序,部分列出)对本文档做出了大量贡献:
-
Bence Beky
-
Daan De Meyer
-
Martin Duke
-
Roy Fielding
-
Alan Frindell
-
Alessandro Ghedini
-
Nick Harper
-
Ryan Hamilton
-
Christian Huitema
-
Subodh Iyengar
-
Robin Marx
-
Patrick McManus
-
Luca Niccolini
-
奥 一穂 (Kazuho Oku)
-
Lucas Pardue
-
Roberto Peon
-
Julian Reschke
-
Eric Rescorla
-
Martin Seemann
-
Ben Schwartz
-
Ian Swett
-
Willy Taureau
-
Martin Thomson
-
Dmitri Tikhonov
-
Tatsuhiro Tsujikawa
Mike Bishop 的部分贡献在他受雇于 Microsoft 时得到了 Microsoft 的资助支持。
索引
- C
- CANCEL_PUSH 2, 4.6, 4.6, 7, 7.2.3, 7.2.5, 7.2.7, 11.2.1, A.2.5
- connection error 2.2, 4.1, 4.1, 4.4, 4.4, 4.6, 5.2, 6.1, 6.2, 6.2.1, 6.2.1, 6.2.1, 6.2.2, 6.2.2, 7.1, 7.1, 7.2.1, 7.2.2, 7.2.3, 7.2.3, 7.2.3, 7.2.4, 7.2.4, 7.2.4, 7.2.4.1, 7.2.4.2, 7.2.4.2, 7.2.5, 7.2.5, 7.2.5, 7.2.5, 7.2.6, 7.2.6, 7.2.7, 7.2.7, 7.2.7, 7.2.8, 8, 10.5, A.4.1
- control stream 2, 3.2, 6.2, 6.2, 6.2, 6.2.1, 7, 7.2.1, 7.2.2, 7.2.3, 7.2.3, 7.2.4, 7.2.4, 7.2.4, 7.2.5, 7.2.6, 7.2.6, 7.2.7, 8.1, 9, A.2.4, A.3
- D
- G
- H
- H3_CLOSED_CRITICAL_STREAM 6.2.1, 8.1, 11.2.3, A.4
- H3_CONNECT_ERROR 4.4, 8.1, 11.2.3, A.4
- H3_EXCESSIVE_LOAD 8.1, 10.5, 11.2.3, A.4
- H3_FRAME_ERROR 7.1, 7.1, 8.1, 11.2.3, A.4
- H3_FRAME_UNEXPECTED 4.1, 4.1, 4.4, 7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.4, 7.2.5, 7.2.5, 7.2.6, 7.2.7, 7.2.7, 7.2.8, 8.1, 11.2.3, A.4
- H3_GENERAL_PROTOCOL_ERROR 7.2.5, 8.1, 11.2.3, A.4
- H3_ID_ERROR 4.6, 5.2, 6.2.2, 7.2.3, 7.2.3, 7.2.5, 7.2.6, 7.2.7, 8.1, 11.2.3
- H3_INTERNAL_ERROR 8.1, 11.2.3, A.4
- H3_MESSAGE_ERROR 4.1.2, 8.1, 11.2.3, A.4
- H3_MISSING_SETTINGS 6.2.1, 8.1, 11.2.3
- H3_NO_ERROR 4.1, 5.2, 6.2.3, 8, 8.1, 8.1, 8.1, 11.2.3, A.4
- H3_REQUEST_CANCELLED 4.1.1, 4.1.1, 4.6, 7.2.3, 7.2.3, 8.1, 11.2.3, A.4, A.4.1
- H3_REQUEST_INCOMPLETE 4.1, 8.1, 11.2.3
- H3_REQUEST_REJECTED 4.1.1, 4.1.1, 4.1.1, 4.1.1, 8.1, 11.2.3, A.4, A.4.1
- H3_SETTINGS_ERROR 7.2.4, 7.2.4.1, 7.2.4.2, 7.2.4.2, 8.1, 11.2.3
- H3_STREAM_CREATION_ERROR 6.1, 6.2, 6.2.1, 6.2.2, 8.1, 11.2.3
- H3_VERSION_FALLBACK 8.1, 11.2.3, A.4
- HEADERS 2, 4.1, 4.1, 4.1, 4.1, 4.1, 4.1, 4.4, 4.6, 7, 7.2.2, 9, 11.2.1, A.2.1, A.2.5, A.2.5, A.2.5
- M
- P
- push ID 4.6, 5.2, 5.2, 5.2, 6.2.2, 6.2.2, 6.2.2, 7.2.3, 7.2.3, 7.2.3, 7.2.3, 7.2.3, 7.2.5, 7.2.5, 7.2.5, 7.2.5, 7.2.5, 7.2.5, 7.2.5, 7.2.5, 7.2.6, 7.2.7, 7.2.7, 7.2.7, 7.2.7, 7.2.7, 8.1, A.2.5, A.2.5
- push stream 4.1, 4.1, 4.6, 4.6, 4.6, 4.6, 4.6, 4.6, 6.2, 6.2.2, 7, 7.2.2, 7.2.3, 7.2.3, 7.2.3, 7.2.3, 7.2.3, 7.2.3, 7.2.3, 7.2.3, 7.2.3, 7.2.5, 7.2.7, A.2.5
- PUSH_PROMISE 2, 4.1, 4.1, 4.1, 4.1, 4.1, 4.6, 4.6, 4.6, 4.6, 4.6, 4.6, 4.6, 4.6, 4.6, 7, 7.2.3, 7.2.3, 7.2.5, 7.2.7, 10.4, 10.5, 11.2.1, A.2.5, A.2.5, A.2.5, A.2.5
- R
- S