HTTP/3 标准文档译文
1. Introduction
因特网上大范围地使用了 HTTP 语义,这些语义通常与 HTTP/1.1 和 HTTP/2 一起使用。
HTTP/1.1 已经被用于各种传输层和会话层,而 HTTP/2 主要是与 TCP 上的 TLS 一起使用。
HTTP/3 在一个新的传输层协议 QUIC 上支持了相同的语义。
1.1. Prior versions of HTTP
HTTP/1.1 [HTTP11] 使用以空格分隔的文本字段来传输 HTTP 消息。
虽然这些交换信息是人类可读的,但是使用空白来作为消息格式,增加了解析的复杂度,以及行为多样性的容忍度。
由于 HTTP/1.1 不包括多路复用层,所以经常使用多个并发的 TCP 连接。然而,这对拥塞控制和网络效率有负面影响,因为 TCP 不会在多个连接之间共享拥塞控制。
HTTP/2 [HTTP2] 引入了二进制分帧和多路复用层,从而在不修改传输层的前提下改善延迟。
然而,因为 HTTP/2 多路复用的并发原理对于 TCP 的丢包恢复机制是不可见的,
一个丢包或者乱序的包就会导致所有活动的事务遭受停顿,不管事务是否收到这个丢包的影响。
1.2. Delegation to QUIC
QUIC 传输层协议纳入了流多路复用和针对每条流的流控制,与 HTTP/2 基础分帧层提供的类似。
通过在 stream 层提供可靠性,在整个连接上提供拥塞控制,相对于 TCP 映射,QUIC 有能力改善 HTTP 的性能。
QUIC 同样在传输层纳入了 TLS 1.3 [TLS13],提供了和 TLS over TCP 同等的保密性和完整性,以及通过 TCP 快速打开 [TFO] 带来的建连延迟改善。
本文定义了一种基于 QUIC 传输协议的 HTTP 语义映射,主要借鉴了了 HTTP/2 的设计。
HTTP/3 依靠 QUIC 提供数据的机密性和完整性保护、身份验证、 可靠性,以及流上可靠有序传输。
将流的生命周期和流控制都委托给了 QUIC 的同时,每条流上使用了类似的二进制分帧。
一些 HTTP/2 特性归入到 QUIC 中,同时另外的特性在 QUIC 上层实现。
[QUIC-TRANSPORT] 对 QUIC 进行了阐述。[HTTP2] 对 HTTP/2 进行了阐述。
2. HTTP/3 Protocol Overview
HTTP/3 提供了通过 QUIC 传输协议来传输 HTTP 语义的功能,和类似 HTTP/2 的内部分帧层。
一旦客户端知晓某个指定终端上支持 HTTP/3 服务,它就可以打开一个 QUIC 连接。
QUIC 提供了协议协商,基于流的多路复用,以及流控制。3.1 节中描述了如何发现 HTTP/3 终端。
在每条流中,HTTP/3 通信的基本单元是帧(frame,参考 7.2 节)。出于不同目的,定义了每个帧的类型。
比如,HEADERS 和 DATA 帧是 HTTP 请求和响应的基础(参考 4.1 节)。
Frames that apply to the entire connection are conveyed on a dedicated control stream.
适用于整个连接的帧在专用的控制流上传输。
[QUIC-TRANSPORT] 的第 2 章中,描述了使用 QUIC 流的抽象概念来实现请求的多路复用。
每个请求-响应对预设了一个 QUIC 流。每个流之间都是独立的,因此一个发生阻塞、丢包的流不会影响其他流的进展。
Server push 是一种 HTTP/2 [HTTP2] 引入的交互模式,这种模式允许服务端向客户端推送一个 request-response 交换,从而来让客户端发出指示的请求。
这种方式在网络利用率和潜在延迟收益之间做了权衡。使用了一些 HTTP/3 帧来实施服务端推送,比如 PUSH_PROMISE,MAX_PUSH_ID,以及 CANCEL_PUSH。
而在 HTTP/2 中,压缩了请求和响应字段来进行传输。
因为 HPACK [HPACK] 依赖了压缩字段分段的有序传输(QUIC 不提供这样的保证),所以 HTTP/3 使用了 QPACK [QPACK] 来替换 HPACK。
QPACK 使用了隔离的单向流来修改和追踪字段表状态,而编码后的字段分段只参考表的状态而不修改它。
2.1. Document Organization
以下章节提供了 HTTP/3 连接生命周期的概述:
- 连接设立和管理(第 3 章)覆盖了如何发现 HTTP/3 终端,以及如何建立 HTTP/3 连接。
- HTTP 请求生命周期(第 4 章)描述了如何使用帧来表达 HTTP 语义。
- 连接关闭(第 5 章)描述了如何终止 HTTP/3 连接,包括文明终止和意外终止。
以下章节描述了线路上的协议和与传输的交互:
- stream 映射与用途(第 6 章)描述了 QUIC 流的使用方式。
- HTTP 分帧层(第 7 章)描述了在大多数流上使用的帧。
- 错误处理(第 8 章)描述了如何在一条流上或者整个连接上处理和表示错误条件。
本文最后的章节中提供了如下资源:
- HTTP/3 扩展(第 9 章)描述了如何在未来的文档中添加新功能。
- 附录 A 提供了 HTTP/2 与 HTTP/3 之间更具体的对比。
2.2. Conventions and Terminology
终止(abort):连接或流的突发终止,可能由错误条件引起。
客户端(client):开始一个 HTTP/3 连接的终端。客户端发送 HTTP 请求,并接受 HTTP 响应。
连接(connection):一个两端之间的传输层连接,使用了 QUIC 作为传输协议。
连接错误(connection error):影响整个 HTTP/3 连接的错误。
终端(endpoint):连接的客户端或者服务端。
帧(frame):HTTP/3 中流通信时的最小单元,包含一个头部和一个根据帧类型组织的变长字节序列。
本文和 [QUIC-TRANSPORT] 中都存在称为“帧”的协议元素。
当引用 [QUIC-TRANSPORT] 中的帧时,帧的名字之前会添加引语“QUIC”。比如,“QUIC CONNECTION_CLOSE 帧”。
没有这个引语的引用(帧)指 7.2 节中定义的帧。
HTTP/3 connection: A QUIC connection where the negotiated
application protocol is HTTP/3.
HTTP/3 连接(HTTP/3 connection):一个 QUIC 连接,其协商的应用协议是 HTTP/3。
对端(peer):一个终端。在讨论某个终端时,”对端 “指的是与主要讨论对象相距较远的终端。
接收端(receiver):一个正在接收帧的终端。
发送端(sender):一个正在传输帧的终端
服务端:接受一个 HTTP/3 连接的端点。 服务端接收 HTTP 请求,并发送 HTTP 响应。
流(stream):QUIC 传输提供的一个双向或者单向的字节流。一个 HTTP/3 连接内的所有流都可以被认为是 “HTTP/3 流”,但 HTTP/3 内定义了多种流类型。
流错误(stream error):个别 HTTP/3 流上的应用级(application-level)错误。
术语 “content” 在 [SEMANTICS] 第 6.4 节中定义。
最后,术语 “resource”、”message”、”user agent”、”origin server”、”gateway”、”intermediary”、”proxy” 和 “tunnel”在 [SEMANTICS] 第 3 节中定义。
本文档中的数据包图片使用 [QUIC-TRANSPORT] 第 1.3 节中定义的格式来说明字段的顺序和大小。
3. Connection Setup and Management
3.1. Discovering an HTTP/3 Endpoint
HTTP 依赖了权威响应的概念:对于指定目标资源,(在目标 URI 中识别的)源服务端组织响应消息的时候,认为对请求最合适的那个响应。
在 [SEMANTICS] 的 4.3 节中讨论了给一个 HTTP URI 设置权威的服务端的事项。
“Https”方案将权限和证书结合起来,其中客户端认为(通过 URI 的权威模块识别的)主机的证书是可信的。
在收到 TLS 握手的服务器证书后,客户端必须使用 [SEMANTICS] 第 4.3.4 节中描述的过程来验证该证书是否与 URI 的源服务器相匹配。 如果证书不能对 URI 的源服务器进行验证,客户端必须不考虑(MUST NOT)该服务器对该源的权威性。
客户端可以(MAY)使用带“https”的 URI 来访问资源:首先将主机标识解析为 IP 地址,
接着建立一个到这个地址和指定端口的 QUIC 连接(包括上述服务器证书的验证),
然后在这个安全的连接上发送一个将 URI 指向服务端的 HTTP/3 请求消息。
除非有其他机制用于选择 HTTP/3,否则在 TLS 握手过程中的应用层协议协商(ALPN;见 [RFC7301])扩展中使用令牌 “h3”。
连通性问题(例如,阻塞 UDP)可能导致 QUIC 建连失败;这种情况下客户端应当(SHOULD)尝试使用基于 TCP 的 HTTP 版本。
服务端可以(MAY)在任何 UDP 端口上开启 HTTP/3 服务;另外一种服务通告的方式总是包含了一个显式端口,以及包含和方案相关的显式端口或者默认端口的 URI。
3.1.1. HTTP Alternative Services
终端可以通过 Alt-Svc 响应头或者 HTTP/2 ALTSVC 帧宣告自己支持 HTTP/3
如响应头 Alt-Svc: h3=”:50781” 表示相同主机下的 50781 端口支持 HTTP/3
当接收到表明支持 HTTP/3 的响应头时,客户端可以(MAY)尝试建立 QUIC 连接,如果连接建立成功,客户端可以使用本文中描述的映射发送 HTTP 请求。
3.1.2. Other Schemes
尽管 HTTP 独立于传输协议,“http”方案将 权限 和 在权限模块认证的任何主机的指定端口上接受 TCP 连接的能力结合起来。
由于 HTTP/3 不使用 TCP,因此它无法直接访问权威服务端上由“http”URI 识别的资源。
然而,诸如 [ALTSVR] 的协议扩展允许权威服务端来识别其他的权威但可以通过 HTTP/3 访问的服务。
在对不是“https”方案的源发送请求之前,客户端必须(MUST)保证服务端愿意为该方案提供服务。
对于 “http 方案”的源,[RFC8164] 中描述了一种实验性的方法来实现这一目标。
将来可能会为各种方案定义其他机制。
3.2 Connection Establishment
HTTP/3 依赖 QUIC version 1 作为传输层,将来的规范可能会定义使用 HTTP/3 的其他 QUIC 传输版本。
QUIC 握手协议必须大于等于 TLS 1.3。
HTTP/3 客户端必须(MUST)支持在 TLS 握手期间向服务端指示目标主机的机制。 如果服务端是通过域名([DNS-TERMS])标识的,则客户端必须发送服务端名称指示(SNI; [RFC6066])TLS 扩展,除非使用了指示目标主机的替代机制。
QUIC 连接的建立如 [QUIC-TRANSPORT] 中所述。在建立连接的过程中,TLS 握手中 ALPN token “h3” 用来表明是否支持 HTTP/3。可以在一次握手中提供对其他应用层协议的支持。
虽然与核心 QUIC 协议有关的连接级选项是在初始加密握手中设置的,但 HTTP/3 特定的设置是在 SETTINGS 帧中传达的。在建立 QUIC 连接后,每个终端必须发送一 SETTINGS 帧(第 7.2.4 节,作为各自 HTTP 控制流的初始帧(见第 6.2.1 节)。
3.3. Connection Reuse
HTTP/3 连接可用于多个请求。为了达到最好的性能,如果客户端一般不会关闭连接,除非客户端认为不再需要继续和服务端进行通信(比如,用户离开指定 web 页),或者知道服务端关闭了连接。
一旦建立上和服务端的连接,该连接可以(MAY)用于请求其他 URI,只要服务端是经过认证的。
要将现有连接用于新的源,客户端必须通过 [SEMANTICS] 第 4.3.4 节中所述的过程,来验证服务端为新的源服务端提供的证书。 这意味着客户端将需要保留服务端证书以及验证该证书所需的任何其他信息,不这样做的客户端将无法为其他的源重用该连接。
如果证书因任何原因不能被新的源接受,该连接决不能被重新使用,应该为新的源建立新的连接。如果证书不能被验证的原因可能适用于其他已经与连接相关联的源,客户端应该为这些源重新验证服务端证书。例如,如果因为证书过期或被撤销而导致证书验证失败,这可能会被用来使所有其他用于建立授权的源无效。
同一个 IP 地址和 UDP 端口的情况下,客户端不应该(SHOULD NOT)打开多个 HTTP/3 连接,其中 IP 地址和端口从 URI、选择的替代服务 [ALTSVR]、或配置代理或其中任何一个的名称解析中获得。客户端可以(MAY)使用不同的传输或 TLS 配置,打开多个到相同 IP 地址和 UDP 端口的连接,但应该(SHOULD)避免以同一个配置创建多个连接。
建议服务端尽可能长地维护 HTTP/3 连接,如果有必要才关闭空闲的连接。如果任一个终端选择关闭 HTTP/3 连接,它应当(SHOULD)先发送一个 GOAWAY 帧(5.2 节),这样两端可以可靠地决策是否之前发送的帧已被处理,且可以优雅完成或终止剩下的所有任务。
如果服务端不希望客户端针对特定来源复用 HTTP/3 连接,可以通过响应请求发送 421(错误定向的请求)状态码来表明该请求对服务端不具有权威性(见 [SEMANTICS] 的 7.4 节)
4. HTTP Request Lifecycle
4.1. HTTP Message Exchanges
客户端在客户端发起的双向 QUIC 流上发送 HTTP 请求。客户端必须(MUST)在一个指定的流上发送单个请求。
服务端在和请求相同的流上发送 0 到多个临时的 HTTP 响应,然后再发单个最终的 HTTP 响应,详见下文。关于临时和最终 HTTP 响应的描述,请参见 [SEMANTICS] 的第 15 章。
推送的响应在一个服务端发起的单向 QUIC 流上进行发送;参考 6.2.2 小节。
和标准的响应方式一样,服务端发送 0 到多个临时的 HTTP 响应,然后发送单个最终的 HTTP 响应。
在 4.4 节中对推送进行了更加详细的描述。
在指定的流上,收到多个请求,或者在一个最终的 HTTP 响应之后再收到一个 HTTP 响应,必须(MUST)把这种情况当成是异常。
一个 HTTP 消息(请求或响应)由以下内容组成:
- 头部字段区:作为单个 HEADERS 帧发送(参考 7.2.2 小节)。
- (可选项)载荷:如果有载荷,就作为一列 DATA 帧发送(参考 7.2.1 小节),
- (可选项)尾部字段区:如果有这个字段,则以单个 HEADERS 帧的形式发送。
头部字段区和尾部字段区在 [SEMANTICS] 的 6.3 和 6.5 节中进行了说明;载荷在 [SEMANTICS] 的 6.4 节中进行了说明。
如果收到一列无效的帧,必须(MUST)把这种情况当成是 H3_FRAME_UNEXPECTED 类型的连接错误(第 8 章)。实际上,出现在 HEADERS 帧之前的 DATA 帧,或者出现在尾部 HEADERS 帧之后的 HEADERS 或 DATA 帧,都是无效的。其他帧类型(尤其是未知帧类型)可能会受其自身规则的约束,请参阅第 9 节。
服务端可以(MAY)在响应消息的帧之前、之后、中间发送一个或多个 PUSH_PROMISE 帧(参考 7.2.5 小节)。
这些 PUSH_PROMISE 帧不是响应的一部分(参考 4.4 节)。PUSH_PROMISE 帧不允许出现在推送响应中;必须把包含 PUSH_PROMISE 的推送响应当做 H3_FRAME_UNEXPECTED 类型的连接错误(见第 8 章)。
在一个请求或者推送流上,未知类型的帧(第 9 章),包括保留帧(第 7.2.8 小节),可以(MAY)在本章描述的其他帧之前、之后、中间发送。
HEADERS 和 PUSH_PROMISE 帧可能引用 QPACK 动态表的更新。虽然这些更新不是消息交换的直接部分,但是他们必须在消息可以被销毁前被接收并处理,详情参考 4.1.1.
Transfer codings (see Section 6.1 of [HTTP11]) are not defined for HTTP/3; the Transfer-Encoding header field MUST NOT be used.
未为 HTTP/3 定义传输编码(请参见 [HTTP11] 的 6.1 节),不得(MUST NOT)使用 Transfer-Encoding 标头字段。
当且只当同一个请求的最终响应之前有一个或多个信息响应(1xx,参考 [SEMANTICS] 第 15.2 节),响应才可以(MAY)包括多个消息。临时响应不包含载荷体或尾部。
一个 HTTP 请求/响应交换完全占用了客户端发起的双向 QUIC 流。在发送请求之后,客户端必须(MUST)关闭流的发送。除非使用了 CONNECT 方法(参考 4.2 节),客户端必须不能(MUST NOT)根据收到的响应来关闭流。在发送最终响应之后,服务端必须(MUST)关闭流的发送。此刻,QUIC 流就完全关闭了。
当关闭了一条流,这意味着最终 HTTP 消息(final HTTP message)结束了。因为一些消息过大,只要收到消息足够多的内容来进行处理,终端应当(SHOULD)开始处理并发 HTTP 消息。如果尚未收到足够多的消息来生成一个完整的响应,但是客户端发起的流关闭了连接,服务端应当(SHOULD)以 H3_REQUEST_INCOMPLETE 错误码终止响应(见第 8 章)。
如果响应不依赖请求的任何部分,即使请求还没有发送和接收,服务端可以在客户端发送一整个请求之前发送一个完整的响应。
当服务端不需要接收请求的剩余部分,它可以(MAY)终止读取请求流,发送一个完整的响应,并干净地关闭流的发送部分。
当要求客户端停止在请求流上发送时,应当(SHOULD)使用 H3_NO_ERROR 错误码。
尽管客户端总是可以因为其他原因而丢弃响应,但是在客户在请求突然终止之后,它必须不能(MUST NOT)丢弃完整的响应。
如果服务端发送了部分或者完整的响应,但是没有终止读取请求,客户端应当(SHOULD)继续发送请求的实体,并正常关闭流。
4.1.1. Field Formatting and Compression
HTTP 消息以 一系列被称为 HTTP 字段的 key-value 对的形式 携带了元数据,见 [SEMANTICS] 中 6.3 和 6.5 节。
https://www.iana.org/assignments/http-fields/ 中的“超文本传输协议(HTTP)字段名注册表”维护了已注册的 HTTP 字段的列表。
字段名是包含 ASCII 字符码子集的字符串,[SEMANTICS] 第 5.1 节讨论了更具体的 HTTP 字段名和值的属性。
在 HTTP/2 中,字段名的字符必须(MUST)在编码前转换成小写格式。
字段名中包含大写字符的请求或响应必须(MUST)被当成异常(4.1.3 小节)。
与 HTTP/2 一样,HTTP/3 不使用 Connection 头部字段来表示连接特定的字段;
在这个协议中,连接特定元数据通过其他方式传输。
终端必须不能(MUST NOT)生成包含连接特定字段的 HTTP/3 字段区。
任何包含连接特定字段的消息必须(MUST)被当成异常(4.1.3 小节)。
唯一的例外是 TE 头部字段,它可以(MAY)出现在 HTTP/3 请求头部中;当它出现时,除了“trailiers”,它必须不能(MUST NOT)包含任何其他值。
将 HTTP/1.x 消息转换为 HTTP/3 的中间适配器必须删除 [SEMANTICS] 第 7.6.1 节中讨论的特定连接头部字段,否则它们的消息将被其他 HTTP/3 终端视为非法的(第 4.1.3 节)。
4.1.1.1. Pseudo-Header Fields、
和 HTTP/2 一样,HTTP/3 采用了一系列以 ’:’(ASCII 码 0x3a) 字符开始的伪头部字段。
这些为头部字段携带了目标 URI,请求的方法,以及响应的状态码。
伪头部字段不是 HTTP 字段。除了本文中定义的,终端必须不能(MUST NOT)生成其他的伪头部字段;
然而,一个扩展可以协商修改此限制(参见第 9 节)。
伪头部字段只在它们定义的上下文中有效。
为请求定义的伪头部字段必须不能(MUST NOT)出现在响应中;为响应定义的伪头部字段必须不能(MUST NOT)出现在请求中。
伪头部字段必须不能(MUST NOT)出现在尾部字段。
终端必须(MUST)把包含未定义或者无效伪头部字段的请求或响应当成是异常(参考 4.1.3)。
所有伪头部字段必须(MUST)出现在头部字段区的常规头部字段之前。
任何请求或响应中,如果头部字段区中,伪头部字段出现在常规头部字段之后,必须(MUST)认为这是一种异常。(4.1.3 小节)
请求的伪头部字段定义如下:
“:method”:包含了 HTTP 方法([SEMANTICS] 第 9 章)。
“:scheme”:包含了目标 URI 的方案部分([URI] 第 3.1 小节)。
“:scheme”不限于”http”和”https”方案的 URI。一个代理或者网管可以将请求转换成非 HTTP 的方案,从而来使 HTTP 与非 HTTP 服务进行交互。
关于使用 “https “以外的方案的指导,请参见 3.1.2 节。
“:authority”:包含了目标 URI 的权限部分([URI 第 3.2 节])。权限必须不能(MUST NOT)在”http”或”https”方案中包含已废弃的”userinfo”子模块。
为了保证准确赋值 HTTP/1.1 请求行,对 - 具有原始形式或者星号形式请求目标的 - HTTP/1.1 请求进行转换时,必须(MUST)删除这个伪头部字段。(参考 [SEMANTICS] 第 7.1 节)。
直接生成 HTTP/3 请求的客户端应当(SHOULD)使用”:authority”伪头部字段来替代 Host 字段。
如果 HTTP/3 请求中没有 Host 字段,一个将 HTTP/3 请求转换成 HTTP/1.1 请求的中间媒介必须(MUST)构建一个 Host 字段,并将”:authority”伪头部字段的值拷贝到这个字段中。
“:path”:包含了目标 URI 的路径和查询部分(“path-absolute”,一个可选的 ’?’ 字符且跟着一个”query”),参考 [URI] 的 3.3 节和 3.4 节。星号格式的请求中,”:path”伪头部字段包含了’’。
“http”或 “https” URI 中,这个伪头部字段必须不能(MUST NOT)为空;
不包含路径模块的“http” 或 “https” URI 必须包含一个 ‘/’ 。但是有一个例外:”http”或”https”URI 的 OPTIONS 请求不包含路径模块。
这些必须(MUST)包含带 ’‘ 符号的”:path”伪头部字段(参考 [SEMANTICS] 第 7.1 节)。
所有 HTTP/3 请求的”:method”,”:scheme”, 以及”:path”伪头部字段必须(MUST)只包含一个值,除非是 CONNECT 请求;(参考 4.2 节)
如果”:scheme”伪头部字段标识了一个有强制权限模块的方案(包括”http”和”https”),
请求必须(MUST)包含”:authority”伪头部字段或”Host”头部字段,且必须不能为空。
如果同时出现了这两个字段,它们必须(MUST)包含相同的值。
如果方案没有强制权限模块,并且请求目标中也没有,那么请求必须不能(MUST NOT)包含”.authority”伪头部字段或“Host”头部字段。
删除了强制的伪头部字段,或者包含无效伪头部字段的 HTTP 请求是非法的。(4.1.3 小节)
HTTP/3 没有定义像 HTTP/1.1 请求行一样携带版本标识的方式。
对于响应,定义了单个”:status”伪头部字段,这个字段携带了 HTTP 状态码,参考 [SEMANTICS] 第 15 章。
这个伪头部字段必须(MUST)包含在所有响应中;否则,这个响应就是非法的(4.1.3 小节)。、
HTTP/3 没有定义像 HTTP/1.1 请求行一样携带版本或者原因的方式。
4.1.1.2. Field Compression
[QPACK] 描述了 HPACK 的一种变体,它使编码器能够对压缩可能造成的队头阻塞进行某种程度的控制, 这使得编码器能够平衡压缩效率和延迟。 HTTP/3 使用 QPACK 来压缩头部和尾部字段,包括头部字段中的伪头部字段。
为了更高的压缩效率,可以(MAY)在压缩前,将“Cookie”字段分割到多个单独的字段行中,每个带一个或多个 cookie 对。
如果解压缩字段区包含了多个 cookie 字段行,在将它们传到到上下文(比如 HTTP/1.1 连接,或者一个通用的 HTTP 服务应用)而非 HTTP/2 或者 HTTP/3 之前,必须(MUST)使用两个 8 位字节定位符 0x3B 和 0x20(ASCII 字符串“; ”,一个分号和一个空格)来将它们整合成单个 8 位字节字符串。
4.1.1.3. Header Size Constraints
一个 HTTP/3 实现可以(MAY)强加一个消息头部的最大尺寸限制,从而来约束它在一个 HTTP 消息上接收的消息头部最大尺寸。
如果服务端收到的头部区尺寸大于这个限制,可以发送一个 HTTP 431(请求头部字段过大)状态码([RFC6585])。
客户端可以丢弃它不能处理的响应。字段列表的大小根据压缩前字段的大小计算,包括名称和值的字节长度,加上每个字段 32 字节的开销。
如果实现希望将这个限制通知给对端,可以将这个字段加入到 SETTINGS_MAX_FIELD_SECTION_SIZE 参数中进行传递。
收到这个参数的实现不应该(SHOULD NOT)发送头部超过这个限制的 HTTP 消息,因为对端很有可能会拒绝处理。
然而,一个 HTTP 消息在到达源服务器之前可以穿越一个或多个中间媒介(参见 [SEMANTICS] 的 3.7 节)。
因为这个限制是由每个处理消息的实现单独应用的,所以消息即使小于这个限制,也不保证一定就会被接受。
4.1.2. Request Cancellation and Rejection
一旦请求流被打开,请求可以被任何一个终端取消。
客户端如果对响应不再感兴趣,就会取消请求;服务端如果不能或选择不响应,就会取消请求。
在可能的情况下,建议(RECOMMENDED)服务端发送带有适当状态代码的 HTTP 响应,而不是取消已经开始处理的请求。
实现应该(SHOULD)通过 突然终止仍然打开的流的任何方向 来取消请求,这意味着重置流的发送部分和中止流的接收部分的读取(参见第 2.4 节 [QUIC-TRANSPORT])。
当服务端在没有执行任何应用处理的情况下取消请求时,该请求被视为 “已拒绝 “。 服务端应该以错误码 H3_REQUEST_REJECTED 中止其响应流。
在这种情况下,”已处理 “意味着流中的一些数据被传递给了一些更高一层的软件,而这些软件可能因此采取了一些行动。 客户端可以把被服务端拒绝的请求当作根本没有发送过,从而可以在以后重试。
对于服务端已经部分或全部处理的 request,必须不能(MUST NOT)用 H3_REQUEST_REJECTED 错误码。
当服务端在部分处理之后放弃响应,它应当(SHOULD)以 H3_REQUEST_CANCELLED 错误码退出响应流。
when a server has requested closure of the request stream with this error code.
客户端应该使用错误码 H3_REQUEST_CANCELLED 来取消请求。
收到这个错误码后,如果没有进行任何处理,服务端可以使用错误代码 H3_REQUEST_REJECTED 突然终止响应。
客户端不能使用 H3_REQUEST_REJECTED 错误码,除非服务端用这个错误码要求关闭请求流。
如果一个 stream 在接收到完整的 response 之后被取消,客户端可以忽略取消,使用这个 response。然而,如果一个 stream 在接收到部分 response 之后被取消,这个 response 不应当被使用。
只有像 GET、PUT 或 DELETE 这样的幂等操作才可以安全地重试;客户端不应该自动重试一个非幂等方法的请求,除非它有办法知道请求语义是独立于方法的幂等操作,或者有办法检测到原始请求从未被应用(详见 [SEMANTICS] 第 9.2.2 节)。
4.1.3. Malformed Requests and Responses
一个请求或响应会因为如下原因而非法:
- 出现了禁止的字段或者伪头部字段,
- 缺失了强制要求的伪头部字段,
- 伪头部字段值无效
- 伪头部字段出现在字段之后,
- HTTP 消息序列无效,
- 包含大写字段名
- 字段名或者值中包含无效字符
包含载荷体的请求或响应可以包含 Content-Length 头部字段。如果请求的 Content-length 头部字段的值和组成实体的 DATA 帧的载荷长度之和不相等,那么这个请求也是非法的。
如果响应没有载荷(详见 [SEMANTICS] 第 6.4.1 小节),即使 DATA 帧中没有内容,它的 content-length 字段也可以为非 0 值。
处理 HTTP 请求或响应的中间媒介(例如任何不是通道的中间媒介)必须不能(MUST NOT)转发非法的请求或响应,
一旦检测到非法请求或响应,必须认为这是一种 H3_MESSAGE_ERROR 类型的流错误。
服务端在收到非法请求之后,可以(MAY)在关闭或重置流之前,发送一个 HTTP 响应,来指示错误。
客户端必须不能(MUST NOT)接受非法响应。
这些要求是为了抵御几种针对 HTTP 的常见攻击;必须极其严格遵守这些要求,否则可能导致实现收到这些攻击。
4.2. The CONNECT Method
CONNECT 方法要求接收者,发布一条到以 request-target([SEMANTICS] 第 9.3.6 节)区分的目标源服务端的信道。这主要被 HTTP 代理用于建立和源服务端的 TLS 会话,从而来与”https”资源进行交互。
在 HTTP/1.x,CONNECT 用来将整个 HTTP 连接转换成连接到远端主机的信道。在 HTTP/2 和 HTTP/3,CONNECT 方法被用来在单个流上建立信道。
CONNECT 请求必须(MUST)按如下规则进行构建:
- “.method:” 伪头部字段填成“CONNECT”;
- 删除“.scheme”和“.path”伪头部字段;
- “.authority”伪头部包含了将连接的主机和端口(等同于 CONNECT 请求中的 request-target 的 authority-form,详见 [SEMANTICS] 第 7.1 节)
请求流在请求的结尾保持打开来传输数据。一个不遵守这些约束的 CONNECT 请求是非法的。
支持 CONNECT 的代理建立的到服务端 TCP 连接 ([RFC0793]) 以”:authority”伪头部字段区分。一旦成功建连,代理就会给客户端发送包含 2xx 系列状态码的 HEADERS 帧,如 [SEMANTICS] 第 15.3 节定义。
所有流上的 DATA 帧和 TCP 连接上收发的数据相对应。客户端发送的任何 DATA 帧的有效载荷被代理传送给 TCP 服务端;代理从 TCP 服务端收到的数据会被打包成 DATA 帧。注意不能保证尺寸和 TPC 分段数能够和 HTTP DATA 或 QUIC STREAM 帧的尺寸和数量匹配。
一旦完成 CONNECT 方法,流上就只能发送 DATA 帧。如果扩展定义了 Extension 帧在这种情况下的使用状况,则可以(MAY)使用 Extension 帧。如果收到任何其他已知的帧类型,必须(MUST)将其当做是 H3_FRAME_UNEXPECTED 类型的连接错误(见第 8 章)。
TCP 连接可以被任一对等端终止。当客户端终止请求流时(也就是代理商的接收流进入到“Data Recved”状态),代理会设置到服务端 TCP 连接的 FIN 位。当代理收到了设置了 FIN 位的包,它会关闭它到客户端的发送流。在单向上保持半关闭状态的 TCP 连接是无效的,但服务端通常不关心,因此客户端如果还想从 CONNECT 的目标收数据的话,不应该(SHOULD NOT)关闭发送流。
通过粗暴地终止流来发出 TCP 连接错误信号。代理将任何 TCP 连接错误当成 H3_CONNECT_ERROR 类型的流错误(见第 8 章),包括收到带设置了 RST 为的 TCP 分段。对应地,如果代理探测到流错误或者 QUIC 连接错误,它必须(MUST)关闭 TCP 连接。如果底层 TCP 实现允许,代理应当(SHOULD)发送设置了 RST 位的 TCP 分段。
由于 CONNECT 创建了一条通往任意服务端的隧道,所以支持 CONNECT 的代理应该(SHOULD)将其使用限制在一组已知端口或安全的请求目标列表中;更多细节请参见 [SEMANTICS] 第 9.3.6 节。
4.3. HTTP Upgrade
HTTP/3 不支持 HTTP 升级机制([SEMANTICS] 第 7.8 节),也不支持 101(切换协议)信息状态码([SEMANTICS] 第 15.2.2 小节)
4.4. Server Push
Server Push 是一种交互模式,服务端在预料到客户端会发送指定请求之后,允许服务端向客户端推送一个 request-response 交换。这种方式抵消了潜在延迟增益的网络占用。HTTP/3 server push 和第 8.2 节中描述的 [HTTP/2] 很相似,但是使用不同的机制。
每个 server push 都会被服务端分配一个唯一的 Push ID。 在整个 HTTP/3 连接的生命周期中,Push ID 用于在各种情况下引用推送。
Push ID 的空间从 0 开始,到 MAX_PUSH_ID 帧设置的最大值结束(见 7.2.7 节)。
特别地,在客户端发送 MAX_PUSH_ID 帧后,服务端才能进行推送。
客户端发送 MAX_PUSH_ID 帧来控制服务端可以承诺的推送次数。
服务端应该按顺序使用 Push ID,从零开始。当没有发送 MAX_PUSH_ID 帧或流引用的 Push ID 大于最大 Push ID 时,客户端必须将收到的推送流视为类型为 H3_ID_ERROR(第 8 章)的连接错误。
Push ID 在一个或多个 PUSH_PROMISE 帧(第 7.2.5 节)中使用,这些帧携带了请求消息的头部字段。
这些帧在生成推送的请求流上发送,这允许服务端推送与客户端请求相关联。
当在多个请求流上应答同一 Push ID 时,解压后的请求字段部分必须以相同的顺序包含相同的字段,并且每个字段中的名称和值都必须是相同的。
然后,Push ID 被包含在最终实现这些承诺的推送流中(见第 6.2.2 节)。
推送流标识了它所实现的承诺的 Push ID,然后包含对承诺请求的响应(如 4.1 节所述)。
最后,Push ID 可以在 CANCEL_PUSH 帧中使用( 见 7.2.3 节)。
客户端使用此帧表示它们不希望收到承诺的资源;服务端使用此帧表示它们将无法履行先前的承诺。
不是所有的请求都能被推送。服务端可以(MAY)推送具备如下属性的请求:
- 可以缓存;参考 [SEMANTICS] 第 9.2.3 小节
- 安全的;参考 [SEMANTICS] 第 9.2.1 小节;
- 不包含请求实体或尾部去
服务端必须(MUST)在服务端授权的”.authority”伪头部字段中包含一个值。如果客户端尚未验证推送请求所指示的源的连接,则它必须执行与 在连接上发送针对该源的请求之前执行 相同的验证过程(见第 3.3 节)。 如果该验证失败,则客户端不得认为(MUST NOT)该源服务器具有权威性。
一收到一个 PUSH_PROMISE 帧,但是它携带的请求不能缓存、风险未知、暗示存在请求实体,或者客户端不认为发这个请求的服务端是权威的,客户端应当(SHOULD)就立刻发送一个 CANCEL_PUSH 帧。不得(MUST NOT)使用或缓存任何相应的响应。
每个推送的响应和一个或者多个请求相关联。推送和收到 PUSH_PROMISE 帧的请求流相关联。
同一个服务端推送可以通过在多个请求流中,使用带相同 Push ID 的 PUSH_PROMISE 帧,从而与多个客户端请求关联起来。这些关联行为不会影响协议的操作,但是用户代理可以(MAY)在决定如何使用被推送的资源时考虑采用。
与响应特定部分相关的 PUSH_PROMISE 帧的排序至关重要。服务端应当(SHOULD)在发送引用了 promised 响应的 HEADERS 或者 DATA 帧之前,发送一个 PUSH_PROMISE 帧。这减少了客户端请求那些会被服务端推送的资源的概率。
如果服务端迟一点兑现承诺,服务端在 push stream 上推送响应;参考 6.6.2 小节。push stream 区分它兑现的承诺的 Push ID,然后向承诺的请求推送一个响应,响应的格式和 4.1 节描述的响应格式相同。
由于乱序的原因,推送流数据可能在对应的 PUSH_PROMISE 帧之前到达。当客户端收到一个目前 Push ID 未知的推送流,相关的客户端请求和推送请求头部字段都是未知的。客户端可以缓存流数据,并等待相关 PUSH_PROMISE。
客户端可以使用流的流控制([QUIC-TRANSPORT] 第 4.1 节)来限制服务器在推送流上送入的数据量。
客户端取消推送后,推送流数据也可以到达。
在这种情况下,客户端可以用错误代码 H3_REQUEST_CANCELLED 中止读取流。
这将要求服务端不要传输其他数据,并表示收到数据后会将其丢弃。
如果客户端实现了 HTTP 缓存,那么可以缓存那些可缓存的(参考 [CACHING] 第 3 章)推送的响应。在服务端收到推送响应的时候,认为它在原始服务器(例如,如果出现了”no-cache”缓存响应指令([CACHING] 第 5.2.2.3 小节))上就被成功校验了。
不能缓存的推送响应必须不能(MUST NOT)被任何 HTTP 缓存存储。它们可以(MAY)被单独提供给应用。
5. Connection Closure
一旦建立,HTTP/3 connection 可以用于很多个 request 和 response 持续一段时间,直到被关闭。connection 关闭可以在几种不同的情况下发生
5.1 Idle Connections
每个 QUIC 终端在握手时定义一个空闲超时时间。如果 QUIC 连接空闲时间超过超时时间,对端会假设连接已经被关闭。如果现有连接的空闲时间超过 QUIC 握手期间协商的空闲超时时间,则 HTTP/3 实现需要新打开一个 HTTP/3 连接来处理新的请求,当接近空闲超时时间时,也应当这么做,请参阅 [QUIC-TRANSPORT] 的 10.1 节。
HTTP 客户端预期连接是打开的,在有 response 或 server push 的时候。如果客户端没有预期从服务端收到 response,允许空闲连接关闭比维持一个可能不再使用的连接更好。网关可能更倾向于保持连接,而愿意引发与服务器建连的延迟。服务端不应当主动保持连接打开。
5.2. Connection Shutdown
即使连接不空闲,但是其中任何一个终端都可以决定停用连接,并开始文明地关闭连接。终端通过发送一个 GOAWAY 帧来文明的关闭一个 HTTP/3 连接(7.2.6 节)。
GOAWAY 帧包含了一个标识,向接收者表明了这个连接中已经或者可能将会被处理的请求或推送的范围。
服务端发送了一个客户端发起的双向 Stream ID;客户端发送一个 Push ID(4.4 节)。
标识大于等于指定值的请求或推送会被 GOAWAY 的发送者拒绝(4.1.2 节)。
如果没有请求或推送被处理,这个标识可以(MAY)是 0。
GOAWAY 帧中的信息可以让客户端和服务端在连接关闭前,在接受哪个请求或推送的问题上达成一致。
在发送 GOAWAY 帧的时候,终端应当(SHOULD)显式地取消所有标识大于等于指示值的请求或推送,从而来清除受影响流的传输状态。如果后续还有请求或者推送达到,终端应当(SHOULD)继续如此处理。
从对端收到 GOAWAY 帧之后,终端必须不能(MUST NOT)在这个连接上发起新的请求、承诺新的推送。
客户端可以(MAY)建立新的连接来发送额外的请求。
一些请求或推送可能已经在传输中:
在收到 GOAWAY 帧的时候,如果客户端已经发送了请求,且请求的 Stream id 大于等于收到的 GOAWAY 帧中的标识,这些请求不会被处理。客户端可以在一个不同的连接上安全地重试未被处理的请求。当服务端关闭连接时,无法重试请求的客户端将失去所有正在处理中的请求。
如果请求的 Stream ID 比来自服务端的 GOAWAY 帧中的 Stream ID 小,那么这个请求可能被处理了;直到收到响应、流被单独重置、收到另一个 Stream ID 比有关请求低的 GOAWAY、连接终止,才能知晓请求的状态。
如果请求是单独的且还没被处理,服务端可以(MAY)拒绝那些小于指示 ID 的请求。
如果服务端在承诺了推送之后,服务端收到了一个 GOAWAY 帧,但是服务端推送的 Push ID 大于或者等于 GOAWAY 帧中的标识,因此这些推送会被拒绝。
当服务端提前知道要关闭连接的时候,即使提前量很小,服务端也应当(SHOULD)发送 GOAWAY 帧,这样远端可以知道请求是否被部分处理。比如,如果一个 HTTP 客户端在服务端关闭 QUIC 连接的同时发送了一个 POST,客户端无法知道服务端是否开始处理 POST 请求,除非服务端发送了 GOAWAY 帧来表示它在哪个 stream 上进行操作。
端点可以发送多个指示不同标识符的 GOAWAY 帧,但是每个帧中的标识符不得大于任何先前帧中的标识符,因为客户端可能已经在另一个 HTTP 连接上重试了未处理的请求。 接收到包含比先前接收到的标识符更大的标识符的 GOAWAY,必须将其视为 H3_ID_ERROR 类型的连接错误; 请参阅第 8 节。
尝试文明终止连接的终端可以发送一个 GOAWAY 帧,并将值设置为最大可能值(服务端 2^62-4,客户端 2^62-1)。这保证了对端停止创建新的请求或推送。在开始等待任何传输中的请求或推送到达的过程中,终端可以在连接终止之前,发送另一个 GOAWAY 帧来表明它可能接受的请求或推送。这保证了连接可以在不丢请求的情况下,文明的关闭连接。
客户端在选择 GOAWAY 帧的 Push ID 是有更高的灵活性。2^62 - 1 表示服务端可以继续承诺过的推送。更小的值意味着客户端会拒绝 Push ID 大于等于这个值的所有推送。跟服务端一样,只要 Push ID 不大于所有之前发送的值,客户端就可以(MAY)发送后续的 GOAWAY 帧。
即使当 GOAWAY 表明指定请求或推送在接收之后不会被处理或接受,下层的传输资源还是存在的。发起这些请求的终端可以取消它们来清理传输状态。
一旦所有接受的请求和推送被处理,终端可以允许让连接变成空闲,或可以(MAY)开始连接的立刻关闭。完成连接文明关闭的终端应当(SHOULD)使用 H3_NO_ERROR 错误码。
如果客户端用尽了所有可用双向流 ID 来发送请求,服务端不需要发送 GOAWAY 帧,因为客户端不能再发请求了。
5.3 Immediate Application Closure
HTTP/3 的实现可以在任何时候关闭 QUIC connection。这会向对端发送一个 QUIC CONNECTION_CLOSE 帧,表示应用层终止了连接。application error code 表示关闭 connection 的原因。
有关在 HTTP/3 中关闭连接时可以使用的错误码,请参见第 8 节。
在关闭 connection 之前,发送 GOAWAY 允许客户端重试一些 requests。将 GOAWAY 帧和 QUIC CONNECTION_CLOSE 帧合并到同一个包能够增加帧被客户端接收的几率。
If there are open streams that have not been explicitly closed, they are implicitly closed when the connection is closed; see Section 10.2 of [QUIC-TRANSPORT].
如果有打开的流没有被显式关闭,那么当连接关闭时,它们会被隐式关闭;参见 [QUIC-TRANSPORT] 的 10.2 节。
5.4 Transport Closure
出于多方面原因,QUIC 传输层可以告知应用层 connection 已经终止了。终止可能由于对端明确关闭,传输层的错误,或者网络连通性的变化。
如果一个 connection 没有用 GOAWAY frame 终止,客户端必须假设全部或部分被发送的 requests 已经被处理过了。
6. Stream Mapping and Usage
QUIC 流提供可靠有序的传输,但是不保证不同流之间有序。在 QUIC version 1 中,包含 HTTP 帧的流数据被分帧封装在 QUIC STREAM 中,但是这些帧对 HTTP 分帧层是不可见的。传输层对接收到的流数据进行缓存并排序,将一个可靠的字节流暴露给应用程序。
虽然 QUIC 允许在流内进行无序传输,但 HTTP/3 并没有利用这个功能。
QUIC 流可以是单向的也可以是双向的,流可以由客户端初始化,也可以由服务器初始化。
当 HTTP 字段和数据通过 QUIC 发送,QUIC 层处理了大多数流管理的事务,HTTP 不需要做任何解复用的事情。在 QUIC 流上发送的数据总是会映射到指定的 HTTP 事务或整个 HTTP/3 连接上下文。
6.1 Bidirectional Streams
所有由客户端启动的双向流都用于 HTTP 请求和响应。
双向流保证响应能关联上请求,这些流称为请求流。
客户端首个请求 stream 为 0,随后的请求 stream 为 4,8,等等。
为了打开这些流,HTTP/3 服务端应当(SHOULD)配置允许的流数量和初始流控制窗口的非 0 最小值。同时为了不必要的限制并发性,同时应(SHOULD)至少允许 100 个请求。
HTTP/3 不使用服务器初始化的双向流,尽管扩展可以定义这些流的使用;客户端收到服务端发起的双向流之后,必须把它当成是 H3_STREAM_CREATION_ERROR 类型(第 8 章)的连接错误,除非有一个支持这种功能的扩展,并且客户端与服务端经过了协商。
6.2 Unidirectional Streams
流开始时会发送一个单字节的头作为 stream type,头后面的数据格式由 stream type 决定。
1 | Unidirectional Stream Header { |
本文中定义了两种流类型:控制流(6.2.1 小节)和推送流(6.2.2 小节)。[QPACK] 定义了两种额外的流类型。其他流类型可以通过 HTTP/3 扩展进行定义;更多细节请参考第 9 章。某些流类型是保留的(第 6.2.3 节)。
在 HTTP/3 连接的生命周期的早期,它们的性能易受单向流上数据的创建和交换的影响。
过度限制这些流的数量和流控窗口的终端,会增加远端很早就达到限制并阻塞的风险。
实际上,实现应该考虑远端可能希望在它们允许使用的单向流上进行预留的流操作(6.2.3 小节)。
为了避免阻塞,客户端和服务端发送的传输参数都必须(MUST)允许对端创建至少一个用于 HTTP 控制流的单向流,以及强制扩展要求的单向流(基础 HTTP/3 协议和 QPACK 要求最少 3 个),同时应当(SHOULD)在每个流上提供最少 1024 个字节的流控信用。
值得注意的是,如果对端在创建关键的单向流之前耗尽了初始的流控信用,不要求终端生成额外的流控窗口来创建更多的单向流。终端应当(SHOULD)和创建强制扩展要求的单向流(比如 QPACK 编、解码流)一样,先创建 HTTP 控制流,再在它们对端允许的前提下创建额外的流。
如果接收者不支持流头部指示的流类型,流中剩余的数据会因为语义未知而不能被消费。
收到未知流类型,可以(MAY)以一个 H3_STREAM_CREATION_ERROR 错误码终止流的读取。但是必须不能(MUST NOT)认为这个流是哪种类型的连接错误。
实现可以(MAY)在知道对端是否支持该 type 前发送 stream types。但是能修改已存在协议组件(QPACK 或其他扩展)状态和语意的 type,不能(MUST NOT)在知道对端支持前发送。
除非有其他特殊的说明,发送者都可以关闭或重置单向流。接受者必须(MUST)兼容在收到单向流头部之前,关闭或重置单向流。
6.2.1 Control Streams
控制流的流类型是 0x00。这条流上的数据只发送 HTTP/3 帧。(7.2 节)
两端必须在连接开始时发起一个 crontrol stream,并且发送 SETTINGS frame 作为这个 stream 的第一帧。如果这个 control stream 的第一帧不是 SETTINGS frame,必须被视作连接错误 H3_MISSING_SETTINGS。两端各自只允许发送一个 crontrol stream;接收到第二个 control stream 必须被视作连接错误 H3_STREAM_CREATE_ERROR。发送者必须不能(MUST NOT)关闭控制流,接收者必须不能(MUST NOT)请求发送者关闭控制流。如果 crontrol stream 被关闭,必须被视为连接错误 H3_CLOSED_CRITICAL_STREAM。连接错误在第 8 章中介绍。
Because the contents of the control stream are used to manage the behavior of other streams, endpoints SHOULD provide enough flow control credit to keep the peer’s control stream from becoming blocked.
因为控制流的内容被用来管理其他流的行为,所以终端应该提供足够的流控信用,以防止对端的控制流被阻塞。
使用一对单向流而不是一个双向流,是为了两端都能尽快发送自己的数据。根据 QUIC 连接上 0-RTT 的使能情况,客户端或服务端可以先发送流数据。
6.2.2 Push Streams
服务端推送是一种在 HTTP/2 引入的可选的特性,允许服务端在尚未请求之前先发起响应,详情参考 4.4 节。
推送流的流类型为 0x01,紧接着为 Push ID,编码为可变长数字。剩下的数据由 HTTP/3 帧组成(见 7.2),通过 0 个或多个临时 HTTP 响应,后跟一个最终的 HTTP 响应,从而兑现了承诺。Server Push 和 Push ID 详见 4.1 节。
只有服务端才能 push,收到客户端发起的推送流,服务端应当视为 H3_STREAM_CREATION_ERROR 类型的连接错误。
1 | Push Stream Header { |
每个 Push ID 必须只被使用一次。如果 push stream header 包含被其他 push stream header 使用过的 Push ID,必须被视作 H3_ID_ERROR 类型的连接错误。
6.2.3 Reserved Stream Types
Stream types 类似于”0x1f * N + 0x21”被预留用作被忽略的 type。
这些流没有实际的意义,被用作应用层的填充(padding),可以用在当前没有请求数据要发送的连接上。
对端接收到时不能(MUST NOT)认为这些流有任何意义。
这种流的载荷和长度由不同的发送方实现自由选择。当发送一个保留的流类型时,实现可以(MAY)干净地终止该流或重置它。当重置流时,应(SHOULD)使用 H3_NO_ERROR 错误码或保留的错误码(第 8.1 节)。
7. HTTP Framing Layer
HTTP 帧通过 QUIC 流来进行传输,如第 6 章所述。HTTP/3 定义了三种流类型:控制流,请求流,推送流。本章描述了 HTTP/3 帧格式和允许出现这些帧的流类型;参考表 1。附录 A.2. 对 HTTP/2 帧和 HTTP/3 帧做了对比。
1 | +--------------+----------------+----------------+--------+---------+ |
SETTINGS 帧只能出现在 Control Stream 的第一个帧中:表 1 中用标记(1)标出。相关章节中会提供特定的指导。
值得注意的是,不像 QUIC 帧,HTTP/3 帧可以出现在多个包中。
7.1. Frame Layout
所有帧的格式如下:
1 | HTTP/3 Frame Format { |
每个帧包含下面的字段:
Type:变长整数帧类型
Length:表示帧载荷长度的变长整数,
Frame Payload:载荷,语意由 Type 字段决定
每个帧必须恰好包含以上定义的字段, payload 实际长度和 length 不符合的情况,必须被当成 H3_FRAME_ERROR 类型的连接错误(见第 8 章)。
In particular, redundant length encodings MUST be verified to be self-consistent; see Section 10.8.
特别是,必须验证冗余长度编码是自洽的,参见 10.8 节。
文明终止一个流,如果流的最后一个帧被截断,必须(MUST)把这种情况当成是 H3_FRAME_ERROR 类型的连接错误(第 8 章)。可以在粗暴终止的流
7.2. Frame Definitions
7.2.1. DATA
DATA frames(type=0x0)传输任意长度的字节序列,作为 HTTP 请求或者回包的载荷。
DATA frames 必须关联一个 HTTP 请求或者回包。如果 DATA frame 在 control stream 中被接收,接受端必须视作一个 H3_FRAME_UNEXPECTED 类型的连接错误(第 8 章)。
1 | DATA Frame { |
7.2.2 HEADERS
HEADERS frame(type=0x1)用来携带 QPACK 编码的 HTTP field section。详见 QPACK 文档
1 | HEADERS Frame { |
HEADERS frames 只能用在 request streams 或 push streams。在控制流上收到 HEADERS 帧,接收者必须(MUST)响应一个 H3_FRAME_UNEXPECTED 类型的连接错误。
7.2.3. CANCEL_PUSH
CANCEL_PUSH frame(type=0x3)用于在 push stram 被创建前取消 server push。CANCEL_PUSH frame 用 Push ID 作为标识,Push ID 为变长的整数。
客户端发送 CANCEL_PUSH 时,意味着它不想要接受服务端承诺的资源。
服务端收到这个 frame 时,终止发送 server push 的响应。但是这个机制具体的处理方式因推送流的状态而异:
尚未创建推送流(旧版本是尚未推送):不创建(不推送);
- 已经打开流:服务端应当粗暴地终止流(旧版本是发送一个 QUIC RESET_STREAM 帧,并终止响应的传输)
- 已经完成推送:服务端可以粗暴地终止流,也可以什么都不做(旧版本没说明)
服务端发送 CANCEL_PUSH 表明自己将不会履行之前发送的承诺,客户端不能指望对应的承诺会兑现,除非它已经收到并处理了承诺的响应。无论推送流是否已经打开,当服务端确定该承诺不会被履行时,都应该(SHOULD)发送一个 CANCEL_PUSH 帧。 如果流已经被打开,服务器可以用错误码 H3_REQUEST_CANCELLED 中止对该流的发送。
发送 CALCEL_PUSH 对推送流的现有状态不会产生直接影响。客户端不应当(SHOULD NOT)在收到推送流之后再发一个 CANCEL_PUSH 帧。因为服务端可能还没有处理 CANCEL_PUSH,所以推送流可能在客户端发送 CANCEL_PUSH 之后到达。客户端应当(SHOULD)以一个 H3_REQUEST_CANCELLED 错误码结束流的读取。
CANCEL_PUSH frame 只能在 control stream 中发送,在其他流中发送必须被视作 H3_FRAME_UNEXPECTED 类型的流错误。
1 | CANCEL_PUSH Frame { |
CANCEL_PUSH frame 携带变长的整数 Push ID,表示将被取消的 server push。如果 CANCEL_PUSH 帧中引用的 Push ID 比当前连接中允许的值要大,必须(MUST)被当成 H3_ID_ERROR 类型的连接错误;
客户端可能会收到 CANCEL_PUSH frame,其中 Push ID 可能因为乱序而还未被 PUSH_PROMISE frame 声明的。如果服务端收到的 CANCEL_PUSH 帧,但是 Push ID 没有在 PUSH_PROMISE 帧中提及,必须(MUST)将其当成 H3_ID_ERROR 类型的连接错误。
7.2.4. SETTINGS
SETTINGS frame (type=0x4) 用来传输配置参数,从而来影响终端通信的方式,比如终端行为的偏好与约束。一个 SETTINGS 参数也可以被称为“setting”;每个配置参数的标识和值可以被称为“setting identifier”和“setting value”。
SETTINGS 帧往往用于连接,而不是单个流。SETTINGS 帧必须(MUST)以每个对等端的每个控制流的第一个帧发送,且必须不能(MUST NOT)后续再发。如果终端在控制流上收到第二个 SETTINGS 帧,必须(MUST)以 H3_FRAME_UNEXPECTED 类型的连接错误进行响应。
除了控制流,其他类型的流必须不能(MUST NOT)发送 SETTINGS 帧。如果终端在其他流类型上收到 SETTINGS 帧,必须(MUST)以 H3_FRAME_UNEXPECTED 类型的连接错误进行响应。
SETTINGS 参数不是协商生成的;参数表明发送方的特性,被接收方使用。但是 SETTINGS 可以实现隐式协商,每个对端使用 SETTINGS 宣告自己支持的集合,双方根据两个集合选择哪些被使用。SETTINGS 不提供机制表明选择何时生效。
同一个参数两端可能会宣告不同的值。比如,客户端可能希望回包字段区足够大,而服务端对请求大小更谨慎。
setting identifier 不能出现超过一次。否则被视作 H3_SETTINGS_ERROR 类型的连接错误。
SETTINGS frame 的载荷由 0 个或多个参数构成,每个参数由变长整数的 ID 和变长的 value 组成,value 用 QUIC variable-length integer 编码。
1 | Setting { |
实现必须忽略带有它不理解的标识符的任何参数。
7.2.4.1. Defined SETTINGS Parameters
HTTP/3 中定义了以下设置项:
SETTINGS_MAX_FIELD_SECTION_SIZE (0x6): 默认值为无穷大,见 4.1.1.3 节。
“0x1f * N + 0x21”格式的 Setting identifiers 被预留用在测试忽略不认识的 identifiers。这种 setting 没有定义的意义,终端应当(SHOULD)至少包含一个这种 setting 在 SETTINGS frame 中,但是接收方必须忽略这种 setting。
因为该 setting 没有任何定义的含义,所以它的值可以为任何值。
HTTP/2 中同样预留了(参考 11.2.2 小节)设置标识,但和 HTTP/3 的不冲突。必须不能(MUST NOT)发送这些设置,一旦收到,则必须(MUST)当成 H3_SETTINGS_ERROR 类型的连接错误。
可以通过对 HTTP 3 的扩展来定义其他设置项。更多详细信息请参见第 9 章。
7.2.4.2 Initialization
HTTP 实现必须不能(MUST NOT)发送对端不能理解的帧或者请求。
一开始所有配置都是初始值。由于携带配置的包有可能丢失或延迟,每个终端应当(SHOULD)在收到对端 SETTINGS 帧使用初始值。当 SETTINGS 帧到达,需要将配置的值改为 SETTINGS 中的新值。
这避免了在发送消息前等待 SETTINGS 帧。终端必须不能(MUST NOT)在发送 SETTINGS 帧之前要求从对端收到数据;一旦传输层就绪,必须(MUST)尽快发送配置。
而服务端上,每个客户端的配置的初始值都是默认值。
对于使用 1-RTT QUIC 连接的客户端,每个服务端的配置的初始值是默认值。
在 QUIC 处理包含 SETTINGS 的数据包之前,1-RTT 密钥总是可用的,即使服务端立刻发送了 SETTINGS。
在发请求之前,客户端不应该(SHOULD NOT)无限期的等待 SETTINGS,
相反应该(SHOULD)处理收到的数据报,从而在发送第一个请求之前,增加处理 SETTINGS 的可能性。
当使用了 0-RTT QUIC 连接,每个服务端配置的初始值是之前会话中使用的值。
客户端应当(SHOULD)将服务端提供的配置和连接的恢复信息存在一起,
但是可以(MAY)选择某些情况下不存储配置(比如,在 SETTINGS 帧之前收到会话票证)。
当使用 0-RTT 方案时,客户端必须(MUST)使用存储的配置,如果没有存储,就使用默认值。
一旦服务端提供了新的配置,客户端必须(MUST)使用新的值。
服务端可以记住它通告的配置,或者将其加上完整性保护,存在票证中,并在收到 0-RTT 数据的时候恢复这个信息。服务端将 HTTP/3 配置值用于决定是否接受 0-RTT 数据。
如果服务端不能决定客户端记住的配置是否跟它当前配置兼容,它必须不能(MUST NOT)接受 0-RTT 数据。
如果客户端遵循的配置不违反服务端当前配置,记住的配置就是兼容的。
服务端可以(MAY)接受 0-RTT,后续在它的 SETTINGS 帧中提供不同配置。
如果服务端接受了 0-RTT 数据,它的 SETTINGS 帧必须不能(MUST NOT)减少任何限制或者改变任何值,否则客户端的 0-RTT 可能违反这个配置。
服务端必须(MUST)包括所有与默认值不同的配置。
如果服务端接受 0-RTT 但接着又发了和之前阐述的配置不兼容的配置,这就是一种 H3_SETTINGS_ERROR 类型的错误。
如果服务端接受了 0-RTT,但是接着发了一个 SETTINGS 帧,并且这个帧删除了一个之前非默认值、且客户端理解的配置值,
必须(MUST)将这种情况认为是一种 H3_SETTINGS_ERROR 类型的连接错误。
7.2.5 PUSH_PROMISE
PUSH_PROMISE frame (type=0x05) 用于服务端向客户端发送请求头部字段区,如 HTTP/2 中一样。
1 | PUSH_PROMISE Frame { |
Push ID: 变长的整数标识 server push 的操作。Push ID 用于 push stream headers(4.4 节)和 CANCEL_PUSH frames(7.2.3 节)。
Encoded Field Section: QPACK 编码的 request header,详见 [QPACK]
服务端不能使用超过客户端在 MAX_PUSH_ID frame 声明的 Push ID, 客户端接收到大于自己声明的 Push ID 时必须视作 H3_ID_ERROR 类型的连接错误。
服务端可以(MAY)在多个 PUSH_PROMISE 帧中使用同一个 Push ID。如果这么做的话解压缩后的请求头部集合中的字段必须(MUST)相同且顺序也一致,同时每个字段中的名称和值也必须严格匹配。客户端应当(SHOULD)对比请求头部区中承诺的资源进行多次对比。如果客户端收到了一个已经承诺过的但不匹配的 Push ID,必须(MUST)响应 H3_GENERAL_PROTOCOL_ERROR 类型的连接错误。如果解压后的字段区严格匹配,客户端应当(SHOULD)推送的内容与每个流关联起来。
允许对同一个 Push ID 重复索引主要是为了减少并发请求引起的冗余。服务端应当(SHOULD)避免长时间复用一个 Push ID。客户端可能会消费服务端推送响应,但不会保存以作后用。客户端如果发现一个 PUSH_PROMISE 帧中的 Push ID 是它们已经消费过的,则需要强制忽略这个 PUSH_PROMISE。
如果在控制流上收到 PUSH_PROMISE,客户端必须(MUST)以 H3_FRAME_UNEXPECTED 类型的连接错误进行响应。
客户端必须不能(MUST NOT)发送 PUSH_PROMISE 帧。如果服务端收到 PUSH_PROMISE 帧,必须(MUST)将这种情况当成 H3_FRAME_UNEXPECTED 类型的连接错误。
服务端推送机制详见 4.4 章。
7.2.6. GOAWAY
GOAWAY frame (type=0x7) 被任一终端用来优雅关闭连接。GOWAY 允许终端完成之前的请求处理的同时拒绝接受新的请求。这个特性提供了管理操作,比如服务器维护,GOAWAY 自己不会关闭连接。
1 | GOAWAY Frame { |
GOAWAY 帧总是在控制流上进行发送。在 S–>C 的方向上,它携带了由客户端发起的双向连接的变长整数 QUIC Stream ID。客户端如果接收到其他类型的 Stream ID,必须(MUST)将这种情况当成是 H3_ID_ERROR 类型的连接错误。
在 Client 到 Server 的方向上,GOAWAY 帧携带了变长整数形式的 Push ID。
GOAWAY 帧用于 connection 而不是特定的 stream。客户端必须(MUST)把在控制流以外的流中收到的 GOAWAY frame 视作 H3_FRAME_UNEXPECTED 类型的连接错误。
GOAWAY 帧的更多使用信息详见 5.2 节。
7.2.7 MAX_PUSH_ID
MAX_PUSH_ID frame (type=0xD) 用来控制服务端 server push 的次数。设置了服务端在 PUSH_PROMISE 帧和 CANCEL_PUSH 帧中能使用的 Push ID 的最大值。server push 的次数同时也受 QUIC 传输层控制。
MAX_PUSH_ID frame 永远在 control stram 中发送,在其他 stream 中接收到必须被视作 H3_FRAME_UNEXPECTED 类型的连接错误。
服务端不能发送 MAX_PUSH_ID frame ,否则客户端视为 H3_FRAME_UNEXPECTED 类型的连接错误。
最大 Push ID 在 HTTP/3 连接创建时被复位,意味着服务端在收到 MAX_PUSH_ID frame 前不能进行 server push。客户端通过增加 maximum Push ID 使用更多的 server push。
1 | MAX_PUSH_ID Frame { |
MAX_PUSH_ID 帧携带一个可变长度的整数,该整数标识了服务器可以使用的 Push ID 的最大值(见第 4.4 节)。 MAX_PUSH_ID frame 不能减少 Push ID 最大值;如果收到的 MAX_PUSH_ID 帧包含的值比之前收到的值小,则必须作为 H3_ID_ERROR 类型的连接错误处理。
7.2.8 Reserved Frame Types
保留了”0x1f * N + 0x21”类型的帧来满足忽略未知类型的需求。这些类型没有语意,可以在允许发送帧的任何流上发送, 这使它们可以用于应用程序层填充。对端收到这些帧时必须不能(MUST NOT)认为有任何意义。
载荷和长度由实现方式选择。
有一些帧类型,在 HTTP/2 中使用,但 HTTP/3 中没有,也被预留了(11.2.1 小节)。必须不能(MUST NOT)发送这些帧类型,一旦收到必须(MUST)当成是 H3_FRAME_UNEXPECTED 类型的连接错误。
8. Error Handling
当一个流无法成功完成时,QUIC 允许应用程序突然终止(重置)该流并传达原因([QUIC-TRANSPORT] 的 2.4 节),这称为“流错误”。
HTTP/3 实现可以决定关闭 QUIC 流并传达错误的类型,错误码在第 8.1 节中定义。
流错误与指示错误情况的 HTTP 状态码不同:流错误表示发送方未能传输或使用完整的请求或响应,而 HTTP 状态代码指示成功接收到请求的结果。
如果需要终止整个连接,则 QUIC 同样提供一种机制来传达原因(参阅 [QUIC-TRANSPORT] 的 5.3 节), 这称为“连接错误”。 与流错误类似,HTTP/3 实现可以终止 QUIC 连接,并使用第 8.1 节中的错误码传达原因。
虽然关闭流和连接的原因被称为 “错误”,但这些操作并不一定表明连接或任何一个实现有问题。 例如,如果不再需要请求的资源,流可以被重置。
端点可以选择在某些情况下将流错误作为连接错误处理,关闭整个连接以响应单个流的条件。
在做出这个选择之前,实现者需要考虑对未完成请求的影响。
因为可以不协商就定义新的错误码(第 9 章),在意外上下文使用错误码,或这收到未知错误码,必须等同于 H3_NO_ERROR。然而,关闭流的时候忽视错误码可能有其他影响(参考 4.1 节)。
8.1. HTTP/3 Error Codes
当粗暴地终止流、停止流的读取、或者立刻关闭 HTTP/3 连接的时候,使用以下错误码:
H3_NO_ERROR (0x100): 关闭连接或流但没有错误的时候使用
H3_GENERAL_PROTOCOL_ERROR (0x101): 某种形式上,对端违反了协议,但又没有对应更具体的错误码,或者终端不愿意使用更具体的错误码
H3_INTERNAL_ERROR (0x102): HTTP 协议栈发生了一个内部错误
H3_STREAM_CREATION_ERROR (0x103): 终端发现它的对端创建了一个它不会接受的流。
H3_CLOSED_CRITICAL_STREAM (0x104): HTTP/3 连接要求的流被关闭或者重置了
H3_FRAME_UNEXPECTED (0x105): 在当前状态或者当前流上,收到了一个不允许的帧。
H3_FRAME_ERROR (0x106): 收到的帧格式不对,或者大小不对
H3_EXCESSIVE_LOAD (0x107): 终端检测到对端当前的行为可能会导致过量的负载。
H3_ID_ERROR (0x108): 错误使用 Stream ID 或 Push ID,比如超过了限制,减小了限制,或者重用
H3_SETTINGS_ERROR (0x109): 终端在 SETTNGS 帧的载荷中检测到了错误
H3_MISSING_SETTINGS (0x10a): 在控制流开始的时候没有收到 SETTING 帧
H3_REQUEST_REJECTED (0x10b): 服务端拒绝了请求,并不会交由应用处理。
H3_REQUEST_CANCELLED (0x10c): 请求或它的响应(包括推送的响应)被取消
H3_REQUEST_INCOMPLETE (0x10d): 客户端请求不完整,流被终止
H3_MESSAGE_ERROR (0x10e): HTTP 消息格式错误,无法处理。
H3_CONNECT_ERROR (0x10f): 响应 CONNECT 请求而建立的 TCP 连接被重置或粗暴地关闭了
H3_VERSION_FALLBACK (0x110): 无法在 HTTP/3 上提供请求的操作,对端应当在 HTTP/1.1 上重试。
保留了”0x1f * N + 0x21”格式的错误码(N 是非负整数)来实现这样的需求:将未知的错误码等同于 H3_NO_ERROR(第 9 章)。
当他们需要发送 H3_NO_ERROR 错误码的时候,实现应当(SHOULD)以一定概率从这个空间中选择一个错误码
9. Extensions to HTTP/3
HTTP/3 允许扩展协议。在本节描述的限制范围内,协议扩展可以提供附加的服务或者改变协议的各方面。扩展只在单个 HTTP/3 connection 中生效
扩展适用于本文档中定义的协议元素。对已存在的 HTTP 扩展选项不生效,如定义新的 methods,status code,或者 header fields
扩展允许使用新的 frame type(4.2 节),新的 settings(4.2.5.1 节),新的 error codes(8 节),或者新的单向 stream types(3.2 节)。登记系统用来管理这些扩展:frame types (Section 10.3)、settings (Section 10.4)、error codes (Section 10.5) 和 stream types (Section 10.6).
实现必须忽略未知的或者不支持的值。实现必须丢弃未知的或者不支持的 frames 和单向 streams。这意味着任何扩展点都能被安全的使用,而不用提前准备或协商。然而,一个已知的帧类型应该在什么位置,比如 SETTINGS 帧作为控制帧的第一帧,位置帧类型不满足这个要求,并应当(SHOULD)被当成错误。
改变已有协议语义的扩展必须在使用前进行协商。举个例子,改变 HEADERS frame 结构的扩展在对端发出接受信号前不能被使用。调整修订后设计的生效时间是复杂的,因此为已有协议元素的新定义申请新的标识可能更高效。
本文档没有授权特定的 method 用来协商扩展,但是 setting(4.2.5.1)可以用来实现这个目的。如果双方都设置了愿意使用,这个扩展就能被使用。如果 setting 用来做扩展协商,setting 中省略的扩展默认都是不开启的。
10. Security Considerations
HTTP/3 的安全注意事项需要和 HTTP/2 over TLS 的安全注意事项兼容。然而,[HTTP2] 第 10 章的很多注意事项适用于 [QUIC-TRANSPORT] ,且在 [QUIC-TRANSPORT] 有所讨论。
10.1. Server Authority
HTTP/3 依赖于 HTTP 的权限定义。[SEMANTICS] 的第 17.1 节讨论了建立授权的安全注意事项。
10.2. Cross-Protocol Attacks
在 TLS 和 QUIC 握手时使用的 ALPN,在应用层数据被处理前建立了目标应用协议,
使终端能够保证对端使用相同的协议。
这并不能保证对所有跨协议攻击的保护。QUIC-TRANSPORT] 第 21.5 节描述了一些利用 QUIC 数据包的明文对不使用认证传输的终端进行请求伪造的方法。
10.3. Intermediary Encapsulation Attacks
HTTP/3 字段编码允许了那些在 HTTP 中语法无效的字段名称([SEMANTICS] 第 5.1 节)。
必须(MUST)将包含无效字段名的请求或响应当成异常(4.1.3 小节)。因此中间媒介不能
将带无效字段名的 HTTP/3 请求或响应转换成 HTTP/1.1 消息。
同样地,HTTP/3 可以传输无效的字段值。虽然大多数可以被编码的值不会改变字段解析,
但是如果 CR、LF、NUL 字符是一字不差的转换的,攻击者可能利用这一点。
任何在字段值中包含不允许字符的请求或响应必须(MUST)被当成异常(4.1.3 小节)。
[SEMANTICS] 第 5.5 节的“field-content” ABNF 规则定义了有效的字符。
10.4. Cacheability of Pushed Responses
推送响应中没有显示的客户端请求;请求在服务端的 PUSH_PROMISE 帧中。
缓存推送响应可能基于源服务端的 Cache-Control 头部字段
但是,如果单个服务主机有多个租户,就会导致问题。
比如,一个服务端在它的 URI 空间上为多个用户都提供一小块空间。
如果同一个服务端上,多个租户共享了空间,这个服务端必须(MUST)保证租户不能推送它们没有权限的资源表示。
如果不能做到这点,会导致某个租户提供一个引起缓存爆满的表示,从而覆盖了真正有权限的租户提供的实际表示。
客户端需要拒绝推送不可信的源服务器的响应(见 4.4 节)。
10.5. Denial-of-Service Considerations
相对于 HTTP/1.1 或 HTTP/2 连接,HTTP/3 连接可以要求更多的资源来进行操作。
字段压缩和流控制都依赖了更多的资源来存储更多的状态。这些特性的配置保证了它们使用的内存是具有严格边界的。
PUSH_PROMISE 帧的数量也受到了类似方法的限制。接收服务端推送的客户端应当(SHOULD)限制它一次性发布 Push ID 的数量。
无法保证处理容量像状态容量一样被有效限制。
滥发未定义协议元素(对端会忽略)会导致对端花费额外的处理时间。
设置多个未定义的 SETTINGS 参数、未知帧类型、未知流类型实现,可以导致这个问题。
而且有些使用方法还是完全合法的,比如可选扩展、抵御流量分析的填充。
字段区的压缩同样会浪费处理资源;(潜在的滥用情况详见 [QPACK] 第 7 章)
所有这些特性,比如服务端推送、未知协议元素、字段压缩,都有合法的使用场景。
只有在不必要或者超量使用的时候,这些特征才会额外带来负担。
不监控这些行为的终端具有受 DOS 攻击的风险。实现应当(SHOULD)跟踪并限制这些特性的使用。
终端可以(MAY)将可疑的活动当成 H3_EXCESSIVE_LOAD 类型的连接错误(第 8 章),但是误判就会导致打断有效的连接和请求。
10.5.1. Limits on Field Section Size
一个大字段区(第 4.1 节)会导致实现消耗大量状态。对路由敏感的头部字段可以出现在头部字段区域的尾部,这阻止了头部字段区流向它最终的目标。
这种排序以及其他原因,比如保证缓存的正确性,意味着终端可能需要缓存整个头部字段区。
因为没有针对字段区域的硬性限制,一些终端可能被迫为头部字段消耗大量内存。
终端可以使用 SETTINGS_MAX_FIELD_SECTION_SIZE(第 4.1.1.3 小节)配置来建议对端限制字段区域的大小。
这个配置只是建议性的,因此终端可以(MAY)发送超过这个限制的字段区,但是面临着请求或响应被当成异常的风险。
这个配置适用于一个连接,因此任何请求或响应都可能碰到一个具有更低未知限制的路由节点。
一个中间媒介可以通过传递不同对端展示的值来避免这个问题,但是他们没有这么做的义务。
如果一个服务端收到的字段区比它愿意处理的尺寸更大,可以发送一个 HTTP 431(请求头部字段过大)状态码 [RFC6585]。
客户端可以丢弃它无法处理的响应。
10.5.2. CONNECT Issues
因为创建流的消耗相对 TCP 连接的创建和维护来说,开销并不高昂,因此使用 CONNECT 方法,可以在一个代理上创建不成比例的负载。因此,支持 CONNECT 的代理在接受的并发请求数上可能更为保守。
由于外部的 TCP 连接停留在 TIME_WAIT 状态,代理在关闭传输 CONNECT 消息的流之后,还可以维护 TCP 连接的一些资源。考虑到这一点,代理可能会在 TCP 连接终止后的一段时间内延迟增加 QUIC 流限制。
10.6. Use of Compression
如果数据压缩的上下文被攻击者破解,攻击者可以恢复加密后的数据。
HTTP/3 可以进行字段的压缩(4.1.1 小节);
以下注意点同样适用于压缩后的 HTTP content-codings;(详见 [SEMANTICS] 第 8.4.1 小节)
有一些攻击明确针对了暴露 web 特性的压缩(比如 [BREACH] 攻击)。
攻击者诱发了多个包含不同文本的请求,再观察每个请求的秘文长度,如果猜对了密钥,那么秘文长度就更短。
在一个安全通道上进行通信的实现必须不能(MUST NOT)压缩那些既包含了机密数据又包含了受攻击者控制数据的内容,
除非为每种数据源提供了隔离的压缩上下文(compression contexts)。
如果不能决定数据源的可靠性,必须不能(MUST NOT)对数据进行压缩。
[QPACK] 描述了更多关于字段区压缩的注意事项。
10.7. Padding and Traffic Analysis
可以使用填充来掩盖帧的实际大小,减少收到针对 HTTP 的特定攻击的风险,
比如,包括受攻击者控制的明文和加密数据的压缩内容受攻击的场景。(比如 [BREACH])
HTTP/2 在另外帧中采用了 PADDING 帧和 Padding 字段来抵御流量分析,
而 HTTP/3 既可以依赖传输层填充,也可以采用保留帧(7.2.8 小节)和流类型(6.2.3 小节)。
根据填充间隔、填充与受保护信息的安排关系、丢包时是否采用填充、实现如何控制填充,这些填充方法会产生不同的结果。
保留的流类型,可以用来给人一种“即使在连接空闲时也在发送流量”的感觉。
因为 HTTP 流量经常以突发方式发生,所以可以使用明显的流量来掩盖此类突发的时间或持续时间,甚至达到似乎在发送恒定数据流的地步。
然而,由于这种流量仍然受到接收方的流量控制,如果不能及时排空这种流并提供额外的流量控制信用,就会限制发送者发送真实流量的能力。
为了减少针对压缩的攻击,相对于填充,禁用或限制压缩可能是更好的对策。
使用填充的保护效果可能没有想象中那样立竿见影。多余的填充甚至可能适得其反。
填充最多只能增大攻击者观察的数据量,从而让攻击者更加难以推断出长度信息。
错误的填充方案很容易就会被破解。实际上,使用可预测分布的随机填充的保护强度很低;
如果将载荷填充为固定大小,载荷大小跨越固定尺寸边界的时候,同样泄露了信息。
如果攻击者可以控制明文的话,它就可能做到这个。
10.8. Frame Parsing
一些协议元素包含嵌套的长度元素,通常以具有显式长度的帧的形式包含可变长度的整数。
这可能会给不谨慎的实现者带来安全风险。实现者必须(MUST)确保帧的长度与其包含的字段的长度完全匹配。
10.9. Early Data
在 HTTP/3 中使用 0-RTT 给重放攻击提供了契机。
因此和 HTTP/3 一起使用 0-RTT 时,必须(MUST)使用 [HTTP-REPLAY] 中的反重放缓解方法。
当将 [HTTP-REPLAY] 应用于 HTTP/3 时,对 TLS 层的引用指的是在 QUIC 内执行的握手,而对应用数据的所有引用指的是流的内容。
10.10. Migration
特定的 HTTP 实现使用客户端地址来记录日志或者访问控制。
由于 QUIC 客户端的地址可能在连接生命周期内改变(并且未来版本可能支持同时使用多个地址),
因此如果这样的实现明确或显式接受源地址可能改变,它们就需要动态检索客户端当前的地址。
10.11. Privacy Considerations
HTTP/3 的几个特性使观察者可将将单个客户端或服务端的多个动作关联起来。
这些包括了配置的值,刺激反馈的定时,以及所有受配置控制的特性的处理。
只要这些特性在行为上产生了巨大的差异,就可以使用它们来作为识别一个指定客户端的基础。
HTTP/3 使用单 QUIC 连接的特性,使用户在一个站点上的行为可以关联起来。
不同的源复用连接允许了这些源之间活动的关联性。
QUIC 的一些特性要求立刻响应,并可以用来测量到它们对端的延迟;
在某些特定场景下,这个特性可能存在隐私问题。