1. 为什么要定义 HTTP/2
超文本传输协议 (HTTP) 是一个非常成功的协议。然而,HTTP/1.1 使用底层传输的方式([RFC7230],第 6 节)有几个特性,这些特性对当今的应用程序性能产生负面影响。
特别是,HTTP / 1.0在给定的TCP连接上一次仅允许一个未完成的请求。HTTP/1.1 增加了流水线请求(Pipelining),但这只是部分解决了请求并发性,并且仍然受到队首阻塞(Head-of-line blocking) 的影响。因此,HTTP/1.0 和 HTTP/1.1 客户端发出许多请求时,需要使用多个到服务器的连接(并发连接和域名分片)以实现并发性,从而减少延迟。
此外,HTTP 首部字段通常重复且冗长,导致不必要的网络流量以及初始TCP拥塞窗口快速填满。当在一个新的TCP连接上进行多个请求时,这可能导致过多的延迟。
HTTP/2 通过支持首部字段压缩和在同一连接上并发发送消息,让应用更有效地利用网络资源,减少感知的延迟时间。具体来说,它允许在同一连接上交替发送请求和响应消息,并为 HTTP 首部字段使用更高效的编码。它还允许对请求进行优先级排序,让更重要的请求更快完成,进一步提高性能。
由此产生的协议对网络更友好,因为与HTTP/1.x 相比,使用的TCP连接更少。这意味着与其他流的竞争更少,连接寿命更长,进而导致更好地利用可用网络容量。
最后,HTTP/2 还通过使用二进制消息帧实现了更高效的消息处理。
2. HTTP/2 协议概述
HTTP/2 为 HTTP 语义提供了优化的传输。HTTP/2 支持 HTTP/1.1 的所有核心功能,但旨在通过多种方式提高效率。
HTTP/2 中的基本协议单元是帧(第 4.1 节)。每种帧类型都有不同的用途。例如,HEADERS和DATA帧构成了HTTP/2请求和响应的基础(第8.1节);其他帧类型,如SETTINGS、WINDOW_UPDATE和PUSH_PROMISE用于支持其他 HTTP/2 功能。
请求的多路复用是通过让每个 HTTP 请求/响应交换与自己的 流(Stream) 关联来实现的(第 5 节)。流在很大程度上是相互独立的,所以一个阻塞或停止的请求或响应不会阻止其他流的进展。
流量控制(Flow control) 和 优先级(Prioritization) 确保可以有效地使用多路复用流。流量控制(第 5.2 节)有助于确保仅传输可以由接收端使用的数据。优先级(第 5.3 节)确保可以将有限的资源首先用于最重要的流。
HTTP/2 添加了一种新的交互模式,服务器可以向客户端推送响应(第 8.2 节)。即除了最初的请求响应外,服务器通过权衡一些网络使用和潜在的延迟增益,推断客户端的需要额外的向客户端推送资源。服务器通过合成一个请求来做到这一点,它作为一个PUSH_PROMISE帧发送。然后,服务器能够在单独的流上发送对合成请求的响应。
由于连接中使用的 HTTP 首部字段可能包含大量冗余数据,因此包含它们的帧会被压缩(第 4.3 节)。在一般情况下,这对请求大小具有特别有利的影响,允许将许多请求压缩为一个数据包。
2.1 文档结构
HTTP/2 规范分为四个部分:
- 启动 HTTP/2(第 3 节)介绍如何初始化一个HTTP连接。
- 帧(frame,第 4 节)和流(stream,第 5 节)层描述了 HTTP/2 帧的结构和形成多路复用流的方式。
- 帧(第 6 节)和错误(第 7 节)的定义了 HTTP/2 中使用的帧和错误类型的详细信息。
- HTTP 映射(第 8 节)和附加要求(第 9 节)描述了如何使用帧和流来表达 HTTP 语义。
虽然帧和流层的某些概念与 HTTP 隔离,但本规范并未定义完全通用的帧层。帧和流层是根据 HTTP 协议和服务器推送的需要量身定制的。
2.2 约定和术语
client: 发起 HTTP/2 连接的端点。客户端发送 HTTP 请求并接收 HTTP 响应。
connection: 两个端点之间的传输层连接。
connection error: 影响整个 HTTP/2 连接的错误。
endpoint: 连接的客户端或服务器。
frame: HTTP/2 连接中最小的通信单元,由首部和根据帧类型构造的的可变长度的字节序列组成。
peer: 一个endpoint。在讨论特定endpoint时,peer指的是对等的远程endpoint。
receiver: 正在接收frame的endpoint。
sender: 正在发送frame的endpoint。
server: 接受 HTTP/2 连接的端点。服务器接收 HTTP 请求并发送 HTTP 响应。
stream: HTTP/2 连接中的双向流( bidirectional flow)。
stream error: 单个 HTTP/2 流上的错误。
3. 启动HTTP/2
HTTP/2 连接是运行在 TCP 连接 ( [TCP] )之上的应用层协议。客户端是 TCP 连接发起者。
HTTP/2 使用与 HTTP/1.1 相同的 "http" 和 "https" URI 方案。HTTP/2 共享相同的默认端口号:"http" URI 为 80,"https" URI 为 443。因此处理目标资源URI(如example.org/foo或https:/… 的请求首先是确认上游服务器是否支持 HTTP/2。
对于 "http" 和 "https" URI,确定对 HTTP/2 是否支持的方式是不同的。第 3.2 节描述了 "http" URI 的发现。"https" URI 的发现在 第 3.3 节 描述。
3.1 HTTP/2 版本标识
本文档中定义的协议有两个标识符。
-
字符串 "h2" 标识 HTTP/2 使用Transport Layer Security (TLS) 协议.。此标识符用于TLS 应用层协议协商 (ALPN) 扩展 [TLS-ALPN] 字段以及标识 HTTP/2 over TLS 的任何地方。
"h2" 字符串被序列化为 ALPN 协议标识符,作为两个八位字节序列:0x68、0x32。
-
字符串 "h2c" 标识在明文TCP上运行 HTTP/2 的协议。此标识符用于 HTTP/1.1 升级首部字段以及标识 HTTP/2 over TCP 的任何位置。
"h2c" 字符串是从ALPN标识符空间保留的,但是描述了不使用TLS的协议。
3.2 使用 "http" URIs 启动 HTTP/2
客户端在不知道下一跳是否支持 HTTP/2 的情况下,使用 HTTP 升级机制(第6.7节 的 [RFC7230] ) 请求一个 "http" URI。客户端通过发出一个 HTTP/1.1 请求,该请求包含一个带有 "h2c" 令牌的升级首部字段。这样一个 HTTP/1.1 请求必须包含一个HTTP2-Settings头部字段。
例如:
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
在客户端可以发送 HTTP/2 帧之前,必须完整发送包含有效负载的请求。这意味着一个大的请求可能会阻塞连接的使用,直到它被完全发送。
如果初始请求与后续请求的并发性很重要,则可以使用 OPTIONS 请求来执行到 HTTP/2 的升级,代价是额外的往返。
不支持 HTTP/2 的服务器可以响应请求,就像Upgrade首部字段不存在一样:
HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html
支持 HTTP/2 的服务器以101(交换协议)响应接受升级。在101响应结束的空行之后,服务器可以开始发送 HTTP/2 帧,这些帧必须包含对发起升级的请求的响应。
例如:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
[ HTTP/2 connection ...
服务器发送的第一个 HTTP/2 帧必须是由SETTINGS帧,组成的服务器连接前言(connection preface 第 3.5 节)。收到 101 响应后,客户端必须发送一个包括SETTINGS帧的连接前言。
在升级之前发送的 HTTP/1.1 请求被分配了一个流标识符 1(第 5.1.1 节)和默认优先级值(第 5.3.5 节)。 从客户端到服务端的流 1 隐式转为half-closed状态(参见 第 5.1 节),因为该请求作为 HTTP/1.1请求完成。启动HTTP/2 连接后,流 1 将用于响应。
3.2.1 HTTP2-Settings 首部字段
从 HTTP/1.1 升级到 HTTP/2 的请求必须包含一个 HTTP2-Settings 首部字段。HTTP2-Settings 首部字段是一个特定于连接的首部字段,它包含了管理 HTTP/2 连接的参数,这些参数是在服务器接受升级请求之前提供的。
如果此首部字段不存在或存在多个首部字段,则服务器不得升级到HTTP/2的连接。服务器不能发送此首部字段。
3.3 使用 "https" URIs 启动 HTTP/2
向“https”URI 发出请求的客户端使用带有应用层协议协商 (ALPN) 扩展 TLS-ALPN 的TLS TLS [TLS12]。
基于 TLS 的 HTTP/2 使用 h2 协议标识符。h2c协议标识符绝对不能由客户端发送或由服务器选择; h2c协议标识符描述了一个不使用TLS的协议。
一旦 TLS 协商完成,客户端和服务器都必须发送连接前言(第 3.5 节)。
3.4 使用 Piror Knowledge 启动 HTTP/2
// TODO 编辑中
3.5 连接前言
在 HTTP/2中,每个端点都需要发送一个连接前言,作为使用协议的最终确认,并为 HTTP/2 连接建立初始设置。客户端和服务器各自发送不同的连接前言。
客户端连接序言以24个字节的序列开始,在十六进制表示法中是:
0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
4. HTTP 帧 (★ ★ )
一旦HTTP/2连接建立,端点就可以开始交换帧。
4.1. 帧格式
所有帧都是一个固定的 9 个字节的首部一个可变长度的负载 (payload)
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
- Length:以无符号 24 位整数表示的帧有效载荷(Payload)的长度。除非接收方为SETTINGS_MAX_FRAME_SIZE设置了更大的值,否则不得发送大于 2^14(16384) / 16M 字节。该值中不包括帧头的 9 个字节。
- Type:8bit 表示帧类型,帧类型决定了帧的格式和语义。实现必须忽略和丢弃任何具有未知类型的帧。
- Flag:定义 frame 的类型,用 8 bits 表示。帧类型决定了帧主体的格式和语义,对于特定帧类型没有定义语义的标志必须被忽略并且在发送时必须保持未设置 (0x0)。
- R:保留的 1bit 字段。该位的语义未定义,并且该位在发送时必须保持未设置 (0x0),在接收时必须被忽略。
- Stream Identifier:表示为无符号 31 位整数的流标识符(参见第 5.1.1 节)。值 0x0 保留用于与整个连接相关联的帧,而不是单个流。
帧有效载荷的结构和内容完全取决于帧类型。
4.2. 帧的大小
帧有效负载的大小受接收端在SETTINGS_MAX_FRAME_SIZE设置中发布的最大大小的限制。这个设置可以有2^14(16384) 到 2^24-1(16777215)之间的任何值。
所有实现都必须能够接收和最少处理长达 2^14字节的帧,加上 9 个字节的帧头(第 4.1 节)。描述帧大小时不包括帧头的大小。
注意:某些帧类型,例如 PING(第 6.7 节),对允许的有效负载数据量施加了额外的限制。
如果帧超过SETTINGS_MAX_FRAME_SIZE 中定义的大小,超过为帧类型定义的任何限制,或者太小而无法包含强制性帧数据,则端点必须发送错误代码FRAME_SIZE_ERROR。帧中可能改变整个连接状态的帧大小错误必须被视为连接错误(第 5.4.1 节);这包括任何带有首部块(第 4.3 节)(即HEADERS、PUSH_PROMISE和CONTINUATION)、SETTINGS 的帧,以及任何流标识符为 0 的帧。
端点不必使用帧中的所有可用空间。可以通过使用小于允许的最大大小的帧来提高响应能力。发送大帧可能会导致对发送时间敏感帧(例如RST_STREAM、WINDOW_UPDATE或PRIORITY)的延迟,如果被大帧的传输阻塞,则可能会影响性能。
4.3. 首部压缩和解压缩(★ ★ ★ )
正如在 HTTP/1 中一样,HTTP/2 中的首部字段是一个具有一个或多个关联值的名称。首部字段用于 HTTP 请求和响应消息以及服务器推送操作(参见第 8.2 节)。
首部列表(Header List)是零个或多个首部字段(header fields)的集合。当通过连接传输时,首部列表使用 HTTP 首部压缩算法 HPACK(HTTP header compression) 序列化为首部块(header block)。然后将序列化的首部块划分为一个或多个字节序列,称为首部块片段(Header Block Fragment)。并在 HEADERS(第 6.2 节)、PUSH_PROMISE(第 6.6 节)或 CONTINUATION(第 6.10 节)帧的有效载荷内传输。
Cookie首部字段( Cookie header field [COOKIE])被HTTP映射特殊处理(见 第8.1.2.5节)。
接收端通过连接的首部块的片段来重组首部块,然后解压缩该块以重构首部列表。
一个完整的头块包括:
- 设置了 END_HEADERS 标志的单个 HEADERS 或 PUSH_PROMISE 帧,或
- 未设置 END_HEADERS 标志的 HEADERS或PUSH_PROMISE帧和一个或多个CONTINUATION帧,其中最后一个 CONTINUATION 帧设置了 END_HEADERS 标志。
首部压缩是有状态的。整个连接使用一个压缩上下文和一个解压缩上下文。头块中的解码错误必须被视为COMPRESSION_ERROR 类型的连接错误。
每个首部块都作为一个互不相连的单元进行处理。首部块必须作为连续的帧序列传输,中间不能有任何其他类型或来自任何其他流的帧交错。 HEADER 帧或 CONTINUATION 帧序列中的最后一帧设置了END_HEADERS标志, PUSH_PROMISE 帧或 CONTINUATION 帧序列中的最后一帧设置了END_HEADERS标志,表示首部块结束。这让首部块在逻辑上等价于一个单独的帧。
首部块片段只能在 HEADERS 、PUSH_PROMISE 或 CONTINUATION 帧的有效载荷中发送,因为只有这些类型的帧能维护接收端的压缩上下文。接收端连接首部块片段并重新组装首部块,然后解压缩首部块重建首部列表。如果接收方没有解压缩头块,则接收方必须以COMPRESSION_ERROR类型的连接错误(第 5.4.1 节)终止连接。
5. 流和多路复用
"流(Stream)" 是 HTTP/2 连接中客户端和服务器之间交换的一个独立的、双向的帧序列。流有几个重要的特征:
- 单个 HTTP/2 连接可以包含多个并发打开的流,两端之间可以交错发送不同流的帧。
- 流可以单方面建立和使用,也可以由客户端或服务器共享。
- 流可以被任一端点(endpoint)关闭。
- 在流上发送帧的顺序很重要。接收者按照接收到的顺序处理帧。特别是,HEADERS 和 DATA帧的顺序在语义上很重要。
- 流由整数标识。流标识符由发起流的端点分配给流。
5.1 流状态(★ ★ ★ )
流的生命周期如下图所示。
请注意,此图仅显示了流状态转换以及影响这些转换的帧和标志。在这方面,CONTINUATION帧不会导致状态转换;它们实际上是它们遵循的HEADERS或PUSH_PROMISE的一部分。出于状态转换的目的,END_STREAM 标志作为单独事件处理到承载它的帧;设置了 END_STREAM 标志的HEADERS帧会导致两个状态转换。
两个端点都对流的状态有主观看法,当帧在传输时可能会有所不同。端点不协调流的创建;它们由任一端点单方面创建。状态不匹配的负面影响仅限于发送RST_STREAM后的“关闭”状态,在关闭后的一段时间内可能会收到帧。
流具有以下状态:
空闲(idle)
所有流都以“空闲”状态开始。
- 发送或接收HEADERS帧会导致流变为"打开(oepn)"。流标识符(stream identifier)的选择如第 5.1.1 节节所述。相同的HEADERS帧(END_STREAM)也可能导致流立即变为"半关闭(half-closed)"。
- 在另一个流上发送PUSH_PROMISE帧将保留标识为稍后使用的空闲流。保留流的流状态转换为"reserved(local)"。
- 在另一个流上接收PUSH_PROMISE帧会保留一个空闲流,该流被标识以供以后使用。保留流的流状态转换为"reserved(remote)"。
- 请注意,PUSH_PROMISE帧不是在空闲流上发送,而是在 Promised Stream ID 字段中引用新保留的流。
在此状态下在流上接收除 HEADERS 或 PRIORITY 之外的任何帧必须被视为类型PROTOCOL_ERROR的连接错误。
reserved (local):
处于“reserved (local)”状态的流是通过发送PUSH_PROMISE帧而被承诺的流。PUSH_PROMISE帧通过将流与由 peer 发起的流(open状态)相关联来保留空闲流。
在此状态下,只能进行以下转换:
- 端点可以发送一个HEADERS帧。这会导致流以
half-closed(remote)状态打开。 - 任一端点都可以发送RST_STREAM帧以使流变为
close。这会释放保留流。
在此状态下,端点不得发送除HEADERS、RST_STREAM或PRIORITY之外的任何类型的帧。接收到 RST_STREAM、PRIORITY、WINDOW_UPDATE 以外的帧被视为 PROTOCOL_ERROR。
reserved (remote):
- 接收一个HEADERS帧。这会导致流转化为
half-closed(local)。 - 任一端点都可以发送RST_STREAM帧以使流变为
close。这会释放保留流。
端点可以在这种状态下发送一个PRIORITY帧来重新确定保留流的优先级。在此状态下,端点不得发送除RST_STREAM、WINDOW_UPDATE或PRIORITY之外的任何类型的帧。
在处于此状态的流上接收除HEADERS、RST_STREAM或PRIORITY之外的任何类型的帧必须被视为PROTOCOL_ERROR类型的连接错误。
open:
处于open状态的流可由两个 peers 用于发送任何类型的帧。在此状态下,发送 peer 遵守公布的stream-level和flow-control(第 5.2 节)。
在此状态下,任一端都可以发送带有 END_STREAM 标识的帧,发送方会转入 half-closed(local) 状态;接收方会转入 half-closed(remote) 状态.
任一端都可以发送RST_STREAM 帧,这会使流立即进入 closed 状态.
half-closed (local):
此状态下的流不能发送 WINDOW_UPDATE、PRIORITY、RST_STREAM 以外的帧。
当接收到包含 END_STREAM 标志的帧或任一 peer 发送RST_STREAM帧时,流从此状态转换为close。
端点可以在此状态下接收任何类型的帧。为了继续接收流控制帧,需要使用WINDOW_UPDATE帧提供流控制信用。在这种状态下,接收端可以忽略WINDOW_UPDATE帧,它可能在带有END_STREAM标记的帧发送后的短时间内到达。
此状态下的流收到的 PRIORITY 帧用以重新调整流的依赖关系顺序。
half-closed (remote):
peer 将不再使用half-closed (remote)状态的流来发送帧,端点不再有义务维护接收器流量控制(flow-control)窗口。
一个端点在此状态的流上接收到 WINDOW_UPDATE、PRIORITY、RST_STREAM 以外的帧,则必须响应一个 STREAM_CLOSED 流错误。
此状态下的流可以被端点用于发送任意类型的帧,且此状态下该端点仍会准守公布的流级别(stream-level)和 流量控制(flow-control)限制。
当接收到包含 END_STREAM 标志的帧或任一 peer 发送RST_STREAM帧时,流从此状态转换为close。
closed:
端点不得在关闭的流上发送PRIORITY以外的帧。在收到RST_STREAM后接收除PRIORITY之外的任何帧的端点必须将其视为STREAM_CLOSED类型的流错误(第 5.4.2 节)。类似地,在接收到设置了 END_STREAM 标志的帧后接收任何帧的端点必须将其视为STREAM_CLOSED类型的连接错误(第 5.4.1 节),除非该帧如下所述被允许。
在发送包含 END_STREAM 标志的DATA或HEADERS帧后,可以在此状态下短时间内接收WINDOW_UPDATE或RST_STREAM帧。在远程 peer 接收并处理RST_STREAM或带有 END_STREAM 标志的帧之前,它可能会发送这些类型的帧。端点必须忽略在此状态下接收到的WINDOW_UPDATE或RST_STREAM帧,尽管端点可以选择将在发送 END_STREAM 后很长时间到达的帧视为PROTOCOL_ERROR类型的连接错误。
PRIORITY帧可以在关闭的流上发送,以对依赖于关闭的流的流重新进行优先级排序。端点应该处理PRIORITY帧,尽管如果流已经从依赖树中移除,它们可以被忽略。
如果由于发送RST_STREAM帧而达到此状态,则接收RST_STREAM的 peer 可能已经在无法撤回的流上发送或排队发送帧。端点必须忽略它在发送RST_STREAM帧后在关闭的流上接收的帧。端点可以选择限制它忽略帧的时间段并将在此时间之后到达的帧视为错误。
发送RST_STREAM后收到的流控制帧(即DATA)计入连接流控制窗口。即使这些帧可能会被忽略,因为它们是在发送方接收到RST_STREAM之前发送的,发送方将考虑对流量控制窗口进行计数的帧。
端点在发送RST_STREAM后可能会收到PUSH_PROMISE帧。PUSH_PROMISE导致流变为“保留”,即使关联的流已被重置。因此,需要一个RST_STREAM来关闭不需要的承诺流。
HTTP 请求/响应交换的状态转换示例可以在第 8.1 节中找到。服务器推送的状态转换示例可以在第8.2.1和8.2.2节中找到。
5.1.1. 流标识符
流由一个无符号的 31 位整数标识。客户端发起的流必须使用奇数流标识符;服务器发起的流必须使用偶数流标识符。零(0x0) 的流标识符用于连接控制消息;0x0流标识符不能用于建立新的流。
HTTP/1.1 升级到 HTTP/2 的请求(见 第 3.2 节)服务端将以一个流标识符(0x1)响应。升级完成后流0x1在客户端将会转入half-closed(local)状态。因此,从 HTTP/1.1 升级的客户端不能将流0x1选择为新的流标识符。
新建立的流的标识符在数字上必须大于发起端点已打开或保留的所有流已使用过的数字(也就是说流标识符是递增的)。这适用于使用 HEADER 帧打开的流和使用 PUSH_PROMISE 帧保留的流。接收到意外流标识符的端点必须响应一个协议类型为 PROTOCOL_ERROR 的连接错误。
第一次使用新的流标识符会隐式关闭可能是由较低流标识符的 peer 发起的所有处于idel状态的流。例如,如果客户端在流 7 上发送HEADERS帧,但从未在流 5 上发送过帧,则在发送或接收流 7 的第一个帧时,流 5 转换为close状态。
流标识符不能重复使用。长期连接可能导致端点耗尽流标识符的可用范围。新流标识符耗尽的客户端可以为新流(stream)建立新连接(connection)。无法建立新流标识符的服务器可以发送GOAWAY帧,以便客户端被迫为新流打开新连接。
5.1.2. 流并发
peer 可以使用SETTINGS帧内的SETTINGS_MAX_CONCURRENT_STREAMS参数(见第 6.5.2 节)限制并发活动流的数量。最大并发流设置特定于每个端点,并且仅适用于接收设置的 peer 。也就是说,客户端指定服务器可以使用的最大并发流数,服务器指定客户端可以使用的最大并发流数。
5.2. 流量控制(★ ★ )
使用流进行多路复用会引入对 TCP 连接的争用,从而导致流被阻塞。流量控制方案确保同一连接上的流不会相互破坏性地干扰。流控制既用于单个流,也用于整个连接。
HTTP/2 通过使用 WINDOW_UPDATE 帧(第 6.9 节)提供流量控制。
5.2.1. 流量控制原理
HTTP/2流控制的目的是在不需要更改协议的情况下使用各种流控制算法。HTTP/2中的流控制具有以下特点:
- 流控制特定于连接。两种类型的流量控制都在单跳的端点之间,而不是在整个端到端路径上。
- 流量控制基于WINDOW_UPDATE帧。接收端通告他们准备在一个流和整个连接上接收多少字节。这是一个基于信用的计划。
- 流量控制是有方向的,由接收方整体控制。接收方可以为每个流和整个连接设置任意的窗口大小。发送方必须遵守接收方设置的流量控制限制。客户端、服务端和中间代理作为接收方时都独立地公布各自的流量控制窗口,作为发送方时都遵守 peer 的流量控制设置。
- 对于新流和整个连接,流控制窗口的初始值是 65,535 个字节。
- 帧的类型决定了流量控制是否适用于帧。目前,只有 DATA 帧会受流量控制影响,所有其它类型的帧并不消耗公布的流量控制窗口的空间。这保证了重要的控制帧不会被流量控制阻塞。
- 流量控制不能被禁用。
- HTTP/2 只定义了WINDOW_UPDATE帧的格式和语义(第 6.9 节)。本文档没有规定接收方如何决定何时发送这个帧和什么样的值,也没有规定发送方如何选择发送数据包。实现可以选择任何适合自己需要的算法。
实现还需要负责管理如何根据优先级发送请求和响应,选择如何避免请求的队头阻塞,以及管理新流的创建。这些算法的选择可以与任何流量控制算法交互。
5.2.2. 适当使用流控制
定义流控制是为了保护在资源约束下运行的端点。例如,代理需要在多个连接之间共享内存,并且可能有一个缓慢的上游连接和一个快速的下游连接。流控制解决了这样的情况:接收端无法处理一个流上的数据,但希望继续处理同一连接中的其他流。
不需要此功能的部署可以通告最大大小 (2^31 -1)的流量控制窗口,并且可以通过在接收到任何数据时发送WINDOW_UPDATE帧来维护此窗口。这有效地禁用了该接收器的流量控制。相反,发送方始终受制于接收方通告的流量控制窗口。
具有受限资源(例如,内存)的部署可以采用流控制来限制 peer 可以消耗的内存量。但是请注意,如果在不了解带宽延迟产品的情况下启用流量控制,这可能导致可用网络资源的次优使用(参见[RFC7323])。
即使充分了解当前的带宽延迟产品,流量控制的实现也可能很困难。使用流量控制时,接收方必须及时从 TCP 接收缓冲区中读取数据。当关键帧(例如WINDOW_UPDATE)未被读取和操作时,如果不这样做可能会导致死锁。
5.3. 流优先级
// TODO
5.3.1. 流依赖
5.4 错误处理 (★ ★)
HTTP/2 框架允许两类错误:
- 导致整个连接无法使用的错误条件是连接错误。
- 单个流中的错误是流错误。
错误代码列表包含在第 7 节中。
5.4.1. 连接错误处理
连接错误是阻止进一步处理帧层或破坏任何连接状态的任何错误。
遇到连接错误的端点应该首先发送一个 GOAWAY 帧,其中包含它从其 peer 成功接收的最后一个流的流标识符。所述GOAWAY帧包括错误代码,指示为什么连接被终止。在发送错误状态的GOAWAY帧后,端点必须关闭 TCP 连接。
可能的是,该GOAWAY将不能可靠地被接收端点接收([RFC7230] ,第 6.6 节描述了立即关闭连接如何导致数据丢失)。在连接错误的情况下,GOAWAY只会尽最大努力尝试与 peer 通信,说明为什么连接被终止。
端点可以随时结束连接。特别是,端点可以选择将流错误视为连接错误。如果情况允许,端点应该在结束连接时发送GOAWAY帧。
5.4.2. 流错误处理
流错误是与特定流相关的错误,不影响其他流的处理。
检测到流错误的端点发送一个 RST_STREAM 帧,其中包含发生错误的流的流标识符。RST_STREAM 帧包含一个表示错误类型的错误码。
RST_STREAM 是终端可以在流上发送的最后一帧。发送 RST_STREAM 帧的 peer 必须准备接收任何由远端 peer 发送或排队发送的帧。这些帧可以被忽略,除非它们修改连接状态(例如首部压缩或流控制的状态)。
通常,终端不应该为任何流发送超过一个RST_STREAM帧。但是,如果终端在一个关闭的流上超过一个往返时间后接收到帧,则可以发送额外的RST_STREAM帧。允许这种行为来处理行为不当的实现。
为了避免循环,终端绝对不能发送RST_STREAM帧来响应RST_STREAM帧。
5.4.3. 连接终止
如果 TCP 连接关闭或重置而流仍处于“打开”或“半关闭”状态,则无法自动重试受影响的流(有关详细信息,请参阅 第 8.1.4 节)。
6. 帧的定义(★ ★ ★ )
该规范定义了多种帧类型,每种类型都由唯一的 8bit 类型代码标识。每个帧类型在作为一个整体或单个流的连接的建立和管理中都有不同的用途。
特定帧类型的传输可以改变连接的状态。如果端点无法保持连接状态的同步视图,连接内的成功通信将不再可能。因此,端点对状态如何受到使用任何给定帧的影响有一个共同的理解是很重要的。
6.1 DATA
数据帧(type=0x0)传递与流相关的任意可变长度的字节序列。例如,一个或多个数据帧被用来携带HTTP请求或响应的有效负载。
数据帧也可能包含填充。可以向 DATA 帧添加填充以掩盖消息的大小。填充是一种安全特性;见 第 10.7 节。
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
上图为 HEADERS 帧有效载荷(PlayLoad)格式,DATA 帧包含以下字段:
Pad Length: 一个 8-bit 字段,包含以八字节为单位的帧填充长度。此字段是有条件的(如图中的“?”所示),仅当设置了填充(PADDED)标志时才存在。
Data: 应用数据。数据量是帧有效载荷(playload)减去存在的其他字段的长度后的剩余部分。
Padding: 不包含应用程序语义值的填充字节。在发送时填充字节必须设置为零。接收端没有义务验证填充,但可以将非零填充视为PROTOCOL_ERROR类型的连接错误。
DATA 帧定义了以下标志:
-
END_STREAM (0x1): 第 0 bit 位设置为1,表示该帧是端点为识别的流发送的最后一个帧。设置此标志会导致流进入“半关闭(half-closed)”状态或“关闭(closed)”状态之一(第 5.1 节)。
-
PADDED (0x8): 设置后,第 3 bit位设置为1,表示存在 Pad Length 及 Padding 描述。
数据帧必须与流相关联。如果接收到流标识符字段为 0x0 的 DATA 帧,则接收者必须以PROTOCOL_ERROR 类型的连接错误进行响应。
数据帧受流控制,只能在流处于“打开(open)”或“半关闭(远程)(half-closed (remote))”状态时发送。 整个数据帧有效载荷包含在流控制中,包括填充长度和填充字段(如果存在)。如果接收到的 DATA 帧的流不处于“打开”或“半关闭(本地)”状态,则接收者必须以 STREAM_CLOSED 类型的流错误进行响应。
填充八位字节的总数由填充长度字段的值决定。如果填充的长度等于或大于帧有效载荷的长度,则接收方必须将此视为PROTOCOL_ERROR类型的连接错误(
6.2 HEADERS
HEADERS 帧(type=0x1)用于打开一个流(第 5.1 节),另外还携带一个头块片段。HEADERS 帧可以在处于“空闲”、“保留(本地)”、“打开”或“半关闭(远程)”状态的流上发送。下图为 HEADERS 帧有效载荷(PlayLoad)格式:
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
Header 帧可以在 "idle", "reserved (local)", "open", 或者 "half-closed (remote)"状态下在流上发送。
HEADERS 帧包含以下字段:
Pad Length: 一个 8-bit 字段,包含以八字节为单位的帧填充长度。此字段是有条件的(如图中的“?”所示),仅当设置了填充(PADDED)标志时才存在。
E: 一个bit,表示流的依赖是排他的。该字段仅在设置了 PRIORITY 标志时才出现。
Stream Dependency: 31 bit的流标识符,表示当前流所依赖的流的 id,该字段仅在设置了PRIORITY标志时才出现。
Weight: 一个无符号的 8 位整数,表示流的优先级权重。将该值加 1 以获得介于 1 和 256 之间的权重。仅当设置了 PRIORITY 标志时才会出现此字段。
Header Block Fragment: Header 块片段(第 4.3 节)。
Padding: 填充
HEADERS 帧定义了以下标志:
-
END_STREAM (0x1): 第 0 bit 位设置为1,表示该头块是端点已识别流发送的最后一个首部块。
HEADERS 帧携带 END_STREAM 标志,表示流结束。 但是,带有END_STREAM标志的 HEADERS 帧可以在同一流上后跟CONTINUATION帧。 逻辑上,CONTINUATION 帧是HEADERS帧的一部分。
-
END_HEADERS (0x4): 第 0 bit 位设置为1,表示该帧包含整个首部块并且后面没有任何CONTINUATION 帧。
-
PADDED (0x8): 第 3 bit位设置为1,表示存在 Pad Length 及 Padding 描述字段。
-
PRIORITY (0x20): 第 5 bit位设置为1,表示存在排他标志(E),Stream Dependency,Weight字段。
HEADERS 帧必须与流相关联。如果接收到流标识符字段为 0x0 的 HEADERS 帧,则接收者必须以PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)进行响应。
HEADERS 帧更改连接状态,如第 4.3 节所述。
6.3. PRIORITY
PRIORITY 帧 (type=0x2) 指定流的发送者建议的优先级(第 5.3 节)。它可以在任何流状态下发送,包括空闲或关闭的流。
+-+-------------------------------------------------------------+
|E| Stream Dependency (31) |
+-+-------------+-----------------------------------------------+
| Weight (8) |
+-+-------------+
PRIORITY 帧的有效载荷包含以下字段:
E: 一个单独 bit 的标志,表示流的依赖性是独占的。
Stream Dependency: 此流所依赖流的流标识符。
**Weight:**一个无符号的 8 位整数,表示流的优先级权重。将该值加 1 以获得介于 1 和 256 之间的权重。
6.4. RST_STREAM
RST_STREAM 帧(type=0x3)用于立即终止流。发送 RST_STREAM 以请求取消流或指示已发生错误情况。
+---------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
上图为 RST_STREAM 帧有效载荷(PlayLoad)格式,RST_STREAM 帧包含一个无符号的 32-bit 整数标识错误码(第 7 节)。错误码表示流被终止的原因。RST_STREAM 帧完全终止引用的流并使其进入“关闭(closed)”状态。在流上接收到 RST_STREAM 后,接收者不得在该上流发送除 PRIORITY 以外的帧。但是,发送端在发送RST_STREAM 帧之后必须准备接收和处理接收端在 RST_STREAM 到达之前已经发送的帧。
RST_STREAM 帧必须与流相关联。如果收到一个 RST_STREAM 帧,其流标识符为 0x0,则接收方必须将此视为PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)。
不得为处于“空闲(idle)”状态的流发送 RST_STREAM 帧。如果接收到标识空闲流的 RST_STREAM 帧,则接收方必须将此视为PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)。
6.5. SETTINGS
SETTINGS 帧的有效载荷由零个或多个参数组成,每个参数由一个无符号16-bit设置标识符和一个无符号32-bit值组成。下图为 SETTINGS 帧有效载荷(PlayLoad)格式:
+-------------------------------+
| Identifier (16) |
+-------------------------------+-------------------------------+
| Value (32) |
+---------------------------------------------------------------+
SETTINGS 帧(type=0x4)用于传递影响端点通信方式的配置参数,另外 SETTINGS 帧还用于确认这些配置参数。
SETTINGS 参数不需要协商;它们描述发送端(sending peer)的特征,然后接收端(receiving peer)使用这些特征。对于同一个参数,每个 peer 可以发布不同的值。例如,客户端可能会设置较高的初始流量控制窗口,而服务端可能会设置较低的值以节省资源。
SETTINGS 帧必须在连接开始时由两个端点发送,并且可以在连接的生命周期内的任何其他时间由任一端点发送。实现必须支持本规范定义的所有参数。
SETTINGS 帧中的每个参数将替换该参数的现有值。参数按照它们出现的顺序处理,设SETTINGS帧的接收方不需要维护其参数的当前值以外的任何状态。因此,SETTINGS 帧参数的值是接收端看到的最后一个值。
SETTINGS 帧参数由接收对端(receiving peer)确认。为了实现这一点,SETTINGS 帧定义了以下标志(flags):
- ACK (0x1): 第 0 bit位设置为1,表示已接收到对方的 SETTINGS 请求并同意设置,设置此标志的 SETTINGS 帧的有效载荷必须为空。接收到设置了 ACK 标志且 Length 字段值不是 0 的 SETTINGS 帧必须被视为FRAME_SIZE_ERROR类型的连接错误(第 5.4.1 节)。
SETTINGS 帧始作用于整个连接,而不是单个流。 SETTINGS 帧的流标识符必须为0 (0x0)。 如果端点收到流标识符字段不是 0x0 的 SETTINGS 帧,端点必须以PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)响应。
SETTINGS 帧会影响连接状态。格式错误或不完整的 SETTINGS 帧必须被视为PROTOCOL_ERROR类型的连接错误。
长度不是 6 个八位字节倍数的 SETTINGS 帧必须被视为FRAME_SIZE_ERROR类型的连接错误(第 5.4.1 节)。
6.5.1. SETTINGS 帧定义的参数
-
SETTINGS_HEADER_TABLE_SIZE (0x1):发送方通知远程端点(remote endpoint)用于解码首部块(header block)的首部压缩表(header compression table)的最大值,以字节为单位。初始值为 4,096 个字节。
-
SETTINGS_ENABLE_PUSH (0x2):此设置可用于禁用服务器推送(第 8.2 节)。如果端点接收到此参数设置为 0 的值,它不得发送 PUSH_PROMISE 帧。已将此参数设置为 0 并已确认的端点必须将 PUSH_PROMISE 帧的接收视为PROTOCOL_ERROR类型的连接错误。
初始值为1,表示允许服务器推送。除 0 或 1 以外的任何值都必须被视为PROTOCOL_ERROR类型的连接错误。
-
SETTINGS_MAX_CONCURRENT_STREAMS (0x3):指示发送方将允许的最大并发流数。此限制是定向的:它用于发送方允许接收方创建的最大流数量。初始时,此值没有限制。建议此值不小于 100,以免不必要地限制并行度。
SETTINGS_MAX_CONCURRENT_STREAMS 的值为 0 不应被端点视为特殊的情况。零值确实会阻止创建新的流;但是,对于活跃流耗尽的任何限制,也可能发生这种情况。服务器应该只在短时间内设置一个零值;如果服务器不想接受请求,关闭连接更合适。
-
SETTINGS_INITIAL_WINDOW_SIZE (0x4): 指明发送端所有流的流量控制窗口的初始大小,初始值为 2^16-1 (65,535) 个字节。此设置会影响所有流(以字节为单位)的窗口大小(请参阅第 6.9.2 节)。高于最大流量控制窗口大小 2^31 -1 的值必须被视为FLOW_CONTROL_ERROR类型的连接错误(第 5.4.1 节)。
-
SETTINGS_MAX_FRAME_SIZE (0x5): 指明发送端允许接收的最大帧有效载荷的字节数,初始值是 2^14(16384) 字节,端点通告的值必须在此初始值(2^14)和最大允许帧大小(2^24-1)之间,包括两者。超出此范围的值必须被视为PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)。
-
SETTINGS_MAX_HEADER_LIST_SIZE (0x6):通知知 peer ,发送端允许接收首部列表(header list)的最大字节数。该值基于首部字段(header filed)的未压缩大小,包括名称和值的字节长度以及每个首部字段的 32 个字节的开销。
6.5.2. Settings Synchronization
SETTINGS 中的大多数值都需要了解对端何时接收并应用和更改了参数值。为了提供这样的同步时间点,没有设置 ACK 标志的 SETTINGS 帧的接收者必须在收到后尽快应用更新后参数。
SETTINGS 帧中的值必须按照它们出现的顺序进行处理,之间不能有其他帧交错处理。 不支持的参数必须被忽略。处理完所有值后,接收方必须立即发出设置了 ACK 标志的 SETTINGS 帧。在接收到设置了 ACK 标志的 SETTINGS 帧后,更改参数的发送方可以依赖已应用的设置。
如果 SETTINGS 帧的发送方在合理的时间内没有收到确认,它可能会发出SETTINGS_TIMEOUT类型的连接错误(第 5.4.1 节)。
6.6. PUSH_PROMISE
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|R| Promised Stream ID (31) |
+-+-----------------------------+-------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
PUSH_PROMISE帧(type=0x5)用于在发送方打算发起流之前通知对端端点。PUSH_PROMISE帧包含端点计划创建的流的流标识符(Promised Stream ID),以及一组为流提供附加上下文头部信息。第 8.2 节包含对 PUSH_PROMISE 帧使用的全面描述。
Pad Length: 一个 8-bit 字段,包含以八字节为单位的帧填充长度。此字段是有条件的(如图中的“?”所示),仅当设置了填充(PADDED)标志时才存在。
R: 1 bit 的保留位。
Promised Stream ID: 31 位的无符号整数,用于标识 PUSH_PROMISE 帧保留的流,对于发送者来说该流标识符必须是可用于下一个流的有效值。
Header Block Fragment: 包含请求首部域的首部块片段。
Padding: 填充字节,没有具体语义,作用与 DATA 的 Padding 一样,存在则代表 PADDING flag 被设置。
PUSH_PROMISE 帧定义了以下标志(flags):
- END_HEADERS (0x4):第 2 bit 位设置为1,表示该帧包含整个首部块并且后面没有任何 CONTINUATION 帧。
没有设置 END_HEADERS 标志的 PUSH_PROMISE 帧后面必须跟有同一流的 CONTINUATION 帧。接收者必须将接收任何其他类型的帧或不同流上的帧视为PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)。
- PADDED (0x8): 设置后,第 3 bit位设置为1,表示存在 Pad Length 及 Padding 描述。
**PUSH_PROMISE 帧只能在由对端(客户端)发起的且处于“open”或“half-closed(remote)”状态的流上发送。**PUSH_PROMISE 帧的流标识符指示与其关联的流。如果流标识符字段指定值 0x0,则接收者必须响应类型为PROTOCOL_ERROR的连接错误(第 5.4.1 节)。
承诺的流不需要按照承诺的顺序使用。PUSH_PROMISE 只保留流标识符供以后使用。
如果对等端点的SETTINGS_ENABLE_PUSH设置设置为 0,则不得发送 PUSH_PROMISE。设置了此设置并收到确认的端点必须将 PUSH_PROMISE 帧的接收视为PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)。
PUSH_PROMISE 帧的接收者可以通过将引用承诺流标识符的RST_STREAM帧返回给PUSH_PROMISE的发送者来选择拒绝承诺的流。
6.7. PING
PING 帧(类型=0x6)是一种用于测量来自发送方的最小往返时间(round-trip time,rtt) 以及确定空闲连接是否有效的机制。PING 帧可以从任何端点发送。下图为 PING 帧有效载荷(PlayLoad)格式:
+---------------------------------------------------------------+
| |
| Opaque Data (64) |
| |
+---------------------------------------------------------------+
除了帧头之外,PING 帧必须在有效载荷中包含 8 个八个字节的不透明数据。发送方可以在这8个字节中包含任意值,并用作任何用途。
接收端收到一个不包含 ACK 标志的 PING 帧,必须回复一个带有 ACK 标志且具有相同载荷的作为响应的 PING 帧, PING 帧的响应比任何帧的优先级都要高。
PING 帧定义了以下标志:
ACK (0x1): 第 0 bit位设置为1,表示此 PING 帧是 PING 响应,端点必须在 PING 响应中设置 ACK 标志。端点不能响应设置了 ACK 标志的 PING 帧。
PING 帧不和任何单独的流相关联,如果接收到的 PING 帧的流标识符字段值不是 0x0,则接收者必须以PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)进行响应。
接收到 Length 字段不是 8 的 PING 帧必须被视为FRAME_SIZE_ERROR类型的连接错误(第 5.4.1 节)。
6.8. GOAWAY
GOAWAY 帧(type=0x7)用于关闭连接或发出警示严重错误的信号,GOAWAY 允许端点优雅的停止接受新流,同时任然完成对先前建立的流的处理。
端点启动的新流和远程发送的GOAWAY 帧存在一个竞态条件。为了处理这种情况 GOAWAY 帧包含了 peer 最后发起的流的流标识符, 例如,如果服务端发送一个 GOAWAY 帧,则标识的流是由客户端启动的编号最高(highest-numbered)的流。
发送方一旦发送了 GOAWAY 帧,将会忽略接收方发起的流ID 大于 Last-Stream-ID 的流。接收了 GOAWAY 帧的接收端不能在连接上打开新的流,但是可以为新的流建立新的连接。如果接收端发送了大于Last-Stream-ID 的流可以将这些流视为从未创建过,然后在新的连接上进行重试。
端点应始终在关闭连接之前发送 GOAWAY 帧,以便 remote peer 可以知道流是否已被处理了一部分。例如,如果HTTP客户端在服务器关闭连接的同时发送POST请求,而服务端没有发送GOAWAY帧以指示其可能对哪些流采取了操作,那么客户端则无法知晓服务端是否已经开始处理该POST请求。(换句话说小于Last-Stream-ID的流都被认为已经处理或正在处理)
端点可以不向已经故障的 peer 发送 GOAWAY 而直接关闭连接。
GOAWAY 帧可能不会出现在连接关闭之前,例如,使用 RST 直接重置连接。
+-+-------------------------------------------------------------+
|R| Last-Stream-ID (31) |
+-+-------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
| Additional Debug Data (*) |
+---------------------------------------------------------------+
上图为 GOAWAY 帧有效载荷(PlayLoad)格式,GOAWAY 帧没有定义任何标志。GOAWAY 帧作用用于连接,而不是特定的流。端点必须将流标识符不是 0x0的GOAWAY帧视为PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)。
GOAWAY 帧中的最后一个流标识符是编号最高的流标识符,GOAWAY 帧的发送者可能已经对其进行了处理或可能尚未进行处理。可能已经以某种方式处理了标识流之前和包括标识流在内的所有流。如果未处理任何流,则可以将最后一个流标识符设置为0。
如果连接在没有 GOAWAY 帧的情况下终止,则最后一个流标识符实际上是可能的最高流标识符。GOAWAY 帧还包含一个 32 位错误代码(第 7 节),其中包含关闭连接的原因。
端点可以将不透明数据附加到任何 GOAWAY 帧的有效载荷中。额外的调试数据仅用于诊断目的,没有语义价值。调试信息可能包含安全或隐私敏感数据。记录或以其他方式持久存储的调试数据必须有足够的保护措施以防止未经授权的访问。
6.9. WINDOW_UPDATE
WINDOW_UPDATE 帧(type=0x8)用于实现流量控制;有关概述,请参阅第 5.2 节。
流量控制工作于两个级别:流级别和连接级别。
两种级别的流量控制都是逐跳的(hop by hop),即只在两个端点之间进行。代理之间不会转发WINDOW_UPDATE 帧。但是任何接收者的数据传输限制都可能导致 flow-control 的信息向原始发送方传递。
只有 DATA 帧用受流量控制帧的影响,不受流量控制影响的帧必须被接收并处理,如果接收方无法分配资源来处理该帧,则接收方可能会响应类型为FLOW_CONTROL_ERROR的流错误(第 5.4.2 节)或连接错误(第 5.4.1 节)。
下图为 WINDOW_UPDATE 帧有效载荷(PlayLoad)格式:
+-+-------------------------------------------------------------+
|R| Window Size Increment (31) |
+-+-------------------------------------------------------------+
WINDOW_UPDATE 帧的有效载荷是一个保留位加上一个无符号的 31 位整数,它表示除了现有的流量控制窗口外,发送方还可以传送的字节数。流量控制窗口的取值范围是1到2^31-1(2,147,483,647)字节。
WINDOW_UPDATE 帧可以是针对一个流或者是针对整个连接的。如果是前者,WINDOW_UPDATE 帧的流标识符指明了受影响的流;如果是后者,流标识符为 0 表示作用于整个连接。
接收端必须将收到流量控制窗口增量(increment)为 0 的 WINDOW_UPDATE 帧的视为PROTOCOL_ERROR类型的流错误(第 5.4.2 节);连接级别流控制窗口上的错误必须被视为连接错误(第 5.4.1 节)。
WINDOW_UPDATE 可以由发送过带有 END_STREAM 标志的帧的对端发送。这意味着接收端可能会在 half-closed (remote) 或者 closed 状态的流上收到 WINDOW_UPDATE 帧,接收端不能将其视为做错误。
这个其实很好理解,因为 TCP 全双工的特点,虽然发送端不发送数据了,但是接收端还会给发送端发送数据,所以依然需要流量控制。
接收端接收到流量控制帧时,必须始终将其大小计入连接的流量控制窗口,除非接收端将其视为连接错误(章节5.4.1)。这是必要的,即使帧是错误的。因为发送方将帧入数到流量控制窗口,但如果接收方不这样做,发送方和接收方的流量控制窗口可能会不同。
6.9.1. 流量控制窗口
HTTP/2 的流量控制是通过为每个发送方在每个流上都保持一个窗口来实现的。流量控制窗口是一个简单的整数值,表示允许发送方传输字节数的大小;因此,其大小是接收方缓冲能力的大小。
流量控制窗口分为:**流流量控制窗口(stream flow-control window)和连接流量控制窗口( connection flow-control window)。**发送端绝对不能发送长度超过接收端通告的任何一个流量控制窗口可用空间的受流量控制影响的帧。如果在两个流量控制窗口中都没有可用空间,则可以发送长度为零且设置了END_STREAM标志的帧(即空DATA 帧)。
对于流量控制计算,帧首部的9个字节的不被计算。
发送一个 DATA 帧后发送方将两个窗口中的可用空间减少传输帧的长度 (Play Load 大小)。
当帧的接收端消耗了数据并释放了流量控制窗口的空间时发送一个 WINDOW_UPDATE 帧。对于流级别和连接级别的流量控制窗口,需要分别发送 单独的 WINDOW_UPDATE 帧。
接收 WINDOW_UPDATE 帧的发送者按帧中指定的大小更新相应的窗口。
发送方不允许流量控制窗口超过2^31-1个字节,如果发送方收到一个 WINDOW_UPDATE 导致流量控制窗口超过这个最大值,它必须适当地终止流或连接。对于流,所述发送方发送一个RST_STREAM与错误代码FLOW_CONTROL_ERROR ; 对于连接,发送错误代码为FLOW_CONTROL_ERROR的GOAWAY帧。
来自发送方的流控制帧和来自接收方的 WINDOW_UPDATE 帧彼此完全异步。此属性允许接收方主动更新发送方保留的窗口大小,以防止流停止。
6.9.2. 初始流量控制窗口大小
新建连接时,流和连接的初始窗口大小都是 2^16 - 1(65535) 字节。两个端点都可以通过连接前言中 SETTINGS 帧的 SETTINGS_INITIAL_WINDOW_SIZE 参数值改变流的初始窗口大小,这会作用于所有流。而连接的初始窗口大小只能用 WINDOW_UPDATE 帧来改变,这是为什么连接前言往往带有一个 WINDOW_UPDATE 帧的原因。
除了改变还未激活的流的流量控制窗口外,SETTIGNS 帧还可以改变已活跃的流 (处于 open 或 half-closed (remote) 状态的流)的初始流量控制窗口的大小。也就是说,当 SETTINGS_INITIAL_WINDOW_SIZE 的值变化时,接收端必须调整它所维护的所有流的流量控制窗口的值,不管是之前就打开的流还是尚未打开的流。
改变 SETTINGS_INITIAL_WINDOW_SIZE 可能引发流量控制窗口的可用空间变成负值。发送端必须追踪负的流量控制窗口,并且直到它收到了使流量控制窗口变成正值的 WINDOW_UPDATE 帧,才能发送新的 DATA 帧。
例如,如果客户端在连接建立时立即发送 60KB 的数据,而服务端将初始窗口大小设置为 16KB,那么客户端一收到 SETTINGS 帧,就会将可用的流量控制窗口重新计算为 -44KB。客户端保持负的流量控制窗口,直到 WINDOW_UPDATE 帧将窗口值恢复为正值,客户端才可以继续发送数据。
SETTINGS 帧不能改变连接流量控制窗口大小。
6.9.3. 减少流量控制窗口
如果接收端希望使用比当前值小的流量控制窗口,可以发送一个新的 SETTINGS 帧。但是,接收端必须准备好接收超出该窗口值的数据,因为可能在收到 SETTIGNS 帧之前,发送端已经发送了超出该较小窗口值的数据。
在发送减少初始流量控制窗口大小的 SETTINGS 帧后,接收者可以继续处理超出流量控制限制的流。允许流继续并不允许接收器立即减少它为流量控制窗口保留的空间。这些流的进度也可能停止,因为需要WINDOW_UPDATE帧来允许发送方恢复发送。接收者可以为受影响的流发送一个错误代码为FLOW_CONTROL_ERROR的RST_STREAM。
6.10. CONTINUATION
CONTINUATION 帧(类型=0x9)用于延续首部块片段(第 4.3 节)。可以发送任意数量的 CONTINUATION 帧,只要前一帧在同一流上并且是没有设置 END_HEADERS 标志的HEADERS、PUSH_PROMISE或 CONTINUATION 帧。
+---------------------------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
CONTINUATION 帧定义了以下标志:
- END_HEADERS (0x4): 第 2 bit位设置为1,表示该帧结束首部块。
如果 END_HEADERS 位未设置,则该帧必须后跟另一个 CONTINUATION 帧。接收者必须将接收任何其他类型的帧或不同流上的帧视为PROTOCOL_ERROR类型的连接错误(第 5.4.1 节)
CONTINUATION 帧必须与流相关联。如果接收到流标识符为 0x0 的 CONTINUATION 帧,则接收者必须以PROTOCOL_ERROR类型的连接错误进行响应。
CONTINUATION 帧必须在没有设置 END_HEADERS 标志的 HEADERS、PUSH_PROMISE 或 CONTINUATION帧之前发送,否则接收者必须以PROTOCOL_ERROR类型的连接错误进行响应。
7. 错误码
错误码是在是在 RST_STREAM 和 GOAWAY 帧中使用的 32 位字段,用来传递流或连接错误的原因。
- NO_ERROR (0x0):没有错误,例如,GOAWAY 可能包含此代码以指示正常关闭连接。
- PROTOCOL_ERROR (0x1):端点检测到一个非特定的协议错误。此错误是在更具体的错误代码不可用时使用的。
- INTERNAL_ERROR (0x2):端点遇到意外的内部错误。
- FLOW_CONTROL_ERROR (0x3):端点检测到对等方违反了流量控制协议。
- SETTINGS_TIMEOUT (0x4):端点发送了一个 SETTINGS 帧,但没有及时收到响应。
- STREAM_CLOSED (0x5):端点在处于半关闭(half-closed)状态后接收到一个帧。
- FRAME_SIZE_ERROR (0x6):端点收到一个长度不合法的帧。
- REFUSED_STREAM (0x7):端点在执行任何应用程序之前拒绝流。
- CANCEL (0x8):指示端点不再需要该流。
- COMPRESSION_ERROR (0x9):端点无法维护连接的头部压缩上下文。
- CONNECT_ERROR (0xa):响应 CONNECT 请求(第 8.3 节)建立的连接被重置或异常关闭。
- ENHANCE_YOUR_CALM (0xb):端点检测到其对等方正在表现出可能会产生过多负载的行为。
- INADEQUATE_SECURITY (0xc):底层传输具有不满足最低安全要求的属性。
- HTTP_1_1_REQUIRED (0xd):端点要求使用 HTTP/1.1 而不是 HTTP/2。
8. HTTP 消息交换
8.1. HTTP 请求/响应交换
客户端使用以前未使用的流标识符(第 5.1.1 节)在新流上发送 HTTP 请求。服务器在与请求相同的流上发送 HTTP 响应。
HTTP 消息(请求或响应)包括:
- 仅对于响应来说,0 个 或者多个包含消息头HEADERS 帧的HTTP响应。
- 一个包含消息头的 HEADERS 帧(后面跟随 0 个或过个 CONTINUATION 帧)。
- 0 个或者多个包含负载的 DATA 帧。
- 可选的,一个 HEAERS 帧后面跟随 0 个或者多个包含剩余首部块的 CONTINUATION 帧。
序列中的最后一帧带有 END_STREAM 标志,带有 END_STREAM 标志的首部帧后可以跟随带有首部块任何剩余部分的 CONTINUATION 帧,其他帧(来自任何流)不得出现在 HEADERS 帧 和其跟随的 CONTINUATION 帧之间。
8.1.2. HTTP 首部字段
HTTP 首部字段以一系列键值对的形式携带信息。有关已注册 HTTP 首部的列表,请参阅“消息首部字段注册表” < www.iana.org/assignments… > 。
8.1.2.1. 伪头(Pseudo-Header )字段
不同于 HTTP/1.x 使用消息起始行来传递目标URI、请求方法、响应状态码。HTTP2 使用以 ':' 字符(ASCII 0x3a)开头的特殊的伪头字段表示。
伪头字段不是 HTTP 首部字段,端点不得生成本文档中定义的以外的伪头字段。
伪头字段仅在定义它们的上下文中有效。为请求定义的伪头字段不得出现在响应中;为响应定义的伪头字段不得出现在请求中。伪标题字段不得出现在“trailers”中。端点必须将包含未定义或无效伪标头字段的请求或响应视为格式错误(malformed)。
所有伪头域必须出现在头块中的常规头域之前。任何包含出现在常规标头字段之后的首部块中的伪标头字段的请求或响应必须被视为格式错误(malformed)。
8.1.2.2. 特定于连接的首部字段
HTTP/2 不使用 Connection 字段来指示特定于连接的首部字段;在这个协议中,特定于连接的元数据是通过其他方式传递的。端点不得生成包含特定于连接的首部字段的 HTTP/2 消息;任何包含特定于连接的头字段的消息必须被视为格式错误(malformed)。
TE 首部字段是唯一的例外,它可能出现在 HTTP/2 的请求之中,如果出现这种情况,它不得包含除了“trailers”以外的任何值。
这意味着将 HTTP/1.x 消息转换为 HTTP/2 的代理(proxy)将需要删除 Connection 首部字段指定的任何首部字段,包括 Connection 首部字段本身。代理还应该删除其他特定于连接的首部字段,例如 Keep-Alive、Proxy-Connection、Transfer-Encoding 和 Upgrade,即便它们没有由 Connection 头字段指定。
8.1.2.3. 请求伪头字段
HTTP/2 定义了以下请求伪头字段:
- :method :使用的 HTTP 方法。
- :scheme:目标URI。
- :authority
- :path:目标URI的路径和查询部分。
所有 HTTP/2 请求必须为:method、:scheme和:path伪标头字段包含一个有效值,除非它是 CONNECT 请求(第 8.3 节)
8.1.2.4. 响应伪头字段
对于 HTTP/2 响应,定义了一个 :status 伪头字段,它携带 HTTP 状态代码。这个伪头域必须包含在所有响应中;否则,响应格式错误(malformed)。
8.1.2.5. 压缩 Cookie 首部字段
... 略
8.1.2.6. 格式错误(malformed)的请求和响应
... 略
8.1.3. 例子
本节展示了 HTTP/1.1 的请求和响应,并举例说明了等效的 HTTP/2 请求和响应。
一个 HTTP GET 请求,它包含请求头但是没有 payload body,因此作为单个 HEADERS 帧传输,后面跟随零个或者多个包含首部块的 CONTINUATION 帧。下面的 HEADERS 帧设置了 END_HEADERS 和 END_STREAM 标志;但是没有发送 CONTINUATION 帧。
GET /resource HTTP/1.1 HEADERS
Host: example.org ==> + END_STREAM
Accept: image/jpeg + END_HEADERS
:method = GET
:scheme = https
:path = /resource
host = example.org
accept = image/jpeg
类似的,只包含响应头的响应被传输为包含首部块的 HEADERS 帧。
HTTP/1.1 304 Not Modified HEADERS
ETag: "xyzzy" ==> + END_STREAM
Expires: Thu, 23 Jan ... + END_HEADERS
:status = 304
etag = "xyzzy"
expires = Thu, 23 Jan ..
一个包含请求头和有效负载的 HTTP POST 请求被作为一个 HEADERS 帧传输,然后是零个或多个包含请求首部字段的 CONTINUATION 帧,然后是一个或多个 DATA 帧。最后一个 CONTINUATION 帧(或者 HEADERS 帧)设置了 END_HEADERS 标志,最后一个 DATA 帧设置了 END_STREAM 标志。
POST /resource HTTP/1.1 HEADERS
Host: example.org ==> - END_STREAM
Content-Type: image/jpeg - END_HEADERS
Content-Length: 123 :method = POST
:path = /resource
{binary data} :scheme = https
CONTINUATION
+ END_HEADERS
content-type = image/jpeg
host = example.org
content-length = 123
DATA
+ END_STREAM
{binary data}
包含响应头和有效负载的响应被作为一个 HEADERS 帧传输,然后是零个或多个CONTINUATION 帧,然后是一个或多个 DATA 帧,最后一个 DATA 帧设置了 END_STREAM 标志。
HTTP/1.1 200 OK HEADERS
Content-Type: image/jpeg ==> - END_STREAM
Content-Length: 123 + END_HEADERS
:status = 200
{binary data} content-type = image/jpeg
content-length = 123
DATA
+ END_STREAM
{binary data}
在请求和响应的首部块和 DATA 帧都发送完之后,尾部头字段(Trailing header fields)将作为首部块发送。trailers 首部块被设置 END_STREAM 标志。以下示例包含了 100 状态码。
HTTP/1.1 100 Continue HEADERS
Extension-Field: bar ==> - END_STREAM
+ END_HEADERS
:status = 100
extension-field = bar
HTTP/1.1 200 OK HEADERS
Content-Type: image/jpeg ==> - END_STREAM
Transfer-Encoding: chunked + END_HEADERS
Trailer: Foo :status = 200
content-length = 123
123 content-type = image/jpeg
{binary data} trailer = Foo
0
Foo: bar DATA
- END_STREAM
{binary data}
HEADERS
+ END_STREAM
+ END_HEADERS
foo = bar