QUIC 传输协议具有 HTTP 传输所需的几个特性,例如stream multiplexing、每个流的流控制和低延迟连接建立。本文档描述了 HTTP 语义在 QUIC 上的映射。本文还介绍了 QUIC 包含的 HTTP/2 功能,并描述了如何将 HTTP/2 扩展移植到 HTTP/3。
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 连接来并行处理请求。然而,这对拥塞控制(congestion control)和网络效率有负面影响,因为 TCP 不跨多个连接共享拥塞控制。
HTTP/2 引入了一个二进制帧和多路复用层,在不修改传输层的情况下改善了延迟。然而,由于HTTP/2的多路复用的并行特性对TCP的丢失恢复机制是不可见的,因此丢失或重新排序的数据包会导致所有活动事务都经历停顿,无论该事务是否直接受到丢失数据包的影响。
1.2 委托给 QUIC
QUIC 传输协议结合了stream multiplexing和每个流的流控制,类似于HTTP/2帧层提供的。通过提供流级别的可靠性和整个连接的拥塞控制,与TCP映射相比,QUIC有能力提高HTTP的性能。QUIC还在传输层合并了TLS 1.3 ([TLS]),提供了与在TCP上运行TLS相当的机密性和完整性,并改善了TCP快速打开([TFO])的连接设置延迟。
本文定义了 HTTP/3:HTTP 语义在 QUIC 传输协议上的映射,大量借鉴了 HTTP/2 的设计。HTTP/3 依赖于 QUIC 来提供数据的机密性和完整性保护;对等认证;可靠、有序、按流交付。在将流生命周期和流控制问题委托给 QUIC 时,每个流都使用类似于 HTTP/2 帧的二进制帧。QUIC 包含一些 HTTP/2 功能,而其他功能则在 QUIC 之上实现。
HTTP/3使用QUIC传输协议和类似于HTTP/2的内部帧层为HTTP语义提供了传输。
一旦客户端知道某个终端存在 HTTP/3 服务器,它就会打开一个 QUIC 连接。QUIC 提供协议协商、基于流的多路复用和流控制。
在每个流中,HTTP/3 通信的基本单位是一个帧。每种帧类型都有不同的用途。例如,HEADERS 和 DATA 帧构成了 HTTP 请求和响应的基础)。适用于整个连接的帧在专用控制流上传输。
请求的多路复用是使用QUIC流抽象来执行的,这在[QUIC- transport]的第2节中描述。每个请求-响应对使用一个QUIC流。流之间是相互独立的,所以一个流被阻止或丢失不会影响其他流的进程。
服务器推送是HTTP/2中引入的一种交互方案,它允许服务器在客户端发出指定请求之前向客户端推送一个请求-响应交换。这就权衡了网络使用和潜在的延迟增益。一些HTTP/3帧被用来管理服务器推送,例如PUSH_PROMISE, MAX_PUSH_ID和CANCEL_PUSH。
与 HTTP/2 一样,请求和响应字段被压缩以进行传输。因为 HPACK ([HPACK]) 依赖于压缩字段部分的顺序传输(QUIC 不提供保证),所以 HTTP/3 用 QPACK ([QPACK]) 替换了 HPACK。QPACK 使用单独的单向流来修改和跟踪字段表状态,而编码字段部分引用表的状态而不修改它。
3.1 发现HTTP/3终端
HTTP依赖权威的概念响应:在给定目标资源在响应消息由源服务器发起(或在其方向)时的状态的情况下,该响应已被确定为对该请求最合适的响应在目标 URI 中标识。
“https”方案将授权与拥有证书相关联,客户端认为该证书对于由 URI 的授权组件标识的主机是可信赖的。在 TLS 握手中接收到服务器证书后,客户端必须验证证书是否与URI的源服务器匹配。如果证书无法针对 URI 的源服务器进行验证,则客户端不得认为服务器对该源具有权威性。
客户端可以尝试通过将主机标识符解析为 IP 地址来尝试访问具有“https”URI 的资源,在指定端口上建立到该地址的 QUIC 连接(包括如上所述的服务器证书验证),并通过该安全连接向服务器发送以URI为目标的HTTP/3请求消息。除非使用其他机制来选择HTTP/3,否则在 TLS 握手期间在应用层协议协商扩展中使用令牌“h3”。
连接问题(例如,阻止UDP)可能导致无法建立一个QUIC连接;在这种情况下,客户端应该尝试使用基于 TCP 的 HTTP 版本。
服务器可以在任何 UDP 端口上提供 HTTP/3;替代服务广告始终包含明确地端口,并且 URI 包含与方案关联的明确地端口或默认端口。
3.1.1 HTTP替代服务
HTTP 源可以使用“h3”ALPN 令牌通过 Alt-Svc HTTP 响应头字段或 HTTP/2 ALTSVC 帧 ([ALTSVC]) 通告等效 HTTP/3 终端的可用性。
例如,在HTTP响应中,通过包含以下标头字段,可以表明HTTP/3在UDP端口50781上相同主机名是可用的:
Alt-Svc: h3=":50781"
在接收到表示HTTP/3支持的Alt-Svc记录时,客户端可以尝试建立到指定主机和端口的QUIC连接;如果连接成功,客户端可以使用本文档中描述的映射发送HTTP请求。
3.1.2 其他方案
尽管HTTP独立于传输协议,但“HTTP”方案将权限与在权限组件中标识的任何主机的指定端口上接收TCP连接的能力相关联。由于HTTP/3不使用TCP, 所以HTTP/3不能用于直接访问由“HTTP”URI标识的资源的权威服务器。然而,诸如[ALTSVC]这样的协议扩展允许授权服务器识别其他同样具有授权且可能通过HTTP/3可访问的服务。
当向方案不是“https”的源发起请求之前,客户端必须确保服务器愿意为该方案提供服务。对于方案为“http”的源,我们会在之后介绍实现此目的的实验方法。
3.2 建立连接
HTTP/3依赖于QUIC版本1作为底层传输,未来的规范可能会定义使用 HTTP/3 的其他 QUIC 传输版本。
QUIC 版本 1 使用 TLS 版本 1.3 或更高版本作为其握手协议。HTTP/3 客户端必须支持在 TLS 握手期间向服务器指示目标主机的机制。如果服务器由域名 ([DNS-TERMS]) 标识,则客户端必须发送服务器名称指示 (SNI; [RFC6066]) TLS 扩展,除非有另一种机制来指示使用的目标主机。
在建立连接期间,通过在TLS握手中选择ALPN令牌“h3”来表示对HTTP/3的支持。对其他应用层协议的支持可以在相同的握手中提供。
虽然与核心 QUIC 协议有关的连接级别选项是在初始加密握手中设置的,但特定于 HTTP/3 的设置在 SETTINGS 帧中传递。在建立了QUIC连接之后,每个终端必须发送一个SETTINGS帧作为它们各自的HTTP控制流的初始帧。
3.3 连接重用
HTTP/3连接是跨多个请求的持久连接。为了获得最佳性能,客户端在确定不再需要与服务器进行进一步通信之前(例如,当用户导航离开某个特定的网页时)不会关闭连接,或者直到服务器关闭连接。
一旦存在到服务器终端的连接,该连接可以被重用于具有多个不同 URI 权限组件的请求。要使用一个现有的连接到一个新的源,客户端必须验证服务器为新的源服务器提供的证书。这意味着客户端将需要保留服务器证书和验证该证书所需的任何额外信息,否则客户端将无法为其他源重用连接。
如果证书由于任何原因对于新源不可接受,则不得重用连接,并且应该为新源建立新连接。如果证书无法验证的原因可能适用于已经与连接关联的其他来源,客户端应该重新验证这些来源的服务器证书。例如,如果由于证书已过期或被吊销而导致证书验证失败,则这可能用于使该证书用于建立权限的所有其他来源无效。
客户端不应打开多个到给定 IP 地址和 UDP 端口的 HTTP/3 连接,其中 IP 地址和端口可能来自 URI、选定的替代服务 ([ALTSVC])、配置的代理或名称解析其中任何一个。客户端可以使用不同的传输或 TLS 配置打开到相同 IP 地址和 UDP 端口的多个 HTTP/3 连接,但应避免使用相同配置创建多个连接。
服务器应尽可能长时间地保持打开的 HTTP/3 连接,但在必要时允许终止空闲连接。当任何一个终端选择关闭HTTP/3连接时,终止的终端应该首先发送一个 GOAWAY 帧,以便两个终端能够可靠地确定之前发送的帧是否已经被处理并完成或终止任何必要的剩余任务。
如果服务器不希望客户端重用HTTP/3连接,它可以发送一个421(错误定向请求)状状态代码来响应请求来表明它对请求不具有权威性。
4.1 HTTP 消息帧
客户端在请求流上发送 HTTP 请求,请求流是客户端发起的双向 QUIC 流。客户端必须在给定的流上只发送一个请求。服务器在与请求相同的流上发送零个或多个临时HTTP响应,然后是一个最终HTTP响应,详细信息如下。
推送响应在服务器发起的单向QUIC流上发送,服务器以与标准响应相同的方式发送零个或多个临时HTTP响应,然后是一个最终HTTP响应。在给定的流中,收到多个请求或在最终 HTTP 响应之后收到额外的 HTTP 响应必须被视为格式错误。
一个HTTP消息(请求或响应)包括:
标头部分,包括消息控制数据,作为单个标头帧发送;
可选地,如果内容存在,则以一系列数据帧的形式发送;
可选地,尾部部分(如果存在)作为单个 HEADERS 帧发送。
接收到无效的帧序列必须被视为 H3_FRAME_UNEXPECTED 类型的连接错误。特别是,任何 HEADERS 帧之前的 DATA 帧,或尾部 HEADERS 帧之后的 HEADERS 或 DATA 帧,都被认为是无效的。其他帧类型,尤其是未知帧类型,可能会根据它们自己的规则被允许。
服务器可以在响应消息的帧之前、之后或与响应消息的帧交错发送一个或多个 PUSH_PROMISE 帧。这些 PUSH_PROMISE 帧不是响应的一部分。push stream上不允许使用 PUSH_PROMISE 帧;包含 PUSH_PROMISE 帧的推送响应必须被视为 H3_FRAME_UNEXPECTED 类型的连接错误。
HEADERS 和 PUSH_PROMISE 帧可能会引用对 QPACK 动态表的更新。虽然这些更新不是消息交换的直接部分,但必须先接收和处理它们,然后才能使用消息。
传输编码没有为HTTP/3定义;Transfer-Encoding标头字段绝对不能被使用。
当且仅当一个或多个临时响应在对同一请求的最终响应之前,响应可能包含多个消息。临时响应不包含内容或预告片部分。
HTTP 请求/响应交换完全使用客户端发起的双向 QUIC 流。发送请求后,客户端必须关闭要发送的流。除非使用 CONNECT 方法,否则客户端不得依赖于接收到对其请求的响应来进行流关闭。发送最终响应后,服务器必须关闭要发送的流。此时,QUIC 流已完全关闭。
当流关闭时,这表示最终HTTP消息的结束。因为有些消息很大或没有绑定,所以一旦接收到足够的消息以进行处理,终端就应该开始处理部分HTTP消息。如果客户端发起的流终止时没有足够的HTTP消息来提供完整的响应,服务器应该以错误码H3_REQUEST_INCOMPLETE终止其响应流。
如果响应不依赖于尚未发送和接收的请求的任何部分,服务器可以在客户端发送整个请求之前发送一个完整的响应。当服务器不需要接收请求的剩余部分时,它可以中止读取请求流,发送一个完整的响应,并关闭流的发送部分。当请求客户端停止发送请求流时,应该使用错误码H3_NO_ERROR。客户绝对不能因为他们的请求被突然终止而删除完整的回复,尽管客户总是可以根据自己的判断删除其他原因的回复。如果服务器发送了部分或完整的响应,但没有终止读取请求,客户端应该继续发送请求的内容,并正常关闭流。
4.1.1 取消和拒绝请求
一旦请求流被打开,请求可以被任何一个终端取消。如果对响应不再感兴趣,客户端会取消请求;如果服务器无法或选择不响应请求,则会取消请求。如果可能,建议服务器发送一个带有适当状态码的HTTP响应,而不是取消它已经开始处理的请求。
实现应该通过突然终止流中仍然打开的任何方向来取消请求。为此,实现重置流的发送部分,并中止流的接收部分的读取。
当服务器取消请求而不执行任何应用程序处理时,该请求被认为是“被拒绝”的。服务器应该中止包含错误代码H3_REQUEST_REJECTED的响应流。在这种情况下,“已处理”意味着流中的一些数据被传递到软件的较高层,该软件可能因此采取了一些操作。客户端可以将被服务器拒绝的请求当作从未发送过,从而允许它们稍后重试。
对于部分或全部处理的请求,服务器不得使用 H3_REQUEST_REJECTED 错误代码。当服务器在部分处理后放弃响应时,它应该以错误代码 H3_REQUEST_CANCELLED 中止其响应流。
客户端应该使用错误代码H3_REQUEST_CANCELLED来取消请求。在接收到此错误码后,如果没有执行任何处理,服务器可能会使用错误码H3_REQUEST_REJECTED突然终止响应。客户端绝对不能使用H3_REQUEST_REJECTED错误码,除非服务端使用此错误码请求关闭请求流。
如果在收到完整响应后取消流,客户端可以忽略取消并使用响应。但是,如果在收到部分响应后取消流,则不应使用响应。只有 GET、PUT 或 DELETE 等幂等操作可以安全地重试;客户端不应该使用非幂等方法自动重试请求,除非它有某种方法可以知道请求语义是独立于方法的幂等,或者有某种方法可以检测到原始请求从未被应用。
4.1.2 格式错误的请求和响应
格式错误的请求或响应是一个有效的帧序列,但由于以下原因而无效:、禁止字段或伪标头字段的存在;
没有强制性的伪标头字段;
伪标头字段的无效值;
字段后的伪标头字段;
无效的 HTTP 消息序列;
包含大写字段名称;
在字段名称或值中包含无效字符。
如果一个请求或响应被定义为包含content - length标头字段,如果content - length标头字段的值不等于接收到的数据帧长度之和,则该请求或响应是不规范的。一个被定义为永远没有内容的响应,即使有content - length,也可以有一个非零的content - length标头字段,即使数据帧中没有包含任何内容。
处理 HTTP 请求或响应的中介(即任何不充当隧道的中介)不得转发格式错误的请求或响应。检测到的格式错误的请求或响应必须被视为 H3_MESSAGE_ERROR 类型的流错误。
对于格式错误的请求,服务器可以在关闭或重置流之前发送一个指示错误的 HTTP 响应。客户端不得接受格式错误的响应。请注意,这些要求旨在防止针对 HTTP 的几种常见攻击;它们是故意严格的,因为允许可能暴露这些漏洞的实现。
4.2 HTTP字段
HTTP消息以一系列项值对的形式携带元数据,称为“HTTP字段”。与HTTP/2类似,HTTP/3还有一些额外的注意事项,包括字段名、连接标头字段和伪标头字段中的字符的使用。
字段名是包含ASCII字符子集的字符串。字段名中的字符必须在编码之前转换为小写。字段名称中包含大写字符的请求或响应必须被视为格式不正确。
HTTP/3 不使用 Connection 头字段来指示特定于连接的字段;在此协议中,特定于连接的元数据通过其他方式传送。终端绝对不能生成包含连接特定字段的HTTP/3字段部分;任何包含连接特定字段的消息都必须被视为格式错误。
唯一的例外是TE标头字段,它可能出现在HTTP/3请求标头中;如果是,它不能包含除“trailers”之外的任何值。
将 HTTP/1.x 消息转换为 HTTP/3 的中介必须删除连接特定的标头字段,否则它们的消息将被其他 HTTP/3 终端视为格式错误。
4.2.1 字段压缩(field compression)
[QPACK]描述了HPACK的一个变体,它使编码器能够控制由压缩引起的标头阻止的数量。这允许编码器平衡压缩效率和延迟。HTTP/3 使用 QPACK 来压缩头部和尾部部分,包括出现在标头部分的控制数据。
为了提高压缩效率,在压缩之前,Cookie 标头字段 ([COOKIES])可以拆分为单独的字段行,每行包含一个或多个 cookie 对。如果一个解压缩的字段部分包含多个cookie字段行,则在传递到 HTTP/2 或 HTTP/3 以外的上下文之前,必须使用“;”的两字节分隔符(ASCII 0x3b,0x20)将它们连接成单个字节字符串,例如 HTTP/1.1 连接,或通用 HTTP 服务器应用程序。
4.2.2 标头大小限制
HTTP/3实现可能会对单个HTTP消息接受的消息标头的最大大小施加限制。如果服务器接收到的标头区域比它愿意处理的更大,它可以发送一个HTTP 431(请求标头字段太大)状态码([RFC6585])。客户端可以删除它无法处理的响应。字段列表的大小是根据未压缩的字段大小计算的,包括名称和值的长度(以字节为单位),外加每个字段的32字节开销。
如果一个实现希望将此限制通知其对等方,则可以在 SETTINGS_MAX_FIELD_SECTION_SIZE 参数中将其作为字节数传送。接收到此参数的实现不应该发送超过指示大小的 HTTP 消息标头,因为对等方可能会拒绝处理它。但是,HTTP 消息在到达源服务器之前可以经过一个或多个中介。因为这个限制是由处理消息的每个实现单独应用的,所以不能保证低于这个限制的消息被接受。
4.3. HTTP控制数据
与HTTP/2类似,HTTP/3使用了一系列伪标头字段,其中字段名以字符(ASCII 0x3a)开头。这些伪标头字段传递消息控制数据。
伪标头字段不是HTTP字段。终端绝对不能生成除本文档中定义的那些以外的伪标头字段。然而,延期可以协商修改此限制。
伪标头字段仅在定义它们的上下文中有效。为请求定义的伪标头字段绝对不能出现在响应中;为响应定义的伪标头字段绝对不能出现在请求中。伪标头字段绝对不能出现在尾部部分。终端必须将包含未定义或无效伪标头字段的请求或响应视为格式错误。
所有的伪标头字段必须出现在普通标头字段之前的标头部分。任何包含出现在普通标头字段之后的标头部分中的伪标头字段的请求或响应都必须被视为格式错误。
4.3.1. 请求伪标头字段
为请求定义了以下伪标头字段:
":method":包含HTTP方法;
":scheme":包含目标URI的方案部分。
:scheme伪标头不局限于具有“http”和“https”方案的URI。代理或网关可以转换非HTTP方案的请求,使 HTTP 能够与非 HTTP 服务进行交互。
":authority":包含目标URI的权限部分,权限不得包含方案“http”或“https”的 URI 的已弃用 userinfo 子组件。
为确保 HTTP/1.1 请求行可以准确再现,当从具有特定方法形式的请求目标的 HTTP/1.1 请求转换时,必须省略此伪标头字段。直接生成 HTTP/3 请求的客户端应该使用 :authority 伪标头字段而不是 Host 标头字段。如果请求中没有Host字段,则将 HTTP/3 请求转换为 HTTP/1.1 的中介必须通过复制 :authority 伪标头字段的值来创建Host字段。
":path":包含目标URI的路径和查询部分后跟“查询”结果。
对于“http”或“https”URI,此伪头字段不得为空;不包含路径组件的“http”或“https”URI 必须包含值 /(ASCII 0x2f)。不包含路径组件的 OPTIONS 请求包含 :path 伪标头字段的值 * (ASCII 0x2a)。
所有 HTTP/3 请求必须只包含一个 :method、:scheme 和 :path 伪标头字段的值,除非请求是 CONNECT 请求。
如果 :scheme 伪标头字段标识了具有强制权限组件(包括“http”和“https”)的方案,则请求必须包含 :authority 伪标头字段或 Host 标头字段。如果这些字段存在,则它们不得为空。如果两个字段都存在,它们必须包含相同的值。如果该方案没有强制授权组件并且在请求目标中没有提供任何内容,则请求不得包含 :authority 伪标头或 Host 标头字段。
如果一个HTTP请求省略了强制的伪标头字段或者包含了这些伪标头字段的无效值,那么这个请求就是不规范的。
HTTP/3没有定义携带HTTP/1.1请求行中包含的版本标识符的方法。HTTP/3请求隐含的协议版本是“3.0”。
4.3.2. 响应伪标头字段
对于响应,定义了一个带有 HTTP 状态代码的“:status”伪标头字段。这个伪标头字段必须包含在所有响应中,否则,响应格式错误。
HTTP/3没有定义一种方式来携带包含在HTTP/1.1状态行中的版本或原因短语。HTTP/3响应隐含的协议版本是“3.0”。
4.4. 连接方法
CONNECT 方法请求接收方建立一条到请求目标标识的目标源服务器的隧道,它主要与 HTTP 代理一起使用,以与源服务器建立 TLS 会话,以便与“https”资源进行交互。
在 HTTP/1.x 中,CONNECT 用于将整个 HTTP 连接转换为到远程主机的隧道。在 HTTP/2 和 HTTP/3 中,CONNECT 方法用于在单个流上建立隧道。
CONNECT请求构造如下:
:method 伪标头字段设置为“CONNECT”;
:scheme 和 :path 伪标头字段被省略;
:authority 伪标头字段包含要连接的主机和端口,相当于 CONNECT 请求的请求目标的权限形式。
请求流在请求结束时保持打开状态,以携带要传输的数据。不符合这些限制的CONNECT请求是不正确的。
支持 CONNECT 的代理与 :authority 伪标头字段中标识的服务器建立 TCP 连接([RFC0793])。一旦成功建立此连接,代理就会向客户端发送一个包含 2xx 系列状态代码的 HEADERS 帧。
流上的所有数据帧都对应于TCP连接上发送或接收的数据。客户端发送的任何数据帧的有效载荷都由代理传输给TCP服务器,从TCP服务器接收到的数据被代理打包成数据帧。请注意,不能保证 TCP 段的大小和数量可预测地映射到 HTTP DATA 或 QUIC STREAM 帧的大小和数量。
CONNECT方法完成后,就只允许在流上发送数据帧。如果扩展的定义特别允许,则可以使用扩展帧。任何其他已知帧类型的接收必须被视为 H3_FRAME_UNEXPECTED 类型的连接错误。
TCP连接可以被任何一个对等方关闭。当客户端结束请求流(即代理接收流进入“Data Recvd”状态)时,代理将设置其与TCP服务器连接的FIN位。当代理接收到一个设置了 FIN 位的数据包时,它将关闭它发送给客户端的发送流。在单个方向上保持半关闭状态的 TCP 连接并非无效,但服务器通常处理不当,因此客户端不应在仍期望从 CONNECT 目标接收数据时关闭用于发送的流。
通过突然终止流来表示 TCP 连接错误。代理将 TCP 连接中的任何错误(包括接收设置了 RST 位的 TCP 段)视为 H3_CONNECT_ERROR 类型的流错误。
相应地,如果代理检测到流或 QUIC 连接错误,它必须关闭 TCP 连接。如果代理检测到客户端已重置流或中止从流中读取,它必须关闭 TCP 连接。如果流被重置或读取被客户端中止,代理应该在另一个方向上执行相同的操作,以确保流的两个方向都被取消。在所有这些情况下,如果底层 TCP 实现允许,代理应该发送一个设置了 RST 位的 TCP 段。
由于 CONNECT 创建了一个到任意服务器的隧道,支持 CONNECT 的代理应该将其使用限制在一组已知端口或安全请求目标列表。
4.5. HTTP升级
HTTP/3 不支持 HTTP 升级机制或 101(交换协议)信息状态代码。
4.6. 服务器推送
服务器推送是一种交互模式,它允许服务器将请求-响应交换推送到客户端,以等待客户端发出指示的请求。这就平衡了网络使用与潜在的延迟增益。
每个服务器推送都由服务器分配一个唯一的Push ID。Push ID 用于在 HTTP/3 连接的整个生命周期中指代各种上下文中的推送。
Push ID 空间从零开始,以 MAX_PUSH_ID 帧设置的最大值结束。特别是,在客户端发送 MAX_PUSH_ID 帧之前,服务器无法推送。客户端发送 MAX_PUSH_ID 帧来控制服务器可以承诺的推送次数。服务器应该按顺序使用Push ID,从零开始。当没有发送 MAX_PUSH_ID 帧或当流引用大于最大Push ID 的Push ID 时,客户端必须将接收到push stream视为 H3_ID_ERROR 类型的连接错误。
Push ID用于一个或多个PUSH_PROMISE帧,这些帧携带请求消息的控制数据和标头字段。这些帧在生成推送的请求流上发送。这允许服务器推送与客户端请求相关联。当在多个请求流上承诺相同的Push ID时,解压后的请求字段段必须以相同的顺序包含相同的字段,并且每个字段的名称和值必须相同。
然后,Push ID被包含在最终实现这些承诺的push stream中。push stream标识它所履行的承诺的Push ID,然后包含对承诺请求的响应。最后,push ID可以在CANCEL_PUSH帧中使用。客户使用此帧表示他们不希望收到承诺的资源。服务器使用这个帧来表明他们不会履行之前的承诺。
并非所有请求都可以推送。服务器可以推送具有以下属性的请求:
可缓存的;
安全的;
不包括请求内容。
服务器必须在 :authority 伪标头字段中包含一个服务器对其具有权威性的值。如果客户端尚未验证推送请求所指示的源的连接,它必须执行与在连接上发送该源的请求之前相同的验证过程。如果此验证失败,则客户端不得认为服务器对该来源具有权威性。
客户端应该在收到一个 PUSH_PROMISE 帧时发送一个 CANCEL_PUSH 帧,该帧携带的请求是不可缓存的,是不安全的,表示请求内容的存在,或者它不认为服务器具有权威性。不得使用或缓存任何相应的响应。
每个推送的响应都与一个或多个客户端请求相关联。推送与接收到 PUSH_PROMISE 帧的请求流相关联。在多个请求流上使用具有相同Push ID 的 PUSH_PROMISE 帧可以将同一服务器推送与其他客户端请求相关联。这些关联不会影响协议的运行,但用户代理在决定如何使用推送资源时可能会考虑它们。
与响应的某些部分相关的 PUSH_PROMISE 帧的顺序很重要。服务器应该在发送引用承诺响应的 HEADERS 或 DATA 帧之前发送 PUSH_PROMISE 帧。这减少了客户端请求将由服务器推送的资源的可能性。
由于重新排序,push stream数据可以在相应的 PUSH_PROMISE 帧之前到达。当客户端接收到带有未知Push ID 的新push stream时,关联的客户端请求和推送的请求标头字段都是未知的。客户端可以缓存流数据以期待匹配的 PUSH_PROMISE。客户端可以使用stream flow控制来限制服务器提交给被push stream的数据量。如果在合理的时间内没有相应的PUSH_PROMISE帧被处理,客户端应该中止读取并删除已经从push stream中读取的数据。
push stream数据也可以在客户端取消推送后到达。在这种情况下,客户端可以使用错误代码 H3_REQUEST_CANCELLED 中止读取流。这要求服务器不要传输额外的数据,并表明将在接收时删除这些数据。
如果客户端实现了HTTP缓存,可缓存的推送响应可以被存储。在接收到推送的响应时,推送的响应被认为在源服务器上成功验证。
不可缓存的推送响应不得由任何 HTTP 缓存存储。它们可以单独提供给应用程序。
HTTP/3连接一旦建立,就可以在一段时间内用于许多请求和响应,直到连接关闭。连接关闭可以通过几种不同的方式中的任何一种发生。
5.1. 空闲连接
每个QUIC终端在握手期间声明一个空闲GOAWAY。如果超过这个时间,QUIC连接保持空闲(没有收到数据包),对等方会认为连接已经关闭。HTTP/3实现将需要为新的请求打开一个新的HTTP/3连接,如果现有连接的空闲时间超过了在QUIC握手期间协商的空闲GOAWAY时间,他们应该这样做。
HTTP客户端期望请求传输保持连接打开,而请求或服务器推送有未响应。如果客户端不期望从服务器得到响应,那么允许空闲连接GOAWAY比花费精力维护可能不需要的连接要好。网关可以在需要的时候维护连接,而不是产生与服务器建立连接的延迟成本。服务器不应该主动保持连接打开。
5.2. 连接关闭
即使连接不是空闲的,任何一个终端都可以决定停止使用该连接并启动一个正常的连接关闭。终端通过发送 GOAWAY 帧启动 HTTP/3 连接的正常关闭。GOAWAY 帧包含一个标识符,该标识符向接收者指示在此连接中已处理或可能处理的请求或推送的范围。服务器发送客户端发起的双向流ID;客户端发送一个Push ID。GOAWAY 的发送者拒绝具有指定标识符或更大标识符的请求或推送。如果没有处理请求或推送,则此标识符可能为零。
GOAWAY帧中的信息使客户端和服务器在HTTP/3连接关闭之前就哪些请求或推送被接受达成一致。在发送GOAWAY帧时,终端应该明确地取消任何标识符大于或等于指定标识符的请求或推送,以清理受影响流的传输状态。当更多的请求或推送到达时,终端应该继续这样做。
终端在收到对方GOAWAY帧后,绝对不能在连接上发起新的请求或承诺新的推送。客户端可以建立一个新的连接来发送额外的请求。
一些请求或推送可能已经在传输中:
1.在收到 GOAWAY 帧后,如果客户端已经发送了流 ID 大于或等于 GOAWAY 帧中包含的标识符的请求,则不会处理这些请求。客户端可以在不同的 HTTP 连接上安全地重试未处理的请求。当服务器关闭连接时,无法重试请求的客户端会丢失所有正在进行的请求。
来自服务器的 GOAWAY 帧中的流 ID 小于流 ID 的请求可能已被处理,在收到响应、单独重置流、接收到另一个流 ID 低于相关请求的流 ID 的 GOAWAY 或连接终止之前,无法知道它们的状态。
如果未处理这些请求,服务器可能会拒绝低于指定 ID 的流上的单个请求。
2.如果服务器在承诺推送后收到 GOAWAY 帧,其Push ID 大于或等于 GOAWAY 帧中包含的标识符,则这些推送将不被接受。
服务器应该在提前知道连接关闭时发送 GOAWAY 帧,即使提前通知很小,以便远程对等方可以知道请求是否已被部分处理。例如,如果 HTTP 客户端在服务器关闭 QUIC 连接的同时发送 POST,如果服务器没有发送 GOAWAY 帧来指示它可能会发送哪些流,则客户端无法知道服务器是否开始处理该 POST 请求已采取行动。
终端可以发送多个指示不同标识符的 GOAWAY 帧,但每个帧中的标识符不得大于任何先前帧中的标识符,因为客户端可能已经在另一个 HTTP 连接上重试了未处理的请求。接收包含比先前接收到的更大标识符的 GOAWAY 必须被视为 H3_ID_ERROR 类型的连接错误。
尝试正常关闭连接的终端可以发送一个 GOAWAY 帧,其值设置为最大可能值。这可确保对等方停止创建新请求或推送。在允许任何正在进行的请求或推送到达之后,终端可以发送另一个 GOAWAY 帧,表明在连接结束之前它可能接受哪些请求或推送。这确保了可以干净地关闭连接而不会丢失请求。
客户端在为它发送的 GOAWAY 中的 Push ID 字段选择的值方面具有更大的灵活性。262-1表示服务器可以继续完成已经承诺的推送。较小的值表示客户端将拒绝Push ID大于或等于该值的推送。与服务器一样,客户端可以发送后续的 GOAWAY 帧,只要指定的Push ID不大于任何先前发送的值。
即使GOAWAY表示接收到的请求或推送不会被处理或接受,底层传输资源仍然存在。发起这些请求的终端可以取消它们以清除传输状态。
一旦处理了所有接受的请求和推送,终端可以允许连接变为空闲,或者它可以启动连接的立即关闭。完成正常关闭的终端在关闭连接时应该使用 H3_NO_ERROR 错误代码。
如果客户端已经通过请求消耗了所有可用的双向流 ID,则服务器不需要发送 GOAWAY 帧,因为客户端无法发出进一步的请求。
参考及来源:https://www.rfc-editor.org/rfc/rfc9114.html