跳到主要内容

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