HTTP/2定义了多种帧的类型,每种类型都有一个唯一的8字节类型编码。在整个TCP连接或者是各个独立的流的建立和管理过程中,每种类型的帧都为特定的目的而服务。
DATA帧
DATA帧(数据帧,类型是0x0)传输与流相关联的任意的、可变长度的字节序列。例如,使用一个或多个DATA帧携带HTTP的请求和响应的有效载荷。
DATA帧可以包含填充字节序列。填充可以被添加到DATA帧,以掩盖消息的大小。因此,填充属于安全特性。
DATA帧的格式如下:
+---------------+
各个字段解释如下:
- Pad Length(填充长度):8比特字段,表示填充字节序列(即上图中的Padding)的字节长度。这个字段是可选的,只有在设置了PADDED标志位时才有。
- Data(数据):具体应用数据,即帧的有效载荷减去其它字段的长度(其它字段是可选的)。
- Padding(填充字节):填充字节不包含任何应用语义值。发送时必须将填充字节的值都设置为0。接收方没有义务验证填充序列,但是可以将非0的填充字节视为类型为PROTOCOL_ERROR的连接错误。
DATA帧定义了以下标志位:
- END_STREAM (0x1):设置此标志位时表明当前帧是端点在流上发送的最后一个帧,从而导致流进入“半关闭”或“关闭”状态。
- PADDED (0x8):设置此标志位表明帧中包含“填充长度”和“填充字节”这2个字段。
DATA帧必须与一个流关联起来。如果接收到的DATA帧的流标志位是0x0,那么接收方必须响应一个类型是PROTOCOL_ERROR的连接错误。
DATA帧受流量控制支配。只有当流处于“打开”或“半关闭(远程)”状态时,才能发送DATA帧。整个DATA帧都包含在流量控制中,数据、填充长度和填充字节字段都不例外。如果接收到的DATA帧关联的流不是“打开”或“半关闭(本地)”状态,那么,接收方必须响应一个类型是STREAM_CLOSED的流错误。
“填充字节”的总数由“填充长度”字段决定。如果填充字节序列的长度等于或大于整个帧的有效载荷,那么,接收方必须将其视为一个类型是PROTOCOL_ERROR的连接错误。
一个特殊情况是,可以通过将“填充长度”字节的值设置为0,为DATA帧增加一个字节的长度。
HEADERS帧(报头帧,类型是0x1)用于打开一个流,另外可以携带一个报头块碎片。HEADERS帧可以在状态为“空闲”、“保留(本地)”、“打开”、“半关闭(远程)”的流上发送。
HEADERS帧的格式如下:
+---------------+
各个字段的解释如下:
- Pad Length(填充长度):8比特位字段,表示填充字节序列(即上图中的Padding)的字节长度。这个字段是可选的,只有在设置了PADDED标志位时才有。
- E(排他标志位):1比特位字段,表明流依赖是排他的。这个字段是可选的,只有在设置了PRIORITY标志位时才有。
- Stream Dependency(流依赖):31比特位的流标志位,表示当前流依赖的那个流,即当前流的“父亲流”。这个字段是可选的,只有在设置了PRIORITY标志位时才有。
- Weight(权重):8比特位的整数,表示流的优先级权重,范围是1-256之间。这个字段是可选的,只有在设置了PRIORITY标志位时才有。
- Header Block Fragment(报头块碎片):报头块的片段。
- Padding(填充字节):填充字节。
HEADERS帧定义了以下标志位:
- END_STREAM(0x1):设置此标志位时表明当前帧是端点在流上发送的最后一个帧。携带END_STREAM标志位的HEADERS帧预示流的结束。然而,在相同的流中,携带END_STREAM标志位的HEADERS帧后面可以跟随一个或多个CONTINUATION帧。逻辑上,CONTINUATION帧是HEADERS帧的一部分。
- PADDED (0x8):设置此标志位时表明帧中包含“填充长度”和“填充字节”字段。
- PRIORITY(0x20):设置此字段时表明帧中包含“排他标志位”、“流依赖”和“权重”字段。
HEADERS帧的有效载荷包含一个报头块碎片。如果HEADERS帧中装不下报头块碎片,那么报头块碎片的剩余部分将被装到后面的CONTINUATION帧中。
HEADERS帧必须与一个流关联起来。如果接收到的HEADERS帧的流标志位是0x0,那么接收方必须响应一个类型是PROTOCOL_ERROR的连接错误。
HEADERS帧会改变流的状态。(参考(四) HTTP/2的流状态)
HEADERS帧可以包含填充字节。填充字段和标志位与DATA帧的相同。填充字节的长度超过为报头块碎片保留的大小时,将导致一个类型是PROTOCOL_ERROR的连接错误。
HEADERS帧中的优先级信息逻辑上等价于一个单独的PRIORITY帧,但是,这些信息包含在HEADERS帧中可以避免在创建新流时潜在的优先级信息丢失。HEADERS帧中的“优先级”字段紧接着流中的第一个,重新安排流的优先级。
PRIORITY帧
PRIORITY帧(优先级帧,类型是0x2)明确了发送者建议的流的优先级。它可以在任意流状态下发送,包括空闲状态和关闭状态。
PRIORITY帧的格式如下:
+-+-------------------------------------------------------------+
各个字段的解释如下:
- E(排他标志位):1比特位字段,表明流依赖是排他的。
- Stream Dependency(流依赖):31比特位的流标志位,表示当前流依赖的那个流,即当前流的“父亲流”。
- Weight(权重):8比特位的整数,表示流的优先级权重,范围是1-256之间。
PRIORITY帧没有定义任何标志位。
PRIORITY帧总是标识一个流。如果接收到一个流标识是0x0的PRIORITY帧,那么接收方必须响应一个类型是PROTOCOL_ERROR的流错误。
PRIORITY帧可以在任意状态的流上发送,但是不能在组成一个报头块的多个连续帧之间发送。注意,PRIORITY帧可能会在流已经处理结束或者已经发送结束的情况下到达,这时,它不会对标识的流有任何影响。对于“半关闭(远程)”或“关闭”状态的流,PRIORITY帧只影响被它标识的流及其“依赖流”们的处理,而并不会影响当前的流。
可以为处于“空闲”或“关闭”状态的流发送PRIORITY帧。这允许通过修改未使用的或关闭的“父亲流”的优先级来对一组“依赖流”的优先级进行调整。
长度非5个字节的PRIORITY帧必须被视为类型是FRAME_SIZE_ERROR的流错误。
RST_STREAM帧
RST_STREAM帧(重置帧,类型是0x3)允许立即终止一个流。一个RST_STREAM帧拥有请求取消流,或者表明发生了错误。
RST_STREAM帧的格式如下:
+---------------------------------------------------------------+
RST_STREAM帧包含一个单独的无符号32位整数,表示错误码。错误码表明为什么流被终止。
RST_STREAM帧未定义任何标志位。
RST_STREAM帧完全终止被它引用的流,使这个流进入“关闭”状态。在流上接收到RST_STREAM帧之后,除了PRIORITY帧之外,接收方不能发送额外的帧。然而,发送完RST_STREAM帧之后,发送方必须准备好接收和处理流上额外的帧,这些帧可能是对端在接收到RST_STREAM帧之前发送出来的。
RST_STREAM帧必须与一个流关联。如果收到流标识是0x0的RST_STREAM帧,那么接收方必须将其视为类型是 PROTOCOL_ERROR的连接错误。
不能为一个“空闲”流发送RST_STREAM帧。如果收到了标记了空闲流的RST_STREAM帧,那么接收方必须将其视为类型是PROTOCOL_ERROR的连接错误。
长度非4字节的RST_STREAM帧必须被视为FRAME_SIZE_ERROR类型的连接错误。
SETTINGS帧
SETTINGS帧(设置帧,类型是0x4)传递影响两端通信的配置参数,例如,对对端行为的偏好和约束。SETTINGS帧也用于确认这些参数的接收。个别地,单个的SETTINGS参数也可以被当做”setting”来引用。
SETTINGS参数不是通过协商确定的,它们描述发送方的特点,这些特点可以被接收方使用。通信双方可以为相同的参数设置不同的值,例如,客户端可以为流量控制窗口设置一个较大的值,而服务端为了节约资源可能会设置一个较小的值。
双方都必须在连接开始时发送SETTINGS帧,并且,在连接的生命周期内任意其它时间,任意一端都可以发送SETTINGS帧。
如果SETTINGS帧中已经存在了某个参数,那么参数的新值将替换掉旧值。参数按照出现的先后顺序被处理。SETTINGS帧的接收方无须维护任何状态,除了参数的当前值之外。因此,SETTINGS参数的值是接收方看到的最后一个值。
SETTINGS参数由接收方确认。为了实现这一目标,SETTINGS帧定义了如下标志位:
- ACK (0x1):设置这个标志位表明当前帧确认接收和应用了对端的SETTINGS帧。当设置了这个标志位的时候,SETTINGS帧的有效载荷必须是空的。接收到设置了ACK标志位但长度不是0的SETTINGS帧时,必须将其视为类型是FRAME_SIZE_ERROR的连接错误。
SETTINGS帧总是被应用于整个连接,而不是一个单独的流。SETTINGS帧的流标识必须是0。如果一个端点接收到流标识字段非0的SETTINGS帧,那么必须响应一个类型是PROTOCOL_ERROR的连接错误。
SETTINGS帧影响连接状态。格式错误或不完整的SETTINGS帧必须被视为一个类型是PROTOCOL_ERROR的连接错误。
SETTINGS帧的长度不是6的整数倍时必须被视为一个类型是FRAME_SIZE_ERROR的连接错误。
SETTINGS帧的格式
SETTINGS帧的有效载荷包含0个或多个参数,每个参数由一个无符号16位标识和一个无符号32位值构成,如下图所示:
+-------------------------------+
已定义的SETTINGS参数
定义了以下参数:
- SETTINGS_HEADER_TABLE_SIZE(0x1):允许发送方通知接收方用于解码报头块的报头压缩表的最大尺寸,以字节为单位。编码器可以通过使用与报头块内压缩格式相关的信令选择小于或等于这个值的任意值。初始值是4096字节。
- SETTINGS_ENABLE_PUSH (0x2):这个设置可以用于禁止服务端推送。如果一个端点接收到的这个参数被设置为0,那么它一定不能发送PUSH_PROMISE帧。如果一个端点设置了这个值为0,并且已经被对端确认,那么,当它接收到 PUSH_PROMISE帧时必须将其视为类型是PROTOCOL_ERROR的连接错误。
- SETTINGS_MAX_CONCURRENT_STREAMS (0x3):表明发送方允许的最大并发流数量。这个限制是定向的:它适用于发送方允许接收方创建的流的数量。最初,这个值没有限制。
- SETTINGS_INITIAL_WINDOW_SIZE (0x4):表明发送方的流级别的流量控制的初始窗口大小(以字节为单位)。初始值是2^16-1(65535)字节。这个设置影响所有流的窗口大小。超过65535的窗口大小必选被视为类型是FLOW_CONTROL_ERROR的连接错误。
- SETTINGS_MAX_FRAME_SIZE (0x5):表明发送方将会接收到的最大帧的有效载荷尺寸(单位是字节)。初始值是2^14(16384)字节。端点公布的值必须在这个初始值和最大允许值(2^24-1,16777215字节)之间。超出这个范围的值必须被视为类型是PROTOCOL_ERROR的连接错误。
- SETTINGS_MAX_HEADER_LIST_SIZE (0x6):这个设置通知对端发送方准备接受的报头列表的最大尺寸,以字节为单位。这个值基于报头字段未压缩的大小,包括字段名和字段值的长度,外界每个报头字段32字节的开销。对于一个给定的请求,低于建议值的限制可以被强制执行。此设置的初始值没有限制。
接收到包含位置或不支持的标识的SETTINGS帧的一端必须忽略这些非法设置。
SETTINGS参数的同步
SETTINGS中的大部分值都受益于或者需要了解对端何时收到和应用已经变化的参数值。为了提供这种同步时间点,接收方收到没有设置ACK标志位的SETTINGS帧时必须尽快应用更新过的参数。
SETTINGS帧的参数值必须按照它们出现的顺序被处理,在处理参数值的中间不能处理其它帧。必须忽略不支持的参数。一旦所有的参数值都被处理完,接收方必须立即发送一个带有ACK标志位的SETTINGS帧。当收到带有ACK标志位的SETTINGS帧时,修改参数的发送方可以认为修改已经生效。
如果SETTINGS帧的发送方在一定时间内没有收到确认,那么它可以发出一个类型是SETTINGS_TIMEOUT的连接错误。
PUSH_PROMISE帧
PUSH_PROMISE帧(推送承诺帧,类型是0x5)用于提前将发送方打算初始化的流通知给对端。PUSH_PROMISE帧包含端点计划创建的流的无符号31位标识,以及为流提供附加上下文的报头集合。
PUSH_PROMISE帧的格式如下:
+---------------+
各个字段的解释如下:
- Pad Length(填充长度):8比特位字段,表示填充字节序列的字节长度。这个字段是可选的,只有在设置了PADDED标志位时才有。
- R(保留标志位):1比特位保留字段。
- Promised Stream ID(承诺流ID):无符号31位整数,用于标识PUSH_PROMISE保留的流。
- Header Block Fragment(报头块碎片):报头块的片段,包含请求报头字段。
- Padding(填充字节):填充字节序列。
PUSH_PROMISE帧定义了以下标志位:
- END_HEADERS (0x4):设置此标志位表明这个帧包含一个完整的报头块,其后不再跟随任何CONTINUATION帧。没有设置END_HEADERS标志位的PUSH_PROMISE帧后面必须在相同流中跟随一个CONTINUATION帧。接收方必须将收到的任何其它类型的帧或者在不同流上的CONTINUATION帧视为类型是PROTOCOL_ERROR的连接错误。
- PADDED (0x8):设置此标志位表明“填充长度”字段以及它表示的填充字节序列是已设置的。
PUSH_PROMISE帧必须只能在由远端初始化的、处于“打开”或“半关闭(远程)”状态的流上发送。PUSH_PROMISE帧的流标识表明它所关联的流。如果流标识字段的值是0x0,那么接收方必须响应一个类型是PROTOCOL_ERROR的连接错误。
被承诺的流不必按照它们被承诺的顺序来使用。PUSH_PROMISE只是保留流标识以备后面使用。
如果远端的SETTINGS_ENABLE_PUSH被设置为0,那么不能发送PUSH_PROMISE帧。如果一个端点设置了SETTINGS_ENABLE_PUSH并且接收到确认,那么当它收到PUSH_PROMISE帧时,必须认为是类型为PROTOCOL_ERROR的连接错误。
PUSH_PROMISE帧的接收方可以选择拒绝被承诺的流,方法是返回一个引用被承诺的流的标识的RST_STREAM帧给PUSH_PROMISE帧的发送方。
PUSH_PROMISE帧以两种方式修改流的状态。首先,帧中包含报头块,潜在地修改了为报头压缩所维护的状态;其次,PUSH_PROMISE保留了一个流以备后续使用,这导致被承诺的流进入“保留”状态。除非流处于“打开”或“半关闭(远程)”状态,否则,发送方一定不能在流上发送PUSH_PROMISE帧。发送方必须保证被承诺的流是新的流标识符的有效选择(也就是说,被承诺的流必须处于“空闲”状态)。
由于PUSH_PROMISE保留了一个流,忽略PUSH_PROMISE帧会导致流状态变得不确定。接收方在状态不是“打开”或“半关闭(远程)”的流上接收到PUSH_PROMISE帧时,必须将其视为一个类型是PROTOCOL_ERROR的连接错误。然而,在关联的流上已经发送了RST_STREAM帧的端点必须处理PUSH_PROMISE帧,因为这些PUSH_PROMISE帧可能在RST_STREAM帧被接收和处理之前就已经被创建出来了。
接收方收到承诺非法流标识的PUSH_PROMISE帧时,必须将其视为一个类型是PROTOCOL_ERROR的连接错误。注意,非法流标识是指当前未处于“空闲”状态的流的标识。
PUSH_PROMISE帧可以包含填充字节序列。填充字段和标志位与DATA帧中定义的一致。
PING帧
PING帧(类型是0x6)是一种从发送方测量最小往返时间的机制,也是一种检测空闲连接是否可用的机制。PING帧可以由任意端点发送。
PING帧的格式如下:
+---------------------------------------------------------------+
| |
| Opaque Data (64) |
除了帧头,PING帧的有效载荷中必须包含8字节的不透明数据。发送方可以在数据中包含任何值,也可以以任何方式使用这些数据。收到不包含ACK标志位的PING帧时,接收方必须在响应中回送一个包含ACK标志位的PING帧,并保证PING帧的有效载荷数据不变。相对于其它任何帧,PING的响应都应该被给予较高的优先级。
PING帧定义了以下标志位:
- ACK (0x1):设置这个标志位表明当前的PING帧是一个PING响应。端点必须在PING的响应中设置这个标志位。端点绝对不能对包含ACK的PING帧发送响应。
PING帧不与任何单独的流关联。如果收到了流标识字段不是0x0的PING帧,那么接收方必须响应一个类型是PROTOCOL_ERROR的连接错误。
接收到长度非8字节的PING帧时,必须将其视为一个类型是FRAME_SIZE_ERROR的连接错误。
GOAWAY帧
GOAWAY帧(离去帧,类型是0x7)用于初始化连接的关闭过程,或者将严重的错误通知给对端。GOAWAY帧允许端点优雅地停止接受新流,同时继续完成之前建立的流的处理过程。这使得服务器维护之类的管理行为能够进行。
在端点开始新流和远端发送GOAWAY帧之间,有一种内在的竞争条件。为了处理这种情况,GOAWAY帧包含了在当前连接中最后一个由远端初始化的流的标识符(下面简称为“最后流标识”),这个流已经或可能被GOAWAY的发送方处理过了。例如,如果服务端发送一个GOAWAY帧,被标识的流就是由客户端初始化的标识数值最大的流。
一旦GOAWAY帧被发送出去,对于那些由接收方初始化的、流标识的值大于“最后流标识”的流上面的帧,就会被发送方忽略掉。GOAWAY帧的接收方一定不能在连接上打开额外的流,虽然可以为新流建立一个新的连接。
如果GOAWAY帧的接收方已经在具有较大流标识(相对于GOAWAY帧中的“最后流标识”)的流上发送了数据,这些流将不会被处理。GOAWAY帧的接收方可以认为这些流好像从来没有被创建过一样,从而允许这些流稍后在一个新的连接上重试。
端点应该总是在关闭连接前发送GOAWAY帧,这样远端就能知道一个流是否已经被部分处理了。例如,一个HTTP客户端发送了一个POST请求,与此同时,服务端关闭了连接,那么,如果服务端没有发送GOAWAY帧来表明它已经处理了哪些流,客户端无法得知服务端是否已经开始处理这个POST请求。
针对行为异常的远端,端点可以选择不发送GOAWAY帧就关闭连接。
GOAWAY帧可能不会立即关闭连接。对于不再使用连接的GOAWAY帧的接收方,仍然应该在终止连接前发送一个GOAWAY帧。
GOAWAY帧的格式如下:
+-+-------------------------------------------------------------+
GOAWAY帧未定义任何标志位。
GOAWAY帧应用于整个连接,而不是某个流。端点必须将带有非0x0流标识的GOAWAY帧视为一个类型为PROTOCOL_ERROR的连接错误。
GOAWAY帧中的“最后流标识”包含具有最高数值的流标识,这个流可能已经被GOAWAY帧的发送方进行了某些处理,也可能尚未处理。所有标识小于或等于“最后流标识”的流都已经被按照某种方式处理过了。如果没有流被处理,那么“最后流标识”可以被设置为0。注意,这里所说的“被处理”是指流中的数据已经被传给软件的更高层,并可能被进行了某些处理。
如果一个连接没有发送GOAWAY帧就终止了,那么“最后流标识”就是尽可能大的有效流标识符。
连接关闭之前,在流标识小于或等于“最后流标识”的、尚未完全关闭的流上,重试请求、事务或任何协议活动都是不可能的,但HTTP的GET、PUT和DELETE等幂等操作是例外。任何使用更大流标识(相对于“最后流标识”)的协议活动都可以安全地在新的连接上重试。
流标识小于或等于“最后流标识”的流上的协议活动仍然可能成功地完成。GOAWAY帧的发送方可以优雅地关闭连接,方法是:发送GOAWAY帧并保持连接处于“打开”状态直到所有正在处理的流都完成。
如果环境改变,那么端点可以发送多个GOAWAY帧。例如,在优雅关闭期间,端点发送了一个错误码是NO_ERROR的GOAWAY帧,然后,突然遇到了需要立即终止连接的情况。从最后一个GOAWAY帧中接收到的“最后流标识”表明了哪些流可能已经被处理了。端点一定不能增加“最后流标识”的值,因为远端可能已经在另外一个连接上重试未完成的请求了。
当服务端关闭连接时,不具备重试请求能力的客户端会丢失所有正在发送的请求。这一点尤其适用于那些可能不提供HTTP/2服务的中间代理。正在尝试优雅关闭连接的服务端应该发送一个初始的GOAWAY帧,这个帧的“最后流标识”是2^32-1,并且设置了NO_ERROR错误码。这就告诉客户端连接即将关闭,禁止发送进一步的请求。在等待至少一次往返时间之后,服务端可以发送另外一个GOAWAY帧并更新“最后流标识”。这保证了连接可以被干净地关闭,不会丢失请求。
在发送了GOAWAY帧之后,发送方可以丢弃由接收方初始化的、流标识小于“最后流标识”的流上的帧。然而,任何修改连接状态的帧都不能被完全忽略。例如,HEADERS、PUSH_PROMISE和CONTINUATION等帧必须被最低限度地处理,来保证为报头压缩所维护的状态的一致性;类似地,DATA帧必须按照连接的流量控制窗口的规则进行计数。对这些帧处理失败会导致流量控制或报头压缩状态的不同步。
GOAWAY帧也包含32比特位的错误码,表明关闭连接的原因。
端点可以在GOAWAY帧的有效载荷上追加不透明数据。附加调试数据仅用于诊断目的,不携带语义值。附加调试信息可以包含安全敏感或隐私敏感的数据。日志记录或以其它方式永久存储的调试数据必须有足够的保障措施,以防止未经授权的访问。
WINDOW_UPDATE帧
WINDOW_UPDATE帧(窗口更新帧,类型是0x8)用于实现流量控制。
流量控制在两个层面上操作:在每个单独的流上和在整个连接上。
这两种方式的流量控制都是逐跳的(HTTP的一跳,而非IP路由的一跳),也就是说,只在两个端点之间进行。中间代理不在依赖的连接之间转发WINDOW_UPDATE帧。然而,任何接收方的数据传输限制可以间接导致流量控制信息向原始发送方传播。
流量控制仅用于确定受流量控制规则支配的帧,在这里介绍的各种帧中,只有DATA帧一种。不受流量控制的帧必须被接收和处理,除非接收方无法为处理帧而分配资源。接收方如果无法接收一个帧,可以响应一个类型是FLOW_CONTROL_ERROR的流错误或连接错误。
+-+-------------------------------------------------------------+
如上图所示,WINDOW_UPDATE帧的有效载荷包含一个保留比特位,外加一个无符号31位整数。这个整数表明发送方在现有的流量控制窗口还可以再发送的字节数。流量控制窗口增量的有效范围是1-2^31-1 (2,147,483,647)字节。
WINDOW_UPDATE帧未定义任何标志位。
WINDOW_UPDATE帧可以具体指定某个流或整个连接。在前一种情况中,帧的流标识表明受影响的流;在后一种情况中,0表示整个连接都受这个帧的影响。
接收方收到流量控制窗口增量是0的情况,必须将其视为类型是PROTOCOL_ERROR的流错误。连接上的流量控制窗口错误必须被视为连接错误。
WINDOW_UPDATE帧可以由已经发送了包含END_STREAM标志位的帧的远端来发送。这意味着接收方可以在处于“半关闭(远程)”或“关闭”状态的流上接收WINDOW_UPDATE帧。接收方不能将此种情况视为错误。
收到服从流量控制的帧(DATA帧)的接收方必须一直计算其对连接流量控制窗口的影响,除非接收方将它视为连接错误。即使帧存在错误这也是必要的。发送方根据流量控制窗口对帧进行计数,但是如果接收方不计数,流量控制窗口在发送方和接收方会变得不同。
长度非4字节的WINDOW_UPDATE帧必须被视为类型是FRAME_SIZE_ERROR的连接错误。
流量控制窗口
HTTP/2的流量控制窗口是利用每个发送方在每个流上维护的一个窗口来实现的。流量控制窗口是个简单的整数值,表明允许发送方发送多少字节的数据,因此,它的大小是接收方缓冲区容量的一个衡量。
可以使用两个流量控制窗口:流的流量控制窗口和连接的流量控制窗口。发送方不能发送长度超出接收方公布的任意一个流量控制窗口可用空间的服从流量控制的帧(DATA帧)。即使两个流量控制窗口都没有可用空间,设置了END_STREAM标志位的、长度是0的帧(空的DATA帧)也仍然可以被发送。
流量控制计算中,9字节的帧头不参与计数。
发送完服从流量控制的帧(DATA帧),发送方根据传输的帧的长度来减少两个流量控制窗口的可用空间。
帧的接收方在它消耗数据并释放流量控制窗口空间的时候发送一个WINDOW_UPDATE帧。用于流级别和连接级别的WINDOW_UPDATE帧将被分别发送。
接收到WINDOW_UPDATE帧的发送方根据帧中指定的数量来更新窗口的大小。
发送方一定不能允许流量控制窗口超过2^31-1字节。如果发送方接收到的WINDOW_UPDATE帧导致流量控制窗口超出这个最大值,那么它必须适当地终止这个流或者这个连接:对于流,发送一个错误码是FLOW_CONTROL_ERROR的RST_STREAM帧;对于连接,发送一个错误码是FLOW_CONTROL_ERROR的GOAWAY帧。
从发送方发出的服从流量控制的帧(DATA帧)和从接收方发出的WINDOW_UPDATE帧相对于彼此都是完全异步的。这个属性允许接收方积极地更新发送方维护的窗口大小来防止流失速停转。
初始流控窗口大小
当HTTP/2连接第一次被建立时,新创建的流被赋予一个初始的流控窗口大小:65535字节。整个连接的流量控制窗口大小也是65535字节。两端可以通过在连接序言的SETTINGS帧中设置SETTINGS_INITIAL_WINDOW_SIZE值来为新创建的流调整初始窗口大小。连接的流量控制窗口只能通过WINDOW_UPDATE帧来修改。
在接收到设置了SETTINGS_INITIAL_WINDOW_SIZE值的SETTINGS帧之前,端点只能使用默认的初始流控窗口大小来发送服从流量控制的帧(DATA帧)。类似地,连接的流量控制窗口也被设置为默认的初始值,直到收到WINDOW_UPDATE帧为止。
除了可以修改非活跃流的流量控制窗口之外,SETTINGS帧也可以修改具有活跃流量控制窗口的流(也就是处于“打开”或“半关闭(远程)”状态的流)的初始流量窗口大小。当SETTINGS_INITIAL_WINDOW_SIZE的值变化时,接收方必须根据新旧值的差值调整它所维护的所有流的流量控制窗口。
SETTINGS_INITIAL_WINDOW_SIZE的变化可能导致流量控制窗口的可用空间变为负数。发送方必须追踪值为负数的流量控制窗口,并且,在它收到使得流量控制窗口变为正数的WINDOW_UPDATE帧之前,绝对不能发送新的服从流量控制的帧(DATA帧)。
例如,如果客户端在连接建立后立即发送60KB数据,而服务端设置初始流量控制窗口大小为16KB。当客户端收到SETTINGS帧时,它会重新计算可用的流量控制窗口为-44KB。客户端保持值为负数的流量控制窗口直到WINDOW_UPDATE帧将窗口恢复为正数。在此之后,客户端可以继续发送数据。
SETTINGS帧不能修改连接级别的流量控制窗口。
对SETTINGS_INITIAL_WINDOW_SIZE的修改导致任意一个流量控制窗口的大小超过最大尺寸时,端点必须将其视为类型是FLOW_CONTROL_ERROR的连接错误。
减小流的窗口大小
希望使用比当前窗口更小的流量控制窗口的接收方可以发送一个新的SETTINGS帧。然而,接收方必须准备好接收比新窗口大的数据的准备,因为发送方可能在处理SETTINGS帧之前发送了比新窗口大的数据。
在发送完减小初始流量控制窗口大小的SETTINGS帧之后,接收方可以继续处理超出流量控制窗口限制的流。由于这些流可以被继续处理,接收方不能立即减小它为流量控制窗口保留的空间。这些流的进度可以停止,因为需要WINDOW_UPDATE帧来让发送方继续发送数据。一种替代方法是:接收方为这些受影响的流发送一个带有FLOW_CONTROL_ERROR错误码的RST_STREAM帧。
CONTINUATION帧
CONTINUATION帧(延续帧,类型是0x9)用于延续一系列报头块片段。可以发送任意数量的CONTINUATION帧,只要前面的一帧和当前的CONTINUATION帧在同一个流中,并且,前面的一帧是没有设置END_HEADERS标志位的HEADERS、PUSH_PROMISE或CONTINUATION帧。
+---------------------------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
如上图所示,CONTINUATION帧的有效载荷包含一个报头块片段。
CONTINUATION帧定义了以下标志位:
- END_HEADERS (0x4):当设置了此标识时,表明当前的CONTINUATION帧结束了一个报头块。如果没有设置END_HEADERS标志位,那么当前CONTINUATION帧后面必须跟随另外一个CONTINUATION帧。接收方必须将收到其它类型的帧或者在其它流上收到帧的情况视为类型是PROTOCOL_ERROR的连接错误。
CONTINUATION帧会修改HTTP/2的连接状态。
CONTINUATION帧必须与一个流关联。如果收到的CONTINUATION帧的流标识是0x0,那么,接收方必须响应一个类型为PROTOCOL_ERROR的连接错误。
CONTINUATION帧的前面必须是没有设置END_HEADERS标志位的HEADERS、PUSH_PROMISE或CONTINUATION帧。接收方观察到与此规则冲突的情况时,必须响应一个类型是PROTOCOL_ERROR的连接错误。