4. Expressing HTTP Semantics in HTTP/3 (在HTTP/3中表达HTTP语义)
4.1. HTTP Message Framing (HTTP消息帧)
客户端在请求流 (Request Stream) 上发送HTTP请求,该流是客户端发起的双向QUIC流;参见第6.1节。客户端必须 (MUST) 在给定流上仅发送单个请求。服务器在与请求相同的流上发送零个或多个临时HTTP响应 (Interim HTTP Responses),然后是单个最终HTTP响应 (Final HTTP Response),详见下文。有关临时和最终HTTP响应的描述,请参见 [HTTP] 的第15节。
推送响应 (Pushed Responses) 在服务器发起的单向QUIC流上发送;参见第6.2.2节。服务器以与标准响应相同的方式发送零个或多个临时HTTP响应,然后是单个最终HTTP响应。推送在第4.6节中有更详细的描述。
在给定流上,接收多个请求或在最终HTTP响应之后接收额外的HTTP响应必须 (MUST) 被视为格式错误 (Malformed)。
HTTP消息(请求或响应)由以下组成:
-
头部段 (Header Section),包括消息控制数据,作为单个HEADERS帧发送,
-
可选地,内容 (Content)(如果存在),作为一系列DATA帧发送,以及
-
可选地,尾部段 (Trailer Section)(如果存在),作为单个HEADERS帧发送。
头部和尾部段在 [HTTP] 的第6.3节和第6.5节中描述;内容在 [HTTP] 的第6.4节中描述。
接收到无效的帧序列必须 (MUST) 被视为类型为H3_FRAME_UNEXPECTED的连接错误 (Connection Error)。特别是,在任何HEADERS帧之前的DATA帧,或在尾部HEADERS帧之后的HEADERS或DATA帧,都被视为无效。其他帧类型,特别是未知帧类型,可能受其自身规则的约束而被允许;参见第9节。
服务器可以 (MAY) 在响应消息的帧之前、之后或与之交错发送一个或多个PUSH_PROMISE帧。这些PUSH_PROMISE帧不是响应的一部分;有关更多详细信息,请参见第4.6节。PUSH_PROMISE帧不允许在推送流上使用;包含PUSH_PROMISE帧的推送响应必须 (MUST) 被视为类型为H3_FRAME_UNEXPECTED的连接错误。
未知类型的帧(第9节),包括保留帧 (Reserved Frames)(第7.2.8节),可以 (MAY) 在请求或推送流上在本节描述的其他帧之前、之后或与之交错发送。
HEADERS和PUSH_PROMISE帧可能引用对QPACK动态表 (Dynamic Table) 的更新。虽然这些更新不是消息交换的直接部分,但它们必须在消息被消费之前被接收和处理。有关更多详细信息,请参见第4.2节。
传输编码 (Transfer Codings)(参见 [HTTP/1.1] 的第7节)未为HTTP/3定义;禁止 (MUST NOT) 使用Transfer-Encoding头字段。
响应可以 (MAY) 由多个消息组成,当且仅当一个或多个临时响应(1xx;参见 [HTTP] 的第15.2节)在对同一请求的最终响应之前时。临时响应不包含内容或尾部段。
HTTP请求/响应交换完全消耗一个客户端发起的双向QUIC流。在发送请求后,客户端必须 (MUST) 关闭流以进行发送。除非使用CONNECT方法(参见第4.4节),否则客户端禁止 (MUST NOT) 使流关闭依赖于接收对其请求的响应。在发送最终响应后,服务器必须 (MUST) 关闭流以进行发送。此时,QUIC流完全关闭。
当流关闭时,这表示最终HTTP消息的结束。由于某些消息很大或无界,端点应该 (SHOULD) 在收到足够的消息以取得进展后立即开始处理部分HTTP消息。如果客户端发起的流终止而没有足够的HTTP消息来提供完整响应,服务器应该 (SHOULD) 使用错误码H3_REQUEST_INCOMPLETE中止其响应流。
如果响应不依赖于尚未发送和接收的请求的任何部分,服务器可以在客户端发送整个请求之前发送完整响应。当服务器不需要接收请求的其余部分时,它可以 (MAY) 中止读取请求流,发送完整响应,并干净地关闭流的发送部分。在请求客户端停止在请求流上发送时,应该 (SHOULD) 使用错误码H3_NO_ERROR。客户端禁止 (MUST NOT) 因其请求被突然终止而丢弃完整响应,尽管客户端出于其他原因始终可以自行决定丢弃响应。如果服务器发送部分或完整响应但不中止读取请求,客户端应该 (SHOULD) 继续发送请求的内容并正常关闭流。
4.1.1. Request Cancellation and Rejection (请求取消和拒绝)
一旦请求流已打开,任一端点可以 (MAY) 取消请求。如果响应不再感兴趣,客户端取消请求;如果服务器无法或选择不响应,则取消请求。在可能的情况下,建议 (RECOMMENDED) 服务器发送带有适当状态码的HTTP响应,而不是取消已经开始处理的请求。
实现应该 (SHOULD) 通过突然终止仍然打开的流的任何方向来取消请求。为此,实现重置流的发送部分并中止读取流的接收部分;参见 [QUIC-TRANSPORT] 的第2.4节。
当服务器在不执行任何应用程序处理的情况下取消请求时,该请求被视为"被拒绝" (Rejected)。服务器应该 (SHOULD) 使用错误码H3_REQUEST_REJECTED中止其响应流。在此上下文中,"已处理" (Processed) 意味着来自流的某些数据已传递到某些更高层的软件,该软件可能因此采取了某些操作。客户端可以将服务器拒绝的请求视为从未发送过,从而允许它们稍后重试。
服务器禁止 (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) 使用响应。只有幂等操作 (Idempotent Actions),如GET、PUT或DELETE,才能安全地重试;客户端不应该 (SHOULD NOT) 自动重试具有非幂等方法的请求,除非它有某种方法知道请求语义独立于方法是幂等的,或有某种方法检测到原始请求从未应用。有关更多详细信息,请参见 [HTTP] 的第9.2.2节。
4.1.2. Malformed Requests and Responses (格式错误的请求和响应)
格式错误的请求或响应是指在其他方面是有效的帧序列,但由于以下原因而无效:
-
存在禁止的字段或伪头字段 (Pseudo-Header Fields),
-
缺少强制性伪头字段,
-
伪头字段的值无效,
-
伪头字段在字段之后,
-
HTTP消息的序列无效,
-
包含大写字段名称,或
-
字段名称或值中包含无效字符。
当包含Content-Length头字段([HTTP] 的第8.6节)时,定义为具有内容的请求或响应是格式错误的,如果Content-Length头字段的值不等于接收到的DATA帧长度的总和。即使没有内容包含在DATA帧中,定义为从不具有内容的响应(即使存在Content-Length)也可以具有非零Content-Length头字段。
处理HTTP请求或响应的中介(即任何不作为隧道的中介)禁止 (MUST NOT) 转发格式错误的请求或响应。检测到的格式错误的请求或响应必须 (MUST) 被视为类型为H3_MESSAGE_ERROR的流错误 (Stream Error)。
对于格式错误的请求,服务器可以 (MAY) 在关闭或重置流之前发送指示错误的HTTP响应。客户端禁止 (MUST NOT) 接受格式错误的响应。请注意,这些要求旨在防止针对HTTP的几种常见攻击类型;它们是故意严格的,因为宽容可能会使实现暴露于这些漏洞。
4.2. HTTP Fields (HTTP字段)
HTTP消息以一系列称为"HTTP字段" (HTTP Fields) 的键值对形式携带元数据;参见 [HTTP] 的第6.3节和第6.5节。有关已注册HTTP字段的列表,请参见维护在 https://www.iana.org/assignments/http-fields/ 的"超文本传输协议 (HTTP) 字段名称注册表" (Hypertext Transfer Protocol (HTTP) Field Name Registry)。与HTTP/2类似,HTTP/3对字段名称中字符的使用、Connection头字段和伪头字段有额外的考虑。
字段名称是包含ASCII字符子集的字符串。HTTP字段名称和值的属性在 [HTTP] 的第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) 删除特定于连接的头字段,如 [HTTP] 的第7.6.1节所述,否则它们的消息将被其他HTTP/3端点视为格式错误。
4.2.1. Field Compression (字段压缩)
[QPACK] 描述了HPACK的一个变体,它使编码器能够对压缩可能引起多少队头阻塞 (Head-of-Line Blocking) 进行一些控制。这允许编码器在压缩效率和延迟之间取得平衡。HTTP/3使用QPACK来压缩头部和尾部段,包括头部段中存在的控制数据。
为了获得更好的压缩效率,Cookie头字段 ([COOKIES]) 可以 (MAY) 在压缩之前被拆分为单独的字段行,每行包含一个或多个cookie对 (Cookie-Pairs)。如果解压缩的字段段包含多个cookie字段行,则必须 (MUST) 在传递到除HTTP/2或HTTP/3之外的上下文(例如HTTP/1.1连接或通用HTTP服务器应用程序)之前,使用两字节分隔符"; "(ASCII 0x3b, 0x20)将它们连接成单个字节字符串。
4.2.2. Header Size Constraints (头部大小约束)
HTTP/3实现可以 (MAY) 对其在单个HTTP消息上接受的消息头的最大大小施加限制。接收到大于其愿意处理的头部段的服务器可以发送HTTP 431(请求头字段过大)(Request Header Fields Too Large) 状态码 ([RFC6585])。客户端可以丢弃其无法处理的响应。字段列表的大小基于字段的未压缩大小计算,包括名称和值的字节长度,加上每个字段32字节的开销。
如果实现希望将此限制告知其对等方,它可以作为SETTINGS_MAX_FIELD_SECTION_SIZE参数中的字节数传递。已接收到此参数的实现不应该 (SHOULD NOT) 发送超过指示大小的HTTP消息头,因为对等方可能会拒绝处理它。然而,HTTP消息可以在到达源服务器之前经过一个或多个中介;参见 [HTTP] 的第3.7节。由于此限制由处理消息的每个实现单独应用,因此低于此限制的消息不能保证被接受。
4.3. HTTP Control Data (HTTP控制数据)
与HTTP/2类似,HTTP/3采用一系列伪头字段 (Pseudo-Header Fields),其中字段名称以字符:(ASCII 0x3a)开头。这些伪头字段传递消息控制数据;参见 [HTTP] 的第6.2节。
伪头字段不是HTTP字段。端点禁止 (MUST NOT) 生成本文档中定义之外的伪头字段。但是,扩展可以协商对此限制的修改;参见第9节。
伪头字段仅在定义它们的上下文中有效。为请求定义的伪头字段禁止 (MUST NOT) 出现在响应中;为响应定义的伪头字段禁止 (MUST NOT) 出现在请求中。伪头字段禁止 (MUST NOT) 出现在尾部段中。端点必须 (MUST) 将包含未定义或无效伪头字段的请求或响应视为格式错误。
所有伪头字段必须 (MUST) 在头部段中的常规头字段之前出现。任何包含在常规头字段之后的头部段中出现的伪头字段的请求或响应必须 (MUST) 被视为格式错误。
4.3.1. Request Pseudo-Header Fields (请求伪头字段)
为请求定义了以下伪头字段:
":method":包含HTTP方法 (HTTP Method)([HTTP] 的第9节)
":scheme":包含目标URI的scheme部分([URI] 的第3.1节)。
:scheme伪头不限于scheme为"http"和"https"的URI。代理或网关可以转换非HTTP scheme的请求,从而允许使用HTTP与非HTTP服务进行交互。
有关使用除"https"之外的scheme的指导,请参见第3.1.2节。
":authority":包含目标URI的authority部分([URI] 的第3.2节)。对于scheme为"http"或"https"的URI,authority禁止 (MUST NOT) 包含已弃用的userinfo子组件。
为确保可以准确再现HTTP/1.1请求行,在从具有方法特定形式的请求目标的HTTP/1.1请求转换时,必须 (MUST) 省略此伪头字段;参见 [HTTP] 的第7.1节。直接生成HTTP/3请求的客户端应该 (SHOULD) 使用:authority伪头字段而不是Host头字段。将HTTP/3请求转换为HTTP/1.1的中介必须 (MUST) 在请求中不存在Host字段时通过复制:authority伪头字段的值来创建Host字段。
":path":包含目标URI的path和query部分("path-absolute"生产式以及可选的?字符(ASCII 0x3f)后跟"query"生产式;参见 [URI] 的第3.3节和第3.4节)。
对于"http"或"https" URI,此伪头字段禁止 (MUST NOT) 为空;不包含path组件的"http"或"https" URI必须 (MUST) 包含值/(ASCII 0x2f)。不包含path组件的OPTIONS请求包含:path伪头字段的值*(ASCII 0x2a);参见 [HTTP] 的第7.1节。
所有HTTP/3请求必须 (MUST) 恰好包含:method、:scheme和:path伪头字段的一个值,除非请求是CONNECT请求;参见第4.4节。
如果:scheme伪头字段标识具有强制性authority组件(包括"http"和"https")的scheme,则请求必须 (MUST) 包含:authority伪头字段或Host头字段。如果这些字段存在,它们禁止 (MUST NOT) 为空。如果两个字段都存在,它们必须 (MUST) 包含相同的值。如果scheme没有强制性authority组件且请求目标中未提供任何authority组件,则请求禁止 (MUST NOT) 包含:authority伪头或Host头字段。
省略强制性伪头字段或包含这些伪头字段的无效值的HTTP请求是格式错误的。
HTTP/3没有定义携带HTTP/1.1请求行中包含的版本标识符的方法。HTTP/3请求隐式具有协议版本"3.0"。
4.3.2. Response Pseudo-Header Fields (响应伪头字段)
对于响应,定义了单个":status"伪头字段,它携带HTTP状态码;参见 [HTTP] 的第15节。此伪头字段必须 (MUST) 包含在所有响应中;否则,响应是格式错误的(参见第4.1.2节)。
HTTP/3没有定义携带HTTP/1.1状态行中包含的版本或原因短语 (Reason Phrase) 的方法。HTTP/3响应隐式具有协议版本"3.0"。
4.4. The CONNECT Method (CONNECT方法)
CONNECT方法请求接收方建立到请求目标标识的目标源服务器的隧道 (Tunnel);参见 [HTTP] 的第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;参见 [HTTP] 的第7.1节)。
请求流在请求结束时保持打开以承载要传输的数据。不符合这些限制的CONNECT请求是格式错误的。
支持CONNECT的代理建立到:authority伪头字段中标识的服务器的TCP连接 ([RFC0793])。一旦成功建立此连接,代理向客户端发送包含2xx系列状态码的HEADERS帧,如 [HTTP] 的第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位的数据包时,它将关闭发送给客户端的发送流。保持半关闭 (Half-Closed) 在单个方向上的TCP连接并不无效,但通常被服务器处理得很差,因此客户端不应该 (SHOULD NOT) 在仍期望从CONNECT的目标接收数据时关闭用于发送的流。
TCP连接错误通过突然终止流来发出信号。代理将TCP连接中的任何错误(包括接收到设置了RST位的TCP段)视为类型为H3_CONNECT_ERROR的流错误。
相应地,如果代理检测到流或QUIC连接存在错误,它必须 (MUST) 关闭TCP连接。如果代理检测到客户端已重置流或中止从流中读取,它必须 (MUST) 关闭TCP连接。如果流被客户端重置或中止读取,代理应该 (SHOULD) 在另一个方向上执行相同的操作,以确保流的两个方向都被取消。在所有这些情况下,如果底层TCP实现允许,代理应该 (SHOULD) 发送设置了RST位的TCP段。
由于CONNECT创建到任意服务器的隧道,因此支持CONNECT的代理应该 (SHOULD) 将其使用限制为一组已知端口或安全请求目标列表;有关更多详细信息,请参见 [HTTP] 的第9.3.6节。
4.5. HTTP Upgrade (HTTP升级)
HTTP/3不支持HTTP升级机制 (HTTP Upgrade Mechanism)([HTTP] 的第7.8节)或101(切换协议)(Switching Protocols) 信息状态码([HTTP] 的第15.2.2节)。
4.6. Server Push (服务器推送)
服务器推送 (Server Push) 是一种交互模式,它允许服务器在预期客户端发出请求之前向客户端推送请求-响应交换。客户端可以在SETTINGS帧中通过将SETTINGS_ENABLE_PUSH设置为0来禁用服务器推送。服务器不能 (MUST NOT) 向设置了SETTINGS_ENABLE_PUSH为0的客户端发送推送;违反此规定的服务器行为必须被视为类型为H3_SETTINGS_ERROR的连接错误。
与HTTP/2一样,服务器通过在客户端发起的请求流上发送PUSH_PROMISE帧(第7.2.5节)来启动推送。推送ID用于标识服务器推送(参见第4.6.1节)。推送ID在PUSH_PROMISE帧中携带,该帧还包括请求头段,该段段归因于服务器生成的请求,如 [HTTP] 的第15节所述。
服务器从自身发起的推送流(第6.2.2节)发送响应。推送响应的传递方式与对常规请求的响应相同。推送响应的响应头段在第7.2.4节中描述的HEADERS帧中传递。服务器可以通过在推送流上发送带有推送ID的CANCEL_PUSH帧来取消已承诺的推送。
客户端使用MAX_PUSH_ID帧控制服务器可以承诺的推送数量(第7.2.7节)。服务器不能 (MUST NOT) 发送PUSH_PROMISE帧或CANCEL_PUSH帧,其推送ID大于客户端为连接提供的最大推送ID。尝试这样做的客户端必须 (MUST) 将其视为类型为H3_ID_ERROR的连接错误。
一旦推送流已由PUSH_PROMISE帧打开或保留,只要客户端没有取消推送,推送流可以被使用。一旦客户端从控制流接收到CANCEL_PUSH帧或从推送流接收到流终止,推送被取消。如果推送流终止且没有CANCEL_PUSH,则仍认为推送已成功完成。
客户端可以通过发送CANCEL_PUSH帧来中止推送。在服务器收到后,服务器必须 (MUST) 中止发送推送,如果推送尚未完成。客户端也可以通过重置推送流来中止推送。在这两种情况下,接收方可以安全地丢弃任何已接收的推送响应状态。
一旦请求流关闭,实现可以选择仅缓冲对推送响应的引用或完全删除推送响应的引用。如果接收到了推送响应,而相关联的请求流关闭,这并不表示推送失败。
推送流始终通过推送ID引用。PUSH_PROMISE帧的接收方将推送ID与客户端发起的流关联,而接收HEADERS帧的客户端在推送流上将推送ID与接收到的推送进行匹配。
4.6.1. Push IDs (推送ID)
推送ID是62位无符号整数(参见 [QUIC-TRANSPORT] 的第16节),用于标识服务器推送。推送ID在连接的生命周期内是唯一的。
推送ID空间从零开始,并且是整数空间的子集;因此,推送ID不能出现在需要流ID或请求ID的上下文中。特别是,推送ID不允许出现在GOAWAY帧中(参见第5.2节)。
推送ID用于单个PUSH_PROMISE帧(参见第7.2.5节)和单个推送流(参见第4.6节和第6.2.2节)。这些使用必须引用服务器在连接的生命周期内作出的相同承诺的推送。
在推送流上发送推送响应后,推送ID不能被重用。如果客户端在同一推送ID上从不同流接收到另一个推送流头或另一个PUSH_PROMISE,这必须 (MUST) 被视为类型为H3_ID_ERROR的连接错误。