跳到主要内容

4. HTTP 帧

一旦 HTTP/2 连接建立, 端点即可开始交换帧.

4.1. 帧格式

所有帧都以固定的 9 八位组头部开始, 后跟可变长度的帧载荷.

HTTP Frame {
Length (24),
Type (8),

Flags (8),

Reserved (1),
Stream Identifier (31),

Frame Payload (..),
}

Figure 1: Frame Layout

帧头部字段定义如下:

Length: 以八位组为单位、用无符号 24 位整数表示的帧载荷长度. 除非接收方已为 SETTINGS_MAX_FRAME_SIZE 设置更大的值, 否则 MUST NOT 发送大于 2^14 (16,384) 的值.

帧头部的 9 个八位组不包含在该值中.

Type: 帧的 8 位类型. 帧类型决定帧的格式和语义. 本文档定义的帧列于 Section 6. 实现 MUST 忽略并丢弃未知类型的帧.

Flags: 一个 8 位字段, 为特定于帧类型的布尔标志保留.

标志会被赋予特定于所指示帧类型的语义. 未使用标志是指对特定帧类型没有已定义语义的标志. 收到未使用标志时 MUST 忽略, 发送时 MUST 保持未设置 (0x00).

Reserved: 一个保留的 1 位字段. 该比特的语义未定义, 发送时 MUST 保持未设置 (0x00), 接收时 MUST 忽略.

Stream Identifier: 用无符号 31 位整数表示的流标识符 (见 Section 5.1.1). 值 0x00 为与整个连接相关而非与单个流相关的帧保留.

帧载荷的结构和内容完全取决于帧类型.

4.2. 帧大小

帧载荷的大小受接收方在 SETTINGS_MAX_FRAME_SIZE 设置中通告的最大大小限制. 该设置可以取 2^14 (16,384) 到 2^24-1 (16,777,215) 八位组之间的任意值, 包含两端.

所有实现 MUST 能够接收并至少处理长度最高为 2^14 八位组的帧, 外加 9 八位组帧头部 (Section 4.1). 描述帧大小时不包含帧头部大小.

Note: 某些帧类型, 例如 PING (Section 6.7), 会对允许的帧载荷数据量施加额外限制.

如果帧超过 SETTINGS_MAX_FRAME_SIZE 中定义的大小, 超过该帧类型定义的任何限制, 或者小到不足以包含强制帧数据, 端点 MUST 发送 FRAME_SIZE_ERROR 错误码. 如果可能改变整个连接状态的帧出现帧大小错误, MUST 将其视为连接错误 (Section 5.4.1); 这包括任何携带字段块 (Section 4.3) 的帧 (即 HEADERS、PUSH_PROMISE 和 CONTINUATION)、SETTINGS 帧, 以及任何流标识符为 0 的帧.

端点没有义务用满帧中的所有可用空间. 使用小于允许最大大小的帧可以提升响应性. 发送大型帧可能导致发送时间敏感帧 (例如 RST_STREAM、WINDOW_UPDATE 或 PRIORITY) 时出现延迟; 如果这些帧被大型帧传输阻塞, 可能影响性能.

4.3. 字段段压缩和解压缩

字段段压缩是将一组字段行 (field line, [HTTP] Section 5.2) 压缩成字段块 (field block) 的过程. 字段段解压缩是将字段块解码为一组字段行的过程. HTTP/2 字段段压缩和解压缩的细节在 [COMPRESSION] 中定义; 出于历史原因, 该文档将这些过程称为头部压缩和解压缩.

每个字段块携带单个字段段中所有被压缩的字段行. 头部段还以伪头字段 (pseudo-header field, Section 8.3) 的形式包含与消息相关的控制数据, 这些字段使用与字段行相同的格式.

Note: RFC 7540 [RFC7540] 使用术语 "header block" 来代替更通用的 "field block".

字段块携带请求、响应、承诺请求和推送响应 (见 Section 8.4) 的控制数据和头部段. 除临时响应和包含在 PUSH_PROMISE (Section 6.6) 帧中的请求外, 所有这些消息都可以选择包含一个携带尾部段 (trailer section) 的字段块.

字段段是字段行的集合. 字段块中的每个字段行携带单个值. 序列化后的字段块随后被分成一个或多个八位组序列, 称为字段块片段. 第一个字段块片段在 HEADERS (Section 6.2) 或 PUSH_PROMISE (Section 6.6) 的帧载荷中传输; 这些帧均可后跟 CONTINUATION (Section 6.10) 帧以携带后续字段块片段.

Cookie 头字段 [COOKIE] 会由 HTTP 映射特殊处理 (见 Section 8.2.3).

接收端点通过连接字段块片段来重新组装字段块, 然后解压缩该块以重建字段段.

完整字段段由以下任一形式组成:

  • 单个 HEADERS 或 PUSH_PROMISE 帧, 且设置了 END_HEADERS 标志, 或

  • 一个未设置 END_HEADERS 标志的 HEADERS 或 PUSH_PROMISE 帧, 后跟一个或多个 CONTINUATION 帧, 其中最后一个 CONTINUATION 帧设置了 END_HEADERS 标志.

每个字段块都作为离散单元处理. 字段块 MUST 作为连续帧序列传输, 中间不得交错任何其他类型的帧或来自任何其他流的帧. HEADERS 或 CONTINUATION 帧序列中的最后一个帧设置 END_HEADERS 标志. PUSH_PROMISE 或 CONTINUATION 帧序列中的最后一个帧设置 END_HEADERS 标志. 这使得字段块在逻辑上等价于单个帧.

字段块片段只能作为 HEADERS、PUSH_PROMISE 或 CONTINUATION 帧的帧载荷发送, 因为这些帧携带的数据可能修改接收方维护的压缩上下文. 接收 HEADERS、PUSH_PROMISE 或 CONTINUATION 帧的端点需要重新组装字段块并执行解压缩, 即使这些帧将被丢弃也是如此. 如果接收方没有解压缩字段块, 它 MUST 以 COMPRESSION_ERROR 类型的连接错误 (Section 5.4.1) 终止连接.

字段块中的解码错误 MUST 被视为 COMPRESSION_ERROR 类型的连接错误 (Section 5.4.1).

4.3.1. 压缩状态

字段压缩是有状态的. 每个端点都有一个 HPACK 编码器上下文和一个 HPACK 解码器上下文, 用于对连接上的所有字段块进行编码和解码. [COMPRESSION] Section 4 定义了动态表, 它是每个上下文的主要状态.

动态表具有由 HPACK 解码器设置的最大大小. 端点使用 SETTINGS_HEADER_TABLE_SIZE 设置传达其 HPACK 解码器上下文选择的大小; 见 Section 6.5.2. 建立连接时, 两个端点的 HPACK 解码器和编码器的动态表大小均从 4,096 字节开始, 这是 SETTINGS_HEADER_TABLE_SIZE 设置的初始值.

使用 SETTINGS_HEADER_TABLE_SIZE 设置的最大值的任何变化, 都在端点确认设置 (Section 6.5.3) 时生效. 该端点的 HPACK 编码器可以将动态表设置为不超过解码器所设最大值的任意大小. HPACK 编码器使用 Dynamic Table Size Update 指令 ([COMPRESSION] Section 6.3) 声明动态表大小.

一旦端点确认了将最大值降低到当前动态表大小以下的 SETTINGS_HEADER_TABLE_SIZE 变更, 其 HPACK 编码器 MUST 以一个 Dynamic Table Size Update 指令开始下一个字段块, 将动态表设置为小于或等于降低后最大值的大小; 见 [COMPRESSION] Section 4.2. 如果在确认最大动态表大小降低之后的字段块没有以符合要求的 Dynamic Table Size Update 指令开始, 端点 MUST 将其视为 COMPRESSION_ERROR 类型的连接错误 (Section 5.4.1).

Note: 建议实现者注意, 降低 SETTINGS_HEADER_TABLE_SIZE 的值并不具备广泛互操作性. 使用连接前言将该值降低到初始值 4,096 以下的支持稍好, 但在某些实现中仍可能失败.