跳到主要内容

8. 在 HTTP/2 中表达 HTTP 语义 (Expressing HTTP Semantics in HTTP/2)

HTTP/2 旨在尽可能兼容 HTTP 的现有用法. 这意味着, 从应用角度看, 协议的功能大体保持不变. 为实现这一点, 所有请求和响应语义都通过 HTTP/1.1 [HTTP] 中的机制保留, 尽管传达这些语义的语法已经改变.

因此, HTTP [HTTP] 的规范和要求、语义与语法, 以及 [COOKIES] 等扩展均适用于 HTTP/2. 本节描述的限制和附加要求仅限于成功实现 HTTP/2 所必需的内容, 这些内容与 HTTP 的线路格式相关, 或者如果 HTTP/2 未作额外定义就会导致歧义.

8.1. HTTP 消息成帧 (HTTP Message Framing)​

HTTP 消息 (请求或响应) 由以下部分组成:

  1. 仅对响应而言, 零个或多个 HEADERS 帧 (每个后跟零个或多个 CONTINUATION 帧), 其中包含信息性 (1xx) HTTP 响应的消息头部 (见 [HTTP] 的 Section 15), 和/或

  2. 一个 HEADERS 帧 (后跟零个或多个 CONTINUATION 帧), 其中包含消息头部 (见 [HTTP] 的 Section 6.3), 以及

  3. 零个或多个 DATA 帧, 其中包含消息内容 (见 [HTTP] 的 Section 6.4), 以及

  4. 可选地, 一个 HEADERS 帧 (后跟零个或多个 CONTINUATION 帧), 其中包含 trailer-part 字段节 (见 [HTTP] 的 Section 6.5).

序列中的最后一帧带有 END_STREAM 标志. 注意, 带有 END_STREAM 标志的 HEADERS 帧之后可以跟随 CONTINUATION 帧, 这些帧携带头部块的任何剩余部分. 其他帧 (来自任何流) MUST NOT 出现在 HEADERS 帧与其后可能跟随的任何 CONTINUATION 帧之间.

HTTP/2 使用 DATA 帧承载消息内容. [CHUNKED] 传输编码 ([HTTP] Section 7.1 中定义) MUST NOT 与 HTTP/2 一起使用.

Trailer 字段承载在一个字段节中; 见 Section 8.2. HTTP/2 没有为 trailer 字段定义字段节名称, 因为所有 trailer 字段都被编码到同一个字段节中. 字段行之间没有定义顺序; 见 Section 8.2.1. 最后, trailer 字段不包括伪头部字段 (见 Section 8.3).

如果 HTTP 请求或响应符合以下任一情况, 则它是格式错误的:

  • 它包含字段名无效的字段行 (见 [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 字段 (HTTP Fields)​

HTTP 字段名称和值是八位组序列, 并使用与 HTTP/1.x 中相同的编码 (见 [HTTP] 的 Section 5.1). 但是, 字段名称 MUST 在编码到 HTTP/2 之前转换为小写. 包含大写字段名称字符的请求或响应 MUST 被视为格式错误 (Section 8.1.1).

HTTP/2 不使用 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 字段本身. 即使未由 Connection 字段指定, 此类中间节点 SHOULD 也删除其他连接特定字段, 例如 Keep-Alive、Proxy-Connection、Transfer-Encoding 和 Upgrade.

|  注意: HTTP/2 有意不支持升级到另一种协议. [Section 3](#3-starting-http2) 中描述的
| 握手方法被认为足以协商替代协议的使用.

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" 之外的任何值.

Cookie 头部字段 [COOKIES] 使用分号 (";") 分隔 cookie-pair (或 "crumb"). 此头部字段不遵循逗号分隔的列表规则 (见 [HTTP] 的 Section 5.3), 这可能阻止 cookie-pair 被高效压缩. 由于 Cookie 头部字段通常可能包含相对大量的数据, 这会显著降低压缩效率.

为实现更好的压缩, Cookie 头部字段 MAY 被拆分为多个单独的头部字段, 每个字段包含一个或多个 cookie-pair. 如果解压缩后存在多个 Cookie 头部字段, 在传递到非 HTTP/2 上下文 (例如 HTTP/1.1 连接或通用 HTTP 服务器应用) 之前, 这些字段 MUST 使用 0x3B, 0x20 这两个八位组分隔符 (ASCII 字符串 "; ") 连接为单个八位组字符串.

因此, 以下两组 Cookie 头部字段在语义上等价.

cookie: a=b; c=d; e=f

cookie: a=b
cookie: c=d
cookie: e=f

8.3. HTTP 控制数据 (HTTP Control Data)​

HTTP/2 使用以 ':' 字符 (ASCII 0x3a) 开头的特殊伪头部字段来传达关于请求或响应的信息; 在 HTTP/1.x 中, 这些信息由消息 start-line 承载. 伪头部字段不是 HTTP 字段. 端点 MUST NOT 生成本文档定义之外的伪头部字段.

伪头部字段仅在 HEADERS 和 PUSH_PROMISE 帧的字段节中有效. 然而, 承载 trailer 字段的字段节 MUST NOT 包含伪头部字段; 包含伪头部字段的 trailer-part 字段节 MUST 被视为格式错误 (Section 8.1.1).

伪头部字段以 ':' 字符 (ASCII 0x3a) 开头. 它们的名称是仅由此前缀组成的字段名称; 冒号不是字段名称的一部分. 冒号不属于字段名称所用的 token 集 (见 [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 的 scheme 部分 ([URI] 的 Section 3.1).

    :scheme 不限于 "http" 和 "https" 方案的 URI. 代理或网关可以转换非 HTTP 方案的请求, 从而允许使用 HTTP 与非 HTTP 服务交互.

    CONNECT 方法省略 :scheme 伪头部字段; 见 Section 8.5.

  • :authority 伪头部字段包含目标 URI 的 authority 部分 ([URI] 的 Section 3.2). 对于 "http" 或 "https" 方案的 URI, authority MUST NOT 包含已弃用的 userinfo 子组件.

    为确保能够准确复现 HTTP/1.1 请求行, 当从具有 origin-form 或 asterisk-form 请求目标的 HTTP/1.1 请求转换时 (见 [HTTP] 的 Section 3.2), MUST 省略此伪头部字段. 直接生成 HTTP/2 请求的客户端 MUST 使用 :authority 伪头部字段, 而不是 Host 字段. 将 HTTP/2 请求转换为 HTTP/1.1 的中间节点在请求中不存在 Host 字段时, MUST 通过复制 :authority 伪头部字段的值来创建 Host 字段.

    如果请求同时包含 :authority 伪头部字段和 Host 字段, 且字段值不相同, 服务器 SHOULD 将该请求视为格式错误.

  • :path 伪头部字段包含目标 URI 的路径和查询部分 (absolute-path 产生式, 以及可选的 '?' 字符后跟 query 产生式; 见 [URI] 的 Section 3.3 和 3.4). asterisk-form 请求在 :path 伪头部字段中包含值 '*'.

    对于 "http" 或 "https" URI, 此伪头部字段 MUST NOT 为空; 不包含路径组件的 "http" 或 "https" URI MUST 包含值 '/', 除非请求使用 asterisk-form, 在这种情况下 MUST 包含值 '*'.

    CONNECT 方法省略 :path 伪头部字段; 见 Section 8.5.

所有 HTTP/2 请求 MUST 恰好包含 :method 和 :scheme 伪头部字段的一个有效值, 除非它是 CONNECT 请求 (Section 8.5).

如果 :scheme 伪头部字段标识的 scheme 使用 authority (见 [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 状态行中所含版本或 reason phrase 的方式.

8.4. 服务器推送 (Server Push)​

HTTP/2 使服务器能够预先向客户端发送 (或 "推送") 与先前客户端发起请求相关联的响应. 当服务器知道客户端需要这些响应才能完整处理原始请求的响应时, 这可能有用.

推送响应始终与来自客户端的显式请求相关联. 服务器在该请求的流上发送 PUSH_PROMISE 帧. PUSH_PROMISE 帧包含一个字段节, 其中含有服务器归属于该请求的一组完整请求字段.

除了单个连接上可用的流标识符外, 服务器可推送的响应数量没有限制. 然而, 客户端可以通过更改 SETTINGS_MAX_CONCURRENT_STREAMS 设置来限制并发推送流的数量. 将此值设为零会阻止服务器创建必要的流, 从而禁用服务器推送. 这并不阻止服务器发送 PUSH_PROMISE 帧; 客户端需要重置任何不需要的承诺流.

8.4.1. 推送请求 (Push Requests)​

服务器推送在语义上等同于服务器响应一个请求; 但在这种情况下, 该请求也由服务器作为 PUSH_PROMISE 帧发送.

PUSH_PROMISE 帧包含一个字段节, 其中含有服务器归属于该请求的一组完整请求字段. 除非 HTTP/2 连接是通过 TLS 发起的 (见 [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 考虑可能的请求内容、请求中的任何缓存字段、请求方法, 以及已知由客户端管理的任何内容 (例如基于早前请求中收到的 Cookies).

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 设置限制服务器可并发推送的响应数量. 通告 SETTINGS_MAX_CONCURRENT_STREAMS 值为零会阻止服务器创建必要的流, 从而禁用服务器推送. 这并不禁止服务器发送 PUSH_PROMISE 帧; 客户端需要重置任何不想要的承诺流.

8.5. CONNECT 方法 (The CONNECT Method)​

在 HTTP/1.x 中, 伪方法 CONNECT ([HTTP] 的 Section 9.3.6) 用于将 HTTP 连接转换为到远程主机的隧道. CONNECT 主要与 HTTP 代理一起使用, 目的是建立到源服务器的 TLS 会话, 以便与 "https" 资源交互.

在 HTTP/2 中, CONNECT 方法用于以类似目的在远程主机上建立隧道.

CONNECT 请求 MUST 使用以下伪头部字段:

  • :method 伪头部字段设置为 "CONNECT".
  • :authority 伪头部字段包含要连接到的主机和端口 (等同于 CONNECT 请求的 request-target 的 authority-form; 见 [HTTP] 的 Section 3.2).

:scheme 和 :path 伪头部字段 MUST 被省略.

CONNECT 请求 MUST NOT 包含 Content-Length 或 Transfer-Encoding 头部字段. 任何包含这些字段的 CONNECT 请求都是格式错误的 (Section 8.1.1).

承载 CONNECT 请求的流不用于传达 HTTP 请求或响应. 任何成功的 CONNECT 响应用于通知客户端该流已成为隧道. 该流不会因为收到带 END_STREAM 标志的 DATA 帧而关闭. CONNECT 请求流也不会因为收到带 END_STREAM 标志的 DATA 帧而关闭.

代理移除任何已知为连接特定的头部字段 (例如 [HTTP] Section 7.6.1 中定义的那些字段), 然后允许剩余请求头部字段在到服务器的连接上原样通过.

CONNECT 请求后收到的任何 2xx (Successful) 响应都表示代理已建立到所请求主机和端口的连接, 并已切换为隧道传输相应流.

对于任何其他成功响应, 不会创建隧道.

成功建立隧道后, 服务器 MUST 在隧道流上发送单个 DATA 帧 (设置 END_STREAM 标志), 以立即关闭隧道. 客户端 SHOULD 在关闭隧道后于任一端点半关闭其流, 然后在收到对等方的 END_STREAM 标志后关闭其流.

TCP 连接错误由任一端点使用带有错误代码的 RST_STREAM 帧发出信号. 代理将其收到的任何错误代码视为流错误 (Section 5.4.2) 的指示, 表明它无法成功完成 CONNECT 请求. 相应地, 如果代理检测到它为 CONNECT 请求流所表示的 TCP 连接出现错误, 它会以相同方式向客户端发送信号.

8.6. Upgrade 头部字段 (The Upgrade Header Field)​

HTTP/2 不支持 101 (Switching Protocols) 信息性状态码 ([HTTP] 的 Section 15.2.2).

Upgrade 头部字段的语义特定于要升级到的协议. 直连连接中的 Upgrade 字段 (Section 9.1.1) 仅适用于下一跳. 因此, Upgrade 字段不适用于 HTTP/2.

希望在 HTTP/2 连接上升级到不同协议的实现 SHOULD 使用 [ALT-SVC].

8.7. 请求可靠性 (Request Reliability)​

一般而言, 当发生错误时, HTTP 客户端无法重试非幂等请求, 因为没有办法确定错误的性质. 某些服务器处理可能已在错误之前发生, 如果重新尝试请求, 可能产生不良后果.

当服务器在未执行任何应用处理的情况下拒绝请求流时, 该请求可以被认为可在不同连接上安全重试. 服务器可以通过 RST_STREAM 帧上的错误代码 REFUSED_STREAM (Section 7) 指示这一点.

服务器在已执行应用处理后 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/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