4. HTTP Frames (HTTP帧)
一旦HTTP/2连接建立,端点就可以开始交换帧。
4.1. Frame Format (帧格式)
所有帧都以固定的9字节头部开始,后跟可变长度的帧有效载荷 (Frame Payload)。
HTTP Frame {
Length (24),
Type (8),
Flags (8),
Reserved (1),
Stream Identifier (31),
Frame Payload (..),
}
图1: 帧布局
帧头部的字段定义如下:
Length (长度):帧有效载荷的长度,表示为无符号24位整数,单位为八位字节。除非接收者为SETTINGS_MAX_FRAME_SIZE设置了更大的值,否则禁止 (MUST NOT) 发送大于2^14 (16,384) 的值。
帧头部的9个八位字节不包含在此值中。
Type (类型):帧的8位类型。帧类型决定了帧的格式和语义。本文档中定义的帧列在第6节中。实现必须 (MUST) 忽略并丢弃未知类型的帧。
Flags (标志):为特定于帧类型的布尔标志保留的8位字段。
标志被分配特定于指示的帧类型的语义。未使用的标志是指对于特定帧类型没有定义语义的标志。接收时必须 (MUST) 忽略未使用的标志,发送时必须 (MUST) 保持未设置状态 (0x00)。
Reserved (保留):保留的1位字段。此位的语义未定义,发送时必须 (MUST) 保持未设置状态 (0x00),接收时必须 (MUST) 被忽略。
Stream Identifier (流标识符):流标识符 (参见第5.1.1节),表示为无符号31位整数。值0x00保留用于与整个连接相关联的帧,而不是单个流。
帧有效载荷的结构和内容完全取决于帧类型。
4.2. Frame Size (帧大小)
帧有效载荷的大小受接收者在SETTINGS_MAX_FRAME_SIZE设置中通告的最大大小的限制。此设置的值可以在2^14 (16,384) 和2^24-1 (16,777,215) 个八位字节之间(包括边界值)。
所有实现必须 (MUST) 能够接收和最低限度地处理长度最多为2^14个八位字节的帧,加上9字节的帧头部 (Section 4.1)。描述帧大小时不包括帧头部的大小。
注意: 某些帧类型,如PING (Section 6.7),对允许的帧有效载荷数据量施加了额外的限制。
如果帧超过SETTINGS_MAX_FRAME_SIZE中定义的大小、超过为帧类型定义的任何限制,或者太小而无法包含强制性帧数据,则端点必须 (MUST) 发送错误码FRAME_SIZE_ERROR。可能改变整个连接状态的帧中的帧大小错误必须 (MUST) 被视为连接错误 (Connection Error, Section 5.4.1);这包括任何携带字段块 (Field Block, Section 4.3) 的帧(即HEADERS、PUSH_PROMISE和CONTINUATION)、SETTINGS帧以及流标识符为0的任何帧。
端点没有义务使用帧中的所有可用空间。通过使用小于允许的最大大小的帧可以提高响应性。发送大帧可能导致发送时间敏感帧(如RST_STREAM、WINDOW_UPDATE或PRIORITY)的延迟,如果被大帧的传输阻塞,可能会影响性能。
4.3. Field Section Compression and Decompression (字段段压缩和解压缩)
字段段压缩 (Field Section Compression) 是将一组字段行 ([HTTP] 第5.2节) 压缩以形成字段块 (Field Block) 的过程。字段段解压缩 (Field Section Decompression) 是将字段块解码为一组字段行的过程。HTTP/2字段段压缩和解压缩的详细信息在 [COMPRESSION] 中定义,由于历史原因,该文档将这些过程称为头部压缩和解压缩。
每个字段块携带单个字段段的所有压缩字段行。头部段还包括与消息关联的控制数据,以伪头部字段 (Pseudo-Header Fields, Section 8.3) 的形式,使用与字段行相同的格式。
注意: RFC 7540 [RFC7540] 使用术语 "头部块 (Header Block)" 代替更通用的 "字段块 (Field Block)"。
字段块携带请求、响应、承诺请求和推送响应的控制数据和头部段(参见第8.4节)。除了临时响应和包含在PUSH_PROMISE (Section 6.6) 帧中的请求之外,所有这些消息都可以选择包括携带尾部段 (Trailer Section) 的字段块。
字段段是字段行的集合。字段块中的每个字段行携带单个值。然后将序列化的字段块分成一个或多个八位字节序列,称为字段块片段 (Field Block Fragments)。第一个字段块片段在HEADERS (Section 6.2) 或PUSH_PROMISE (Section 6.6) 的帧有效载荷中传输,每个帧可以后跟CONTINUATION (Section 6.10) 帧来携带后续的字段块片段。
Cookie头字段 [COOKIE] 由HTTP映射特殊处理(参见第8.2.3节)。
接收端点通过连接其片段来重新组装字段块,然后解压缩该块以重建字段段。
完整的字段段由以下任一组成:
-
单个HEADERS或PUSH_PROMISE帧,设置了END_HEADERS标志,或
-
HEADERS或PUSH_PROMISE帧未设置END_HEADERS标志,以及一个或多个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. Compression State (压缩状态)
字段压缩是有状态的。每个端点都有一个HPACK编码器上下文 (HPACK Encoder Context) 和一个HPACK解码器上下文 (HPACK Decoder Context),用于编码和解码连接上的所有字段块。[COMPRESSION] 第4节定义了动态表 (Dynamic Table),它是每个上下文的主要状态。
动态表具有由HPACK解码器设置的最大大小。端点使用SETTINGS_HEADER_TABLE_SIZE设置传达其HPACK解码器上下文选择的大小;参见第6.5.2节。当建立连接时,两个端点的HPACK解码器和编码器的动态表大小都从4,096字节开始,这是SETTINGS_HEADER_TABLE_SIZE设置的初始值。
使用SETTINGS_HEADER_TABLE_SIZE设置的最大值的任何更改在端点确认设置时生效 (Section 6.5.3)。该端点的HPACK编码器可以将动态表设置为解码器设置的最大值以下的任何大小。HPACK编码器使用动态表大小更新指令 (Dynamic Table Size Update Instruction, [COMPRESSION] 第6.3节) 声明动态表的大小。
一旦端点确认SETTINGS_HEADER_TABLE_SIZE的更改将最大值减小到低于动态表的当前大小,其HPACK编码器必须 (MUST) 以动态表大小更新指令开始下一个字段块,该指令将动态表设置为小于或等于减小的最大值的大小;参见 [COMPRESSION] 第4.2节。如果字段块在确认动态表最大大小减小后没有以符合要求的动态表大小更新指令开始,则端点必须 (MUST) 将该字段块视为类型为COMPRESSION_ERROR的连接错误 (Section 5.4.1)。
建议: 实现者应注意,减小SETTINGS_HEADER_TABLE_SIZE的值并不广泛可互操作。使用连接前言将值减小到低于初始值4,096的做法得到了更好的支持,但这可能在某些实现中失败。