8. Expressing HTTP Semantics in HTTP/2 (在HTTP/2中表达HTTP语义)
HTTP/2旨在尽可能地与当前使用HTTP保持操作兼容性。这意味着,从应用程序的角度来看,协议的功能基本保持不变。为了实现这一点,所有请求和响应语义都通过HTTP/1.1的机制来保留 [HTTP],尽管传递这些语义的语法已经改变。
因此,HTTP [HTTP] 中定义的规范和要求、语义和语法以及扩展(例如 [COOKIES])都适用于HTTP/2。本节描述的限制和附加要求仅限于成功实现HTTP/2所需的那些,这些与HTTP的线路格式有关,或者在没有HTTP/2中额外定义的情况下会导致歧义。
8.1. HTTP Message Framing (HTTP消息帧化)
一个HTTP消息 (请求或响应) 包含:
-
对于响应来说,零个或多个 HEADERS 帧 (每个后面跟着零个或多个 CONTINUATION 帧),包含信息性 (1xx) HTTP 响应的消息头 (参见 [HTTP] 的 Section 15),和/或
-
一个 HEADERS 帧 (后面跟着零个或多个 CONTINUATION 帧),包含消息头 (参见 [HTTP] 的 Section 6.3),和
-
零个或多个 DATA 帧,包含消息内容 (参见 [HTTP] 的 Section 6.4),和
-
可选地,一个 HEADERS 帧 (后面跟着零个或多个 CONTINUATION 帧),包含尾部字段部分 (参见 [HTTP] 的 Section 6.5)。
序列中的最后一个帧带有 END_STREAM 标志,注意带有 END_STREAM 标志的 HEADERS 帧后面可以跟着包含在消息头块中剩余部分的 CONTINUATION 帧。其他帧 (来自任何流) 不得出现在 HEADERS 帧和任何可能跟随的 CONTINUATION 帧之间。
HTTP/2 使用 DATA 帧来承载消息内容。[CHUNKED] 传输编码 (在 [HTTP] 的 Section 7.1 中定义) 不得与 HTTP/2 一起使用。
尾部字段使用字段部分进行编码; 参见 Section 8.2。尾部字段没有定义的字段部分名称,因为在HTTP/2中所有尾部字段都编码到相同的字段部分中。字段行之间没有定义的顺序; 参见 Section 8.2.1。最后,尾部字段不包含伪字段部分 (参见 Section 8.3)。
HTTP请求或响应只有在以下情况下被认为是畸形的 (malformed):
-
它包含具有无效字段名称的字段行 (参见 [HTTP] 的 Section 5.1)、具有无效字段行值 (参见 [HTTP] 的 Section 5.5) 的字段行,或包含不允许在该上下文中出现的伪字段 (参见 Section 8.3),
-
它省略了对于该消息类型必需的伪字段 (参见 Section 8.3),
-
它包含字段行名称中的大写字符,或
-
它包含无效的 Content-Length 字段值。
流的错误处理在 Section 8.1.1 中描述。
除了消息的强制性部分之外,还可以在 HEADERS 帧中存在 PRIORITY 信息。参见 Section 5.3 获取更多信息。
接收到具有无效字段块的 HEADERS 帧会导致流错误 (参见 Section 5.4.2)。
8.1.1. Malformed Messages (畸形消息)
畸形消息是指遵循协议格式,但因违反语义要求而无效的消息。检测到畸形请求或响应的对等端必须 (MUST) 以流错误 (参见 Section 5.4.2) 进行响应。对于畸形请求,服务器可以 (MAY) 在关闭或重置流之前发送 HTTP 响应。客户端不得 (MUST NOT) 接受畸形响应。请注意,这些要求旨在保护免受几种常见的攻击; 它们是故意严格的,因为在畸形消息情况下宽松可能会暴露实现到这些漏洞。
8.2. HTTP Fields (HTTP字段)
HTTP 字段名称和值是八位字节序列,并与 HTTP/1.x 中使用的相同编码一起使用 (参见 [HTTP] 的 Section 5.1)。然而,字段名称必须 (MUST) 在编码到 HTTP/2 之前转换为小写。包含大写字段名称的请求或响应必须 (MUST) 被视为畸形 (Section 8.1.1)。
HTTP/2 不使用 HTTP/1.1 中定义的 Connection 字段 (参见 [HTTP] 的 Section 7.6.1) 来指示连接特定的字段; 在该协议中,连接特定的元数据通过其他方式传达。端点不得 (MUST NOT) 生成包含连接特定字段的 HTTP/2 消息; 任何包含连接特定字段的消息必须 (MUST) 被视为畸形 (Section 8.1.1)。
唯一的例外是 TE 字段,它可以 (MAY) 存在于 HTTP/2 请求中; 当它存在时,它不得 (MUST NOT) 包含除 "trailers" 之外的任何值。
这意味着中介体将 HTTP/1.x 消息转换为 HTTP/2 时将需要删除 Connection 字段中指定的任何字段,以及 Connection 字段本身。此类中介体还应该 (SHOULD) 删除其他连接特定的字段,例如 Keep-Alive、Proxy-Connection、Transfer-Encoding 和 Upgrade,即使它们未被 Connection 字段命名。
| 注意: HTTP/2 故意不支持升级到其他协议。Section 3 | 中描述的握手方法被认为足以用于协商备用协议的使用。
8.2.1. Field Validity (字段有效性)
字段名称按照 [HTTP] 的 Section 5.1 中的规则进行验证,但在 HTTP/2 中表示之前将其转换为小写。
使用 [COMPRESSION] 压缩的字段部分可以包含空格,如 "optional whitespace" (OWS) 产生所定义的那样; 白空格不是字段的一部分; 在解压期间或在将消息传递给非 HTTP/2 上下文(例如 HTTP/1.1 连接,或通用 HTTP 服务器应用程序)之前,必须 (MUST) 删除空格。
伪字段的值在 Section 8.3 中指定。
8.2.2. Connection-Specific Header Fields (连接特定的头字段)
HTTP/2 不使用 Connection 头字段来指示连接特定的字段; 在该协议中,连接特定的元数据通过其他方式传达。端点不得 (MUST NOT) 生成包含连接特定字段的消息; 任何包含连接特定字段的消息必须 (MUST) 被视为畸形 (Section 8.1.1)。
唯一例外是 TE 字段,它可以 (MAY) 存在于 HTTP/2 请求中,但当它存在时,不得 (MUST NOT) 包含除 "trailers" 之外的任何值。
8.2.3. Compressing the Cookie Header Field (压缩Cookie头字段)
Cookie 头字段 [COOKIES] 使用分号 (";") 来分隔 cookie 对 (或 "crumbs")。此头字段不遵循逗号分隔值 (参见 [HTTP] 的 Section 5.3) 的列表规则,这可能会使 cookie 对退化为不可接受的程度。因为 Cookie 头字段可能包含大量的数据,这可能会使压缩降级。
为了更好地压缩,Cookie 头字段可以 (MAY) 被分成单独的字段行,每行都有一个或多个 cookie 对。如果在解压后存在多个 Cookie 字段行,则这些必须 (MUST) 在传递到非 HTTP/2 上下文(例如 HTTP/1.1 连接,或通用 HTTP 服务器应用程序)之前使用两个八位字节的分隔符 0x3B、0x20 (ASCII 字符串 "; ") 连接成单个八位字节字符串。
因此,以下两个 Cookie 头字段列表在语义上是等效的。
cookie: a=b; c=d; e=f
cookie: a=b
cookie: c=d
cookie: e=f
8.3. HTTP Control Data (HTTP控制数据)
HTTP/2 使用特殊的伪字段 (pseudo-header fields) 来携带有关请求或响应的控制信息,这些信息在 HTTP/1.x 中通过消息的开始行携带。伪字段不是 HTTP 字段。端点不得 (MUST NOT) 生成本文档中定义的伪字段之外的伪字段。
伪字段只能出现在字段部分的 HEADERS 和 PUSH_PROMISE 帧中。然而,携带尾部的字段部分不得 (MUST NOT) 包含伪字段; 包含伪字段的尾部字段部分必须 (MUST) 被视为畸形 (Section 8.1.1)。
伪字段以 ':' 字符 (ASCII 0x3a) 开头。它们的名称是仅包含此前缀的字段名称; 冒号不是字段名称的一部分。冒号不是用于字段名称的标记集的一部分 (参见 [HTTP] 的 Section 5.1)。
所有伪字段必须 (MUST) 在所有常规字段之前出现。任何请求或响应,如果包含出现在常规字段之后的伪字段,都必须 (MUST) 被视为畸形 (Section 8.1.1)。
定义了以下伪字段:
8.3.1. Request Pseudo-Header Fields (请求伪字段)
以下伪字段针对 HTTP/2 请求定义:
-
:method伪字段包含 HTTP 方法 ([HTTP] 的 Section 9)。 -
:scheme伪字段包含目标 URI 的方案部分 ([URI] 的 Section 3.1)。:scheme不限于 "http" 和 "https" 方案化的 URI。代理或网关可以为非 HTTP 方案翻译请求,从而实现与非 HTTP 服务的使用。参见 Section 8.5 了解 CONNECT 方法,它省略了
:scheme伪字段。 -
:authority伪字段包含目标 URI 的授权部分 ([URI] 的 Section 3.2)。授权不得 (MUST NOT) 包含已弃用的 URI 用户信息子组件。要确保 HTTP/1.1 请求行可以被精确地复制,当从具有源或星号表单的 HTTP/1.1 请求转换时 (参见 [HTTP] 的 Section 3.2),此伪字段必须 (MUST) 被省略。来源表单的客户端生成的 HTTP/2 请求必须 (MUST) 改为包含
:authority伪字段。来源或星号表单的 HTTP/1.1 请求在转换时包含主机字段的客户端可以 (MAY) 选择在生成的 HTTP/2 请求中包含:authority或主机字段。如果 HTTP/2 请求中省略了
:authority伪字段,则必须 (MUST) 存在主机字段。服务器应该 (SHOULD) 将包含:authority伪字段和主机字段的请求视为畸形,如果字段值不相同的话。 -
:path伪字段包含目标 URI 的路径和查询部分 (绝对路径产生和一个可选的 '?' 字符后面跟随查询产生,参见 [URI] 的 Sections 3.3 和 3.4)。星号表单的请求包含:path伪字段的值 '*'。此伪字段不得 (MUST NOT) 为空; URI 没有路径组件的必须 (MUST) 包含值 '/',除非请求为星号表单,在这种情况下它必须 (MUST) 包含值 '*'。
参见 Section 8.5 了解 CONNECT 方法,它省略了
:path伪字段。
所有 HTTP/2 请求必须 (MUST) 恰好包含一次以下伪字段的有效值: :method 和 :scheme,除非它是 CONNECT 请求 (Section 8.5)。
如果 :scheme 伪字段标识一个使用授权的方案 (参见 [URI] 的 Section 3.2),则包括请求目标的 HTTP/2 请求必须 (MUST) 包含 :authority 或 Host 字段之一。如果这些字段都不存在,收到请求的服务器应该 (SHOULD) 以 400 (Bad Request) 状态码作出响应 (参见 [HTTP] 的 Section 15.5.1)。
HTTP/2 不定义携带 HTTP/1.1 请求行中包含的版本标识符的方式。
8.3.2. Response Pseudo-Header Fields (响应伪字段)
对于 HTTP/2 响应,定义了单个 :status 伪字段,它携带 HTTP 状态码字段 (参见 [HTTP] 的 Section 15)。此伪字段必须 (MUST) 包含在所有响应中; 否则,响应是畸形的 (Section 8.1.1)。
HTTP/2 不定义携带 HTTP/1.1 状态行中包含的版本或原因短语的方式。
8.4. Server Push (服务器推送)
HTTP/2 允许服务器为客户端可能需要的响应主动地 (抢占式地) 发送 (或 "push") 响应,以及由原始请求指示的响应。这在服务器知道客户端将需要这些响应以便完全处理对原始请求的响应时很有用。
推送响应总是与来自客户端的显式请求相关联。服务器通过在该请求上发送的流中发送 PUSH_PROMISE 帧来推送响应。PUSH_PROMISE 帧包含一个字段部分,其中包含服务器归因于请求的完整请求字段集。
除了单个连接上的可用流标识符之外,服务器可以推送的响应数量没有限制。但是,客户端可以通过更改 SETTINGS_MAX_CONCURRENT_STREAMS 设置来限制并发推送流的数量。将此值设置为零通过防止服务器创建必要的流来禁用服务器推送。这不会阻止服务器发送 PUSH_PROMISE 帧; 客户端需要重置任何不需要的承诺流。
8.4.1. Push Requests (推送请求)
服务器推送在语义上等同于服务器响应请求; 但是,在这种情况下,该请求也由服务器发送,作为 PUSH_PROMISE 帧。
PUSH_PROMISE 帧包括一个字段部分,其中包含服务器归因于请求的一组完整的请求字段。除了 HTTP/2 连接开始于 TLS 的情况外,不可能推送使用安全方案 (例如 https) 的响应 (参见 [TLS13])。
推送的请求必须 (MUST) 是可缓存的 (参见 [HTTP] 的 Section 9.2.3),必须 (MUST) 是安全的 (参见 [HTTP] 的 Section 9.2.1),并且不得 (MUST NOT) 包含请求内容。客户端接收到不可缓存、不安全或包含请求内容的推送请求必须 (MUST) 使用类型为 PROTOCOL_ERROR 的流错误 (Section 5.4.2) 来重置承诺流。
推送的请求字段和请求伪字段必须 (MUST) 是权威的 (参见 Section 10.1)。服务器必须 (MUST) 在 :authority 伪字段中包含一个它对其具有权威性的值 (参见 Section 10.1)。客户端必须 (MUST) 将不权威的推送请求视为类型为 PROTOCOL_ERROR 的流错误 (Section 5.4.2)。
客户端可以通过向服务器发送引用承诺流标识符的 RST_STREAM 来指示它不想接收推送的响应。
服务器应该 (SHOULD) 仅推送它认为客户端会使用或从中受益的响应。服务器在决定推送什么时应该 (SHOULD) 考虑可能的请求内容、请求中的任何缓存字段、请求的方法和由客户端管理的任何已知内容 (基于例如在较早请求上接收的 Cookie)。
8.4.2. Push Responses (推送响应)
在发送引用承诺流的 PUSH_PROMISE 帧之后,服务器可以开始在使用承诺流标识符的流上发送推送响应。服务器通过发送 HEADERS 帧来传送初始响应字段,使用承诺流标识符。此流进入 "half-closed (remote)" 状态 (Section 5.1)。服务器应该 (SHOULD) 在 PUSH_PROMISE 帧之前发送发起推送的响应,以避免出现竞态条件。
一旦客户端收到 PUSH_PROMISE 帧并选择接受推送响应,客户端不应该 (SHOULD NOT) 发出对承诺响应的任何请求,直到承诺流关闭。
如果客户端确定它不想从服务器接收推送响应,或者如果服务器花费了太长时间开始发送承诺响应,则客户端可以发送 RST_STREAM 帧,使用 CANCEL 或 REFUSED_STREAM 代码并引用推送流标识符。
客户端可以使用 SETTINGS_MAX_CONCURRENT_STREAMS 设置来限制服务器可以同时推送的响应数量。过度使用服务器推送可能导致客户端资源耗尽或资源被浪费到客户端不想要的推送响应上。因此,客户端应该 (SHOULD) 通过仅保留有限数量的推送流同时打开来控制资源分配。
8.5. The CONNECT Method (CONNECT方法)
在 HTTP/1.x 中,伪方法 CONNECT ([HTTP] 的 Section 9.3.6) 用于将 HTTP 连接转换为远程主机的隧道。CONNECT 主要与 HTTP 代理一起使用,以建立与源服务器的 TLS 会话,以便与 https 资源进行交互。
在 HTTP/2 中,CONNECT 方法用于建立到远程主机的隧道,以类似于 HTTP/1.x 的方式。
CONNECT 请求必须 (MUST) 使用以下伪字段:
:method伪字段设置为 "CONNECT"。:authority伪字段包含要连接到的主机和端口 (相当于 CONNECT 请求的请求目标的授权形式; 参见 [HTTP] 的 Section 3.2)。
:scheme 和 :path 伪字段必须 (MUST) 省略。
CONNECT 请求不得 (MUST NOT) 包含 Content-Length 或 Transfer-Encoding 字段。任何包含这些字段的 CONNECT 请求都是畸形的 (Section 8.1.1)。
包含 CONNECT 请求的流不用于传送 HTTP 请求或响应。成功的 CONNECT 响应用于通知客户端流已变为隧道。此流上的任何 DATA 帧对应于在隧道上发送或接收的数据。CONNECT 请求流不会因为接收到具有 END_STREAM 标志的 DATA 帧而关闭。
代理删除任何已知禁止的消息字段 (例如,如 [HTTP] 的 Section 7.6.1 中定义的那些连接特定字段),然后允许其余的请求字段通过未更改的连接到服务器。
在 CONNECT 请求之后收到的任何 2xx (成功) 响应都表示代理已建立到请求的主机和端口的连接,并已开始桥接相应的流。
对于其他成功的响应,不会创建隧道。
成功建立隧道后,服务器必须 (MUST) 在隧道流上发送单个 DATA 帧 (带有 END_STREAM 标志集) 以立即关闭隧道。客户端应该 (SHOULD) 在任一端点关闭隧道后半闭其流,然后在接收到对等端的 END_STREAM 标志之后关闭其流。
TCP 连接错误由任一端点通过在 DATA 帧中使用 RST_STREAM 帧来发信号通知。代理将接收到的任何错误代码视为流错误 (Section 5.4.2) 的表示,即它不能与服务器成功完成 CONNECT 请求。相应地,如果代理检测到代表 CONNECT 请求流的 TCP 连接出现错误,则它将以相同的方式向客户端发送信号。
8.6. The Upgrade Header Field (Upgrade头字段)
HTTP/2 不支持 101 (Switching Protocols) 信息状态码 ([HTTP] 的 Section 15.2.2)。
Upgrade 字段的语义特定于升级到的协议。直接连接 (Section 9.1.1) 中的 Upgrade 仅对下一步适用。因此,Upgrade 字段不适用于 HTTP/2。
在 HTTP/2 连接上可能需要升级到基于 HTTP 的协议的实现应该 (SHOULD) 使用 [ALT-SVC]。
8.7. Request Reliability (请求可靠性)
通常,HTTP 客户端无法在连接关闭时重试非幂等请求,因为无法确定服务器已经处理了哪些请求。然而,服务器通常无法立即处理请求。
当服务器拒绝请求流但未执行任何应用程序处理时,该请求可以在不同的连接上安全地重试。服务器可以使用错误码 REFUSED_STREAM (Section 7) 发送 RST_STREAM 来指示这一点。
服务器不得 (MUST NOT) 在其执行了应用程序处理之后使用错误码 REFUSED_STREAM。这是因为使用 REFUSED_STREAM 表示服务器未对请求执行任何处理,因此重试是安全的。对于已知由服务器以任何方式处理的请求,必须 (MUST) 使用不同的错误码。
客户端可以重试使用 REFUSED_STREAM 拒绝的请求,即使请求不是幂等的。然而,如果客户端选择重试此类请求,则必须 (MUST) 假设服务器在拒绝它之前可能已经处理了该请求。如果客户端重试了不是幂等的请求,它应该 (SHOULD) 在后续请求中包含指示器,以便服务器检测重试的请求。
包含不可缓存内容的 PUSH_PROMISE 请求是不可重试的。推送不可缓存或不安全的请求是不允许的,如 Section 8.4.1 中所述。
8.8. Examples (示例)
本节显示了 HTTP/1.1 和 HTTP/2 中的 HTTP 请求和响应。HTTP/2 请求和响应使用 HTTP/2 帧层上未显示的压缩字段部分。
8.8.1. Simple Request (简单请求)
HTTP/1.1 GET 请求包括请求字段、方法和请求目标。
GET /resource HTTP/1.1
Host: example.org
Accept: image/jpeg
相同的请求在 HTTP/2 中用伪字段表示,并作为字段部分在 HEADERS 帧中发送:
:method = GET
:scheme = https
:path = /resource
:authority = example.org
accept = image/jpeg
8.8.2. Simple Response (简单响应)
简单的 HTTP/1.1 响应包括状态码、响应字段和响应内容:
HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Length: 123
{binary data}
在 HTTP/2 中,使用伪字段表示状态码:
:status = 200
content-type = image/jpeg
content-length = 123
{binary data}
8.8.3. Complex Request (复杂请求)
具有可选字段的 HTTP/1.1 POST 请求:
POST /resource HTTP/1.1
Host: example.org
Content-Type: image/jpeg
Content-Length: 123
{binary data}
相同的 HTTP/2 请求:
:method = POST
:scheme = https
:path = /resource
:authority = example.org
content-type = image/jpeg
content-length = 123
{binary data}
8.8.4. Response with Body (带主体的响应)
HTTP/1.1 响应,包括字段和响应主体:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 552
<html>
<head>
<title>An Example Page</title>
</head>
<body>
<h1>Example</h1>
<p>This is an example page.</p>
</body>
</html>
在 HTTP/2 中使用字段部分和 DATA 帧:
:status = 200
content-type = text/html; charset=utf-8
content-length = 552
<html>
<head>
<title>An Example Page</title>
</head>
<body>
<h1>Example</h1>
<p>This is an example page.</p>
</body>
</html>
8.8.5. Informational Responses (信息性响应)
HTTP/2 支持使用 HEADERS 帧发送的信息性 (1xx) 响应,如 [HTTP] 的 Section 15 中定义的那样。
例如,100 (Continue) 响应可以在最终响应之前发送:
:status = 100
:status = 200
content-type = image/jpeg
content-length = 123
{binary data}
第8章完成!
References
- [HTTP] RFC 9110
- [COOKIES] RFC 6265
- [COMPRESSION] RFC 7541
- [URI] RFC 3986
- [TLS13] RFC 8446
- [ALT-SVC] RFC 7838