在一个HTTP/2的连接中, 流是服务器与客户端之间用于帧交换的一个独立双向序列. 流有几个重要的特点:
- 一个HTTP/2连接可以包含多个并发的流, 各个端点从多个流中交换frame
- 流可以被客户端或服务器单方面建立, 使用或共享
- 流也可以被任意一方关闭
- frames在一个流上的发送顺序很重要. 接收方将按照他们的接收顺序处理这些frame. 特别是
HEADERS
和DATA
frame的顺序, 在协议的语义上显得尤为重要. - 流用一个整数(流标识符)标记. 端点初始化流的时候就为其分配了标识符.
下图展示了流的生存周期:
+--------+
send PP | | recv PP
,--------| idle |--------.
/ | | \
v +--------+ v
+----------+ | +----------+
| | | send H / | |
,------| reserved | | recv H | reserved |------.
| | (local) | | | (remote) | |
| +----------+ v +----------+ |
| | +--------+ | |
| | recv ES | | send ES | |
| send H | ,-------| open |-------. | recv H |
| | / | | \ | |
| v v +--------+ v v |
| +----------+ | +----------+ |
| | half | | | half | |
| | closed | | send R / | closed | |
| | (remote) | | recv R | (local) | |
| +----------+ | +----------+ |
| | | | |
| | send ES / | recv ES / | |
| | send R / v send R / | |
| | recv R +--------+ recv R | |
| send R / `----------->| |<-----------' send R / |
| recv R | closed | recv R |
`----------------------->| |<----------------------'
+--------+
send: 发送这个frame的终端
recv: 接受这个frame的终端
H: HEADERS帧 (隐含CONTINUATION帧)
PP: PUSH_PROMISE帧 (隐含CONTINUATION帧)
ES: END_STREAM标记
R: RST_STREAM帧
该图只展示了流的状态转换以及frame和标记如何对转换产生影响. 这方面, CONTINUATION
frames不会导致状态的转换, 他们只是跟在HEADERS
或PUSH_PROMISE
frame后面的有效组成部分.
状态转换的用途, 对于设置了END_STREAM
标记的frame来说, END_STREAM
被当做一个分开的事件处理. 设置了END_STREAM
标记的HEADERS
frame会导致两次状态转换.
在传输过程中, 每个端点对流状态的主观认识可能不同. 这些终端不会协商流的创建, 都是由终端独立创建的. 端点的流状态不同会带来负面影响: 在发送了RST_STREAM
之后流处于关闭状态, 而frame可能在流关闭之后才到达.
流有如下状态:
-
idle
所有流最初状态都是idle
.
下面描述了流从idle
状态到其它状态的几种可能转换:-
发送或接收到一个
HEADERS
frame会使流状态变换open
. 流标识符的选择参见5.1.1里的描述. 收到相同的HEADERS
frame会导致流立即变为half-close
状态. -
(Sending a PUSH_PROMISE frame on another stream reserves the idle stream that is identified for later use.)在另一个流上发送一个
PUSH_PROMISE
frame 被标识为以后使用. 预留流的状态对应转换到reserved (local)
. -
(Receiving a PUSH_PROMISE frame on another stream reserves an idle stream that is identified for later use.)在另一个流上接收一个
PUSH_PROMISE
frame 被标识为以后使用. 预留流的状态对应转换到reserved (remote)
. -
注意
PUSH_PROMISE
frame并不在idle流上发送, 只是promised流的ID字段引用了新的reserved流.在
idle
状态接收到任何非HEADERS
或PUSH_PROMISE
frame必须视为连接错误, 错误类型为PROTOCOL_ERROR
-
-
reserved (local)
处于这种状态的流表示它已经发送了一个PUSH_PROMISE
frame并成为promised流.PUSH_PROMISE
frame通过关联一个由远程对等点初始化的流来转换idle流到reserved流.
处于这个状态的流, 只有下面的几种可能状态转换:-
端点发送一个
HEADERS
frame, 流进入half-closed (remote)
状态. -
任何一个端点发送一个
RST_STREAM
frame, 流变成closed
状态. 这将释放一个流保留的资源.端点不准发送除
HEADERS
,RST_STREAM
或PRIORITY
之外任何类型的frame.
这一状态可能收到
PRIORITY
或WINDOW_UPDATE
frame. 除了RST_STREAM
,PRIORITY
以及WINDOW_UPDATE
frame之外, 收到其他类型的frame必须视为PROTOCOL_EROR
类型的连接错误. -
-
reserved (remote)
如果一个流已被远程对等点保留, 状态就会变成reserved(remote)
.
可能的转换如下:- 收到一个HEADERS frame导致状态变为half-close(local)
- 任何端点发送一个
RST_STREAM
frame会导致状态变成closed
, 并释放流保留的资源.
端点可以发送一个PRIORITY frame以重新确定reserved流的优先级次序. 不允许发送除RST_STREAM, WINDOW_UPDATE或PRIORITY之外的frame.
在一个流上拿到非HEADERS, RST_STREAM或PRIORITY的frame必须视为
PROTOCOL_EROR
类型的连接错误. -
open
任何一对等方可以使用open状态的流发送任意类型的frame. 这一状态下, 发送方会监视给出的流级别和流控范围.在任意一方发送设置了END_STREAM标记的frame后, 流状态会变为half-closed的其中一个状态: 如果一方发送了该frame, 其流变为half-closed(local); 如果一方收到该frame, 流变为half-closed(remote).
在这个状态发送RST_STREAM frame可以使状态立即变成closed.
-
half-closed (local)
处于这个状态的流不能发送除WINDOW_UPDATE, PRIORITY以及RST_STREAM之外的frame.收到一个标记了END_STREAM的frame或者发送一个RST_STREAM frame, 都会使状态变成closed.
端点允许接收任意类型的frame. 便于后续接收用于流控的frame, 使用WINDOW_UPDATE frame提供流控credit很有必要. 接收方可以选择忽略WINDWO_UPDATE frame, (which might arrive for a short period after a frame bearing the END_STREAM flag is sent.)
收到的PRIORITY frame用于重定流的优先级次序(依据流的标记而定)
-
half-closed (remote)
处于这个状态的流,对端不再用来发送frame了. 并且端点也无需继续维护接收方流控窗口.如果端点收到额外的frame,并且不是WINDOW_UPDATE, PRIORITY或RST_STREAM,那么必须响应一个类型为STREAM_CLOSED的流错误.
这一状态下的流可以发送任意类型的frame. 端点仍会继续监视已知的流级别和流控范围.
发送一个END_STERAM标记的frame或任意一个对等方发送了RST_STREAM frame都会使流变为closed.
-
closed
closed标识终止状态.在一个closed的流上不允许发送PRIORITY之外的其他frame. 端点在收到RST_STREAM frame后又收到非PRIORITY的frame的话, 一定被视为流错误对待(类型STREAM_CLOSED).
同样, 收到END_STREAM标记后又收到非如下描述的frame, 会触发一个连接错误(类型STREAM_CLOSED):
发送了包含END_STREAM标记的DATA或HEADERS frame后的一小段时间内, 允许WINDOW_UPDATE或RST_STREAM frame被接收. 直到远程对等端收到并处理了RST_STERAM或包含END_STREAM标记的frame, 才可以发送这些类型的frame. 假如在发送了END_STREAM后已明显过了超时时间, 这时却再次收到frame, 尽管终端可以选择把这个frame当成PROTOCOL_ERROR类型的连接错误来处理, 但无论如何最终必须忽略这种情况下收到的WINDOW_UPDATE或RST_STREAM frame.
PRIORITY帧可从closed流上发到优先级更高的流(取决于closed流). 终端应该处理PRIORITY帧, 尽管他们可能因为流已经从依赖树中移除而被忽略.
如果是发送RST_STREAM帧的原因让状态转换到了closed, 收到RST_STREAM的对等端这时可能已经发送了RST_STREAM或者入队等待发送中, 但是已经在流上传输的帧是不可以被撤销的. 这时, 终端必须忽略从closed的流上再取得的帧, 如果这个closed流已经发送了RST_STREAM帧. 终端也可以选择一个超时时间, 忽略在此之后到达的帧, 并一律视作错误.
在发送了RST_STREAM之后收到的流控帧(比如DATA帧)也会被用于计算当前连接的流控窗口.(are counted toward the connection flow-control window.) 尽管这些帧有可能被忽略掉, 但是因为他们在发送方收到RST_STREAM之前被发送了, 所以发送方仍有可能会根据这些帧计算流控窗口大小.
终端发送了RST_STREAM帧之后可以再接收一个PUSH_PROMISE帧. PUSH_PROMISE帧会将流状态变为reserved即使相关的流已经被重置. 因此需要一个RST_STREAM帧去关闭不再需要的promised流.
本文档中没有给出更具体说明的地方, 对于收到的那些未在上述状态描述中明确认可的帧, 协议实现上应该视这种情况为一个类型为PROTOCOL_ERROR的连接错误. 另外注意PRIORITY帧可以在流的任何一个状态被发送/接收. 忽略未知类型的帧.
每个流都用31位无符号整型标识. 客户端初始化流时必须使用奇数做标识, 而那些被服务器初始化的流则要使用偶数做标识. 注: 流标识符0x0用于连接控制消息, 不能用于建立一个新的流.
用于升级到HTTP/2的HTTP/1.1请求会以一个0x1为标识的流响应. 升级完成后, 客户端流0x1处于half-closed(local)状态. 因此, 0x1不能被客户端(从HTTP/1.1升级过来的)选作新的流标示符.
新建立的流标识符必须在数字排序上大于所有由该端系统已打开或保留的流. 这个要求制约了HEADERS帧打开的流和PUSH_PROMISE帧保留的流. 当接收到不期望的标识符时, 端系统必须响应一个类型为PROTOCOL_ERROR的连接错误.
首次使用一个新的流标识符会隐式关闭所有该端系统初始化的处于idle状态且标识符更小的流. 就是说, 如果一个客户端通过7号流发送了一个HEADERS帧, 并且它尚未使用5号流发送过帧, 那么当7号流上的第一个帧已发送或接收时, 5号流就会变为closed状态.
流标识符不能被重用. 而长连接会占用端系统的流标识符可用空间. 如果一个客户端无法建立新的流了, 那么他可以选择建立新连接来创建新的流. 同样, 当服务器不能建立新的流时, 可以发送一个GOAWAY帧以便客户端强制打开一个到服务器的新连接来创建新的流.
一个对等端可以通过SETTINGS帧的SETTINGS_MAX_CONCURRENT_STREAMS
字段限制活跃状态的流的并发数, 流的最大并发量设置是特定于每个端系统而言的, 并且只作用于收到设置的对等端. 也就是说, 客户端指定服务器可用来初始化流的最大并发数, 反之亦然.
处于open或half-closed状态的流, 其数量受最大可打开流数量所限. 任意这三种状态(译注: half-closed包括remote和local两种状态)的流数量都受到SETTINGS_MAX_CONCURRENT_STREAMS
的限制. 而reserved状态的流则没这种限制.
端系统的配置不准超过由其对等端所设置的最大限度. 如果一个端系统收到了HEADERS帧, 并且帧的设置超过了该端系统的流最大并发限制, 那么这种情况必须视为一个类型为PROTOCOL_ERROR或REFUSED_STREAM的流错误来对待, 错误码的选择取决于端系统是否希望自动重试(详见8.1.4章节).
如果一个端系统希望减小SETTINGS_MAX_CONCURRENT_STREAMS
到一个比当前打开流的总量还要低的一个值, 两种情况可供选择: 或者直接关闭那些超过限制的流, 或者等待这些流完成传输.
流的多路复用会让TCP连接的使用产生竞争, 这会导致流的阻塞. 流控手段确保同一连接上的流互相之间不会产生严重的妨碍. 流控既可用于某个单独的流, 也可用于整个连接.
HTTP/2通过使用WINDOW_UPDATE帧提供了流控功能.
HTTP/2流允许在不修改原有协议的基础上使用一系列流控算法. 流控具有以下特点:
- 流控针对于连接. 所有类型的流控都作用于两个单跳的端系统之间, 而非整个端到端链路.
- 流控基于WINDOW_UPDATE帧实现. 接收者告知他们准备在一个流上以及整个连接上分别接收多少字节. 这属于基于信用的模型.
- 流控具有方向性, 且完全由接收者控制. 接收方可以选择给每个流和整个连接设置期望的任意窗口大小. 发送方必须遵循接收者施加的流控限制. 所有客户端和服务器以及中间设施, 作为接收方均独立声明其流控窗口, 并在发送时同样遵循其对等端设置的流控限制.
- 对于所有的流和连接, 流控窗口的初始大小是65535字节(2^16).
- 帧的类型也决定了是否要在这个帧上应用流控. 对于这篇文档中提到的帧, 只有DATA帧是流控的作用对象. 其他所有类型的帧均不占用流控窗口空间, 这保证重要的控制帧不会因流控被阻塞.
- 流控不能被禁用
- HTTP/2只定义了WINDOW_UPDATE帧的格式和语义. 这篇文当中并未规定接收者何时发送这个帧或是值如何设定, 同样没有规定发送者如何选择发送分组. 协议的实现允许选择任意一种符合其需求的算法.
协议的实现也需要负责: 管理基于优先级发送请求和响应, 选择避免请求的队首阻塞, 以及管理流的创建. 这些选择算法可以与任何流控算法协同工作.
流控的目的是保护端系统使之在资源约束下执行操作. 例如, 一个代理服务器需要在许多连接之间共享内存, 这些连接里可能有一条速度比较慢的上游连接和一条比较快的下游连接. 流控关注的情况是: 接收者尚未处理完一个流上的数据, 然而想要处理同一连接上其他流的数据.
如果部署时不需要这个功能, 可以把流控窗口设为最大值(2^31-1), 并在收到任意数据时发送一个WINDOW_UPDATE帧. 这相当于禁用了接收方的流控限制. 相反地, 发送方总是遵循接收方告之的流控窗口.
有资源约束情况下的部署, 可以使用流控来限制对等方可以消费的资源(比如内存). 需要注意的是, 如果不了解带宽时延积就启用了流控, 对于可用的网络资源来说这可能并非是最优的使用方案.(见译注)
即便对当前网络的带宽时延积有了充分的了解, 流控的实现也是很难的. 启用流控时, 接收方必须要立刻读取TCP的接收缓冲区, 否则达到了临界点时, 对于WINDOW_UPDATE来说, 这时既不能读取也不能采取其他行动, 就会导致死锁产生.
译注
带宽时延积: 即链路上的最大比特数,也称以比特为单位的链路长度. 计算方法: 带宽 × ACK时延
实际传输数据的时候不可能每个报文发送后就立即收到确认,如果每报文等待确认就会导致传输速率变慢,所以TCP允许在没收到上一个确认前发送下N个报文,但是发送报文不能超过窗口大小,如果窗口<带宽时延积,就会导致有些响应报文还未到达发送端时,发送端就已经达到窗口大小,这时发送端就必须重新发送之前的报文(而实际上接收端已经有响应了,只是未到达发送端),这样就会导致报文有大量重传,叠加效应就会导致传输速度远低于带宽(被无用的重传给占用了)
相反,窗口足够大,发送端连续发送的N个报文都能在窗口内收到响应,不会有数据重传,理论上的传输速度就会等于带宽值.
客户端可以给一个新的流分配一个优先级, 做法是在用来打开流的HEADERS帧中包含优先次序信息. 在其他任何时间, 可以用PRIORITY帧改变一个流的优先级.
优先次序的目的是在并发流管理的时候, 允许端系统选择它所期望的对等端分配资源方式. 最重要的是, 当发送受到限制的情况下, 可以通过优先级选择使用哪个流来传输帧.
流可以通过被标记为其他流完成的依赖的方法赋予优先级. 每个依赖被赋予一个相对权重, 它用一个数值表示. 对于依赖这个流的其他流拥有的可用资源, 这个数值决定了的他们的相对比重.
显式设置过优先级的流将被优先安排. 但这种优先并不保证一个优先级高的流能得到优先处理或者优先传输. 端系统不能用优先级强迫其对等方按照特定顺序处理并发流. 所以说, 优先级仅仅作为一种建议存在.
可以忽略消息中的优先级信息. 默认情况下使用显式提供的任何优先级值.
每个流可以显式指定依赖其他的流. 如果流被其他流所依赖, 这就表明这个流在资源的分配上优先于它的从属流.
不依赖其他任何流的流, 会被隐式指定依赖标识符为0x0的流. 换句话说, 并非实际存在的流0x0成为了依赖树的根节点.
依赖于其他流的流称作一个从属流. 上游被依赖的流称作上级流. 如果一个流的依赖项目前不在依赖树中(比如idle状态的流), 这个流就会被赋予默认优先级.
当给一个流添加依赖时, 这个流就成为上级流的一个新依赖项. 共享同一个上级流的多个从属流之间并没有严格的顺序要求. 比如, 流B和C依赖于流A, 并且流D在创建时也把A作为依赖添加进来, 那么A的依赖项顺序可以是任意的(BCD/DBC...):
A A
/ \ ==> /|\
B C B D C
一个专属(exclusive)标记允许新依赖等级的插入. 拥有专属标记的流会成为其上级流的唯一依赖项, 而上级流的其余依赖项会变成这个专属流的依赖项. 在前一个例子中, 如果使用一个对A的专属依赖创建D, 那么D就会变成B和C的上级流:
A
A |
/ \ ==> D
B C / \
B C
对于依赖树内部的一个从属流, 直到它依赖的所有流(包括上级流依赖链上的流, 直到0x0)都已处于closed状态, 或者它不可能再使用这些上级流时, 它才能被分配到到资源.
流不可以依赖自身. 否则端系统必须将此视作类型为PROTOCOL_ERROR的流错误.
所有的从属流会分配到一个介于[1, 256]之间的整数, 表示权重.
依赖于相同上级的流应该依据其权重比例分配资源. 因此, 如果权重为4的流B和权重为12的流C都依赖于流A, 并且A上不会有任何动作, 那么B会分得1/4的资源, C分得3/4的资源.
流的优先级可以通过PRIORITY帧来改变. 添加依赖会让这个流对其上级流产生依赖关系.
如果上级流重置了优先级, 它的从属流也会跟随其变化. 给重值优先级的流添加带有专属标记的依赖会导致其上级流的所有依赖项成为这个重置优先级的流的依赖项.
如果流把它的一个依赖项做为一个新的依赖, 那么之前的从属流首先会成为重置优先级的流(译注: 当前描述的流)的前一个上级的依赖项. 而那些被移动的依赖项的权重不会改变.
考虑这样一棵依赖树: B和C依赖于A, D和E依赖于C, F依赖于D. 如果把A设成D的一个依赖项(即A依赖D), 那么最后D就会替换A在树中的位置. 所有其他的依赖关系保持不变, 除非用专属标记重置了A的优先级(这会导致F直接依赖于A).
x x x x
| / \ | |
A D A D D
/ \ / / \ / \ |
B C ==> F B C ==> F A OR A
/ \ | / \ /|\
D E E B C B C F
| | |
F E E
(中间过程) (未使用专属标记) (使用了专属标记)
当一个流从依赖树中被移除时, 它的依赖项可能直接变成它直接上级流的依赖项, 之后会依据这些依赖项在直接上级流的原有依赖项所占权重比例来重新计算新的依赖项权重.
从依赖树中移除流会导致一些优先级信息的丢失. 资源是在具有相同上级流的从属流之间共享的, 这意味着如果这些从属流中有一个处于关闭状态或阻塞住了, 其上分配到任何闲置资源将重新分配到其临近的流上. 如果公共的依赖项被移除, 那么对依赖项有依赖的那些流会和上一层的流共享资源.
例如, 假如流A和流B共享同一个上级流, 并且流C和流D都依赖流A. 移除流A之前, 如果A和D不再使用, 那么流C接收流A专享的所有资源. 如果流A被移除, 它的权重会在C和D之间分配. 如果流D仍无法继续使用, 那么流C得到的资源比重会减小. 依据平等的初始权重, C能得到1/3而非1/2的可用资源.
一个流上的优先级信息正在变化时, 这个流可能变成closed了. 如果一个依赖项中有标识的流并没有对应的的优先级信息, 那么它的从属流会替代它分配一个默认的优先级. 这潜在的创建一个非最优的优先级, 因为瘤可能分配到一个并非预期的优先级.
为了避免这些问题, 在流closed之后, 端系统还应该保存一段时间流的优先级状态. 保存的时间越长, 流分得非预期或默认优先级的可能性就越小.
同样地, idle状态的流可能被分配优先级或成为其他流的上级. 这就相当于在依赖书中创建一个节点组, 同时也赋予了优先级更灵活的表达方式. idle流最初被分配了默认优先级.
流上优先级信息的保留不受SETTINGS_MAX_CONCURRENT_STREAMS值的影响, 因此为了维护这些信息可能会给一个端系统带来巨大的负担. 因此, 可能需要限制一下保存的优先级状态信息的数量.
端系统维护的额外状态信息数量可以依负载而定. 高负载情况下, 可以取消优先级状态以防止资源的不合理使用. 极端情况下, 端系统甚至可以取消活跃的或reserved流的优先级状态. 如果施加了限制, 端系统应该至少维护和每个流所设置的SETTINGS_MAX_CONCURRENT_STREAMS一样多的状态. 协议的实现也应该尽量为优先级树中正在使用的流保存状态.
如果已经保存了足够多的状态来改变优先级, 端系统通过接收一个PRIORITY帧来改变一个closed流优先级, 那么它应该修改这个流的依赖项.
所有的流初始化时都会成为0x0的非专属依赖. 推送流在初始化时则依赖于和它们相关联的流. 不管何时, 流的默认权重都是16.
HTTP/2成帧过程产生的错误会分为两类:
- 当一个错误导致整个连接不可用时, 这种情况视为一个连接错误.
- 独立于流的错误属于流错误.
错误码的列表包含在第七节.
任何妨碍帧处理过程或破坏任何连接状态的错误都属于连接错误.
当一个端系统遭遇了连接错误时应该首先通过最后与其对等端成功交互的流发送一个GOAWAY帧. GOAWAY帧包含一个用于说明连接终止原因错误码. 发送了GOAWAY帧之后, 端系统必须要关闭TCP连接.
对等方很有可能收不到这个GOAWAY帧 (RFC7230中6.6小节描述了立即关闭的连接如何导致的数据丢失). 在连接错误发生的情况下, GOAWAY帧只是"尽力而为"的尝试与其接收对等端交流, 告诉它为何终止了连接.
一个端系统可以在任何时间结束连接. 特别是, 端系统可以选择性的将流错误视为连接错误. 端系统在断开连接时应该发送一个GOAWAY帧, 所以提供的时候就发送它.
流错误是与一个特定的流有关的错误, 并且它不会影响其他流的处理.
端系统检测到流错误发生时发送一个RST_STREAM帧, 包含了发生错误的流的标识符. 除此之外还包含一个表明错误类型的错误码.
RST_STREAM是端系统可以在一个流上发送的最后一个帧. 发送RST_STREAM帧的对等端必须准备接收其远程端点发送的任何正在传输链路上或入队等待发送的帧. 可以忽略那些不会改变连接状态的帧 (比如维护头部压缩的状态或流控).
通常情况下, 端系统不应该在任何一个流上发送多于一个的RST_STERAM帧. 然而, 如果在多于一个RTT的时间里, 端系统再次从一个已经closed的流上收到了帧, 那么它就可以发送额外的RST_STREAM帧. 后面描述这一行为用于对付那些未严格遵守标准的协议实现.
为了避免循环发送, 端系统不允许再以一个RST_STREAM作为对接收到的RST_STREAM的响应.
如果TCP连接在流仍处于open或half-closed状态时关闭了或被重置, 然而受影响的流不会去自动重试.(详见8.1.4小节)
除了协议基本的约定外, 允许对其进行扩展. 在本章节描述中, 协议扩展可以用来提供额外的服务或修改协议的任一部分. 需要注意的是扩展功能仅在单个连接范围内有效.
扩展会应用到该文档中定义的协议元素上, 但不会影响扩展HTTP的现有设置, 比如定义新的方法,状态码,头部字段.
扩展允许使用新的帧类型(见4.1节), 新的settings参数(见6.5.2小节), 或者使用新的错误码(见第7章). 注册中心会管理这些扩展内容: 帧类型(见11.2节), settings参数(见11.3节), 错误码(见11.4节).
协议实现上必须忽略那些未知的或不支持的扩展值, 也必须丢弃那些未知/不支持类型的帧. 这意味着所有这些可扩展的地方都可以被安全使用而无需提前安排协商. 然而, 报头区块中是不允许出现扩展帧的; 如果出现就要将其视为一个类型为PROTOCOL_ERROR的连接错误来对待.
如果一个扩展可能改变既有协议某部分的语义, 必须在使用前商定好. 举个例子, 一个扩展改变了HEADERS帧的结构, 那么直到对等方给出一个表示接受变更的信号时, 这个扩展才能被应用. 这个例子中, 可能有必要商定一下修改后的HEADERS结构何时起效. 除此之外需要注意的是, 将任何非DATA帧用作流控属于语义上的变化, 并且只能通过协商来完成.
该文档并没有描述如何协商使用扩展的方法, 但是注意一个settings参数就可以用来实现这一目的. 如果双方都设置一个值表示愿意使用扩展, 扩展即可被启用. 如果一个settings参数用在了扩展协商上, 那么他的初始值在设置上必须表示扩展在最初是被禁用的.