HTTP2-rfc7540 中文版
嗯,这是之前看 grpc 源码时,闲的*疼翻译的http2文档。
[TOC]
1.介绍
HTTP/2为HTTP语义提供了一个优化的传输。HTTP/2支持HTTP/1.1的所有核心特性,但目标是通过几种方式提高效率.
超文本传输协议(HTTP)是一个非常成功的协议。然而,HTTP/1.1使用底层传输的方式([RFC7230],第6节)有几个特点,对当前的应用程序性能有负面的总体影响。
特别地,HTTP/1.0在一个给定的TCP连接上,一次只允许一个未完成的请求。HTTP/1.1增加了请求管道,但这只是部分解决了请求并发性问题,仍然存在 head-of-line 阻塞的问题。因此,HTTP/1.0和HTTP/1.1客户端需要发出许多请求,为了实现并发性,从而减少延迟,它们会使用到服务器的多个连接。
此外,HTTP头字段经常重复和冗长,导致不必要的网络流量,并导致初始TCP [TCP]拥塞窗口快速填充。当在一个新的TCP连接上发出多个请求时,这可能会导致过多的延迟。
HTTP/2通过定义一个优化的HTTP语义到底层连接的映射来解决这些问题。具体来说,它允许在同一连接上交叉发送请求和响应消息,并对HTTP报头字段使用高效编码。它还允许对请求进行优先排序,让更重要的请求更快地完成,进一步提高性能。
结果协议对网络更友好,因为与HTTP/1.x相比,可以使用更少的TCP连接。这意味着减少与其他流的竞争,延长连接的寿命,从而更好地利用可用网络容量。
最后,HTTP/2还通过使用二进制消息帧来更有效地处理消息。
2.HTTP2协议概述
HTTP/2支持HTTP/1.1的所有核心特性,但目标是通过几种方式提高效率。
HTTP/2中的基本协议单元是一个帧。每种帧类型都有不同的用途。例如,HEADES
和DATA
帧构成了HTTP请求和响应的基础;其他帧类型如SETTINGS
, WINDOW_UPDATE
和PUSH_PROMISE
用于支持其他HTTP/2特性。
请求的多路复用是通过将每个HTTP请求/响应交换与它自己的流相关联来实现的(第5节)。流在很大程度上是相互独立的,因此阻塞或停滞的请求或响应不会阻止其他流的进展。
流控制
和优先级
确定可以确保有效地使用多路复用的流。流控制有助于确保只传输接收方可以使用的数据。优先级确保有限的资源可以首先分配给重要的流。
HTTP/2增加了一种新的交互模式,服务器可以将响应推送给客户端。服务器推送
允许服务器推测地向客户端发送服务器预期客户端将需要的数据,以牺牲一些网络使用和潜在的延迟增益。服务器通过合成一个请求来实现这一点,该请求以PUSH_PROMISE
帧的形式发送。然后,服务器能够在单独的流上向合成请求发送响应。
因为连接中使用的HTTP头字段可以包含大量冗余数据,因此包含冗余数据的帧被压缩(章节4.3)。在通常情况下,这对请求大小有特别有利的影响,允许将多个请求压缩到一个包(packet)中。
2.1约定和术语
所有数值都按网络字节顺序排列。值是无符号的,除非另有指示。文字值根据情况以十进制或十六进制提供。十六进制字面值前缀为0x,以区别于十进制字面值。
使用的术语如下:
client:发起HTTP/2连接的端点。客户端发送HTTP请求并接收HTTP响应。 connection:两个端点之间的传输层连接。 connection error:影响整个HTTP/2连接的错误。 endpoint:连接的客户端或服务器。 frame:HTTP/2连接中最小的通信单元,由报头和根据帧类型构造的可变长度的八进制序列组成。 peer:一个端点。在讨论特定端点时,“peer”指的是与主要讨论主题相隔较远的端点。 receiver:接收帧的端点。 sender:传送帧的端点。 server:接受HTTP/2连接的端点。服务器接收HTTP请求并发送HTTP响应。 stream:HTTP/2连接中的双向帧流。 stream error:单个HTTP/2流上的错误。
payload
3.开始HTTP2
HTTP/2连接是运行在TCP连接([TCP])上的应用层协议。客户端是TCP连接启动程序。
HTTP/2使用与HTTP/1.1相同的“HTTP”和“https”URI模式。HTTP/2共享相同的缺省端口号:80代表“HTTP”uri, 443代表“https”uri。因此,处理诸如http://example.org/foo 或 https://example.com/bar 等目标资源uri请求的实现需要首先确定上游服务器(客户端希望建立连接的直接对等端)是否支持HTTP/2。
对于“HTTP”和“https”uri,确定对HTTP/2的支持的方法是不同的
。第3.2节描述了“http”uri的发现。发现“https”uri在3.3节中进行了描述
3.1HTTP/2版本识别
本文档中定义的协议有两个标识符。
h2(http/2 with TLS)
字符串”h2”表示HTTP/2使用传输层安全(TLS) [TLS12]的协议。此标识符用于TLS应用层协议协商(ALPN)扩展[TLS-ALPN]字段中,以及用于标识TLS上的HTTP/2的任何位置。
“h2”字符串被序列化到一个ALPN协议标识符中,作为两字节序列:0x68, 0x32。
h2c(http/2 with no TLS)
字符串“h2c”(http/2 with no TLS)标识HTTP/2在明文TCP上运行的协议。此标识符用于HTTP/1.1 更新报头字段和标识TCP上的HTTP/2的任何位置。
“h2c”字符串从ALPN标识符空间中保留,但描述了一个不使用TLS的协议。
协商“h2”或“h2c”意味着使用本文档中描述的传输、安全、帧和消息语义。
主流的浏览器都只支持h2,即 http2 需要运行在tls基础上,需要走 https。
3.2为“http”uri启动HTTP/2
如果客户机请求一个“http”URI,而事先不知道下一跳是否支持http /2,则使用http升级(Upgrade)机制。客户端通过发出一个HTTP/1.1请求来实现这一点,该请求包含一个带有“h2c”令牌的 Upgrade
头字段。这样的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>
包含有效负载主体(payload)的请求必须在客户端发送HTTP/2帧之前全部发送。这意味着一个大的请求可能会阻塞连接的使用,直到它被完全发送。
如果初始请求与后续请求的并发性很重要,那么可以使用OPTIONS请求来执行升级到HTTP/2的操作,代价是额外的往返。
一个不支持HTTP/2的服务器可以响应请求,直接忽略Upgrade: h2c
:
HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html
服务器必须忽略升级头字段中的“h2”令牌。带有“h2”的令牌意味着TLS上的HTTP/2,这是在3.3节中讨论的。
支持HTTP/2的服务器通过101(交换协议)响应接受升级。在终止101响应的空行之后,服务器可以开始发送HTTP/2帧。这些帧必须包含对发起升级的请求的响应。
例如:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
服务器发送的第一个HTTP/2帧必须是服务器连接序言(preface),由SETTINGS帧组成。在接收到101响应时,客户端必须发送一个连接序言(preface),包括一个SETTINGS帧。
在升级之前发送的HTTP/1.1请求被分配一个流标识符为1
(参见5.1.1节),该流标识符具有默认的优先级值。从客户端到服务器,流1隐式地“半关闭”,因为请求是作为HTTP/1.1请求完成的。在启动HTTP/2连接之后,将使用流1进行响应.
HTTP2-Settings Header
一个从HTTP/1.1升级到HTTP/2的请求必须包含一个HTTP2-Settings头字段。HTTP2-Settings报头字段是一个特定于连接的报头字段,它包含管理HTTP/2连接的参数,是在服务器接受升级请求时提供的.
HTTP2-Settings = token68
如果没有这个头字段,或者有多个头字段,服务器不能将连接升级到HTTP/2。服务器不能发送此头字段。
HTTP2-Settings头字段的内容是SETTINGS
帧的有效负载,编码为base64url字符串。为token68生产的ABNF [RFC5234]在[RFC7235]的第2.1节中定义。
由于升级仅适用于即时连接,发送HTTP2-Settings头字段的客户端也必须将HTTP2-Settings作为连接头字段中的连接选项发送,以防止其被转发(参见[RFC7230] 6.1节)。
服务器像对其他设置帧一样对这些值进行解码和解释。对于这些设置的明确确认(第6.5.3节)是没有必要的,因为101响应是隐式的确认。在升级请求中提供这些值使客户端有机会在从服务器接收任何帧之前提供参数。
3.3为“https”uri启动HTTP/2
向“https”URI发出请求的客户端使用TLS [TLS12]和应用层协议协商(ALPN)扩展[TLS-ALPN]。
HTTP/2通过TLS使用“h2”协议标识符。“h2c”协议标识符不得由客户端发送或由服务器选择;“h2c”协议标识符描述了一个不使用TLS的协议。
一旦TLS协商完成,客户端和服务器端都必须发送一个连接序言
。
3.4使用HTTP/2的知识准备
客户端可以通过其他方式了解到某个服务器支持HTTP/2。例如,[ALT-SVC]描述了一种宣传此功能的机制。
客户端必须发送连接序言(第3.5节),然后可以立即向该服务器发送HTTP/2帧;服务器可以通过出现连接序言来识别这些连接。这只影响在明文TCP上建立HTTP/2连接;在TLS上支持HTTP/2的实现必须使用TLS [ls - alpn]中的协议协商。
同样,服务器必须发送一个连接序言(章节3.5)。
如果没有额外的信息,预先支持HTTP/2并不意味着给定服务器将支持HTTP/2用于未来的连接。例如,服务器配置可能会更改,集群服务器中的实例之间的配置可能会不同,或者网络条件可能会更改。
3.5HTTP2 连接序言 preface
在HTTP/2中,每个端点都需要发送一个连接序言,作为使用协议的最终确认,并为HTTP/2连接建立初始设置
。客户端和服务器各自发送不同的连接序言。
客户端连接序言以24个八进制的序列开始,在十六进制表示法中是:
0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
也就是说,连接序言以字符串PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)
开始。这个序列后面必须有一个SETTINGS
帧(章节6.5),它可能是空的。客户端在收到101(交换协议)响应(指示成功升级)或作为TLS连接的第一个应用程序数据字节时立即发送客户端连接序言。如果在预先知道服务器支持协议的情况下启动HTTP/2连接,客户端连接序言将在连接建立时发送。
注意:选择客户端连接序言是为了让大部分HTTP/1.1或HTTP/1.0服务器和中介不尝试处理进一步的帧。请注意,这并没有解决[会谈]中提出的关切。
服务器连接序言包含一个可能为空的SETTINGS
帧(章节6.5),它必须是服务器在HTTP/2连接中发送的第一个帧。
作为连接序言的一部分,从peer端接收到的SETTINGS
帧必须在发送连接序言后被确认(见第6.5.3节)
为了避免不必要的延迟,允许客户端在发送客户端连接序言之后立即向服务器发送额外的帧,而不需要等待服务器连接序言。但是需要注意的是,服务器连接序言SETTINGS 帧可能包含一些必要的参数,这些参数会改变客户端与服务器通信的方式。在接收到设置帧时,客户端被期望尊重任何已建立的参数。在某些配置中,服务器可以在客户端发送附加帧之前传输设置,从而提供了避免此问题的机会。
客户端和服务器必须将无效的连接序言视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。在这种情况下,一个GOAWAY
帧(章节6.8)可能会被省略,因为一个无效的序言表明对等方没有使用HTTP/2。
4.HTTP帧
一旦HTTP/2连接建立,两端就可以开始交换帧。
4.1帧格式
所有帧都以固定的9字节头和可变长度的有效负载(payload)开始。
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
帧头(Frame header)的字段定义为:
Length:表示为24位无符号整数的帧有效负载的长度。不能发送大于2^14(16,384)的值,除非接收方为SETTINGS_MAX_FRAME_SIZE
设置了更大的值。
帧头的9个字节不包括在这些值中。
Type:帧的8位类型。框架类型决定框架的格式和语义。实现必须忽略和丢弃任何具有未知类型的框架。
Flags:为特定于帧类型的布尔标志保留的8位字段,目前一个有10种类型。
Flags 被分配特定于指定的帧类型的语义。必须忽略没有为特定帧类型定义语义的标志,并且在发送时必须保持未设置(0x0)。
R:保留的1位字段。该位的语义未定义,并且在发送时必须保持未设置(0x0),在接收时必须忽略该位。
Stream Identifier:表示为31位无符号整数的流标识符(参见5.1.1节)。值0x0被保留给作为一个与连接相关联的帧(而不是单个流相关联的帧)。
有效负载的结构和内容完全依赖于帧类型。不同类型的帧有不同的结构。
4.2帧大小
帧有效负载的大小受到接收方在SETTINGS_MAX_FRAME_SIZE
设置中声明的最大大小的限制,默认大小约16M。该设置可以包含2^14(16,384)和2^24-1(16,777,215)字节数之间的任何值。
所有的实现都必须能够接收和最少处理长度为2^14个字节的帧,加上9个字节的帧头(章节4.1)。描述帧大小时,不包括帧头的大小
。
注意:某些帧类型,例如PING(第6.7节),对允许的有效负载数据量施加了额外的限制。
如果一个帧超过了SETTINGS_MAX_FRAME_SIZE
中定义的大小,超过了为帧类型定义的任何限制,或者太小而不能包含强制的帧数据,端点必须发送一个FRAME_SIZE_ERROR
错误代码。可能改变整个连接状态的帧大小错误必须被视为连接错误;这包括任何带有header块的帧(即HEADERS
、PUSH_PROMISE
和CONTINUATION
)、SETTINGS
和流标识符为0的帧。
端点没有义务使用一个帧中的所有可用空间。可以通过使用小于允许的最大大小的帧来提高响应性。发送大帧会导致发送时间敏感帧(如RST_STREAM、WINDOW_UPDATE或PRIORITY)的延迟,如果被大帧的传输阻塞,可能会影响性能。
4.3头部压缩和解压
别混淆了HTTP/2的 Header lists 和帧的 header
就像在HTTP/1中一样,HTTP/2中的头字段是带有一个或多个关联值的名称。头字段用于HTTP请求和响应消息以及服务器推送操作(见8.2节)。
Header lists 是零个或多个头字段的集合。当通过连接传输时,使用HTTP报头压缩将header列表序列化为报头块(header block)。序列化的报头块然后被划分成一个或多个字节序列,称为报头块片段(header block),并在HEADERS
(章节6.2)、PUSH_PROMISE
(章节6.6)或CONTINUATION
(章节6.10)帧的有效负载内传输。
Cookie头字段[Cookie]由HTTP映射特殊处理(参见8.1.2.5节)。
接收端点通过连接报头块(header block)的片段来重新组装报头块,然后解压缩该块以重新构造报头列表(header list即http请求头)。
一个完整的头块(header block)包括:
- 设置了
END_HEADERS
标记的HEADERS
或PUSH_PROMISE
帧 - 一个报头结束标记
END_HEADERS
清除的HEADERS
或PUSH_PROMISE
帧和一个或多个延续CONTINUATION
帧,其中最后一个延续帧CONTINUATION
设置了报头结束标记。
头压缩是有状态的。整个连接使用一个压缩上下文和一个解压缩上下文。报头块中的解码错误必须作为类型为COMPRESSION_ERROR的连接错误(章节5.4.1)处理。
每个报头块作为一个独立的单元进行处理。报头块必须作为一个连续的帧序列传输,没有任何其他类型或来自任何其他流的交叉帧
。HEADERS
或PUSH_PROMISE
帧序列的最后一帧设置了报头结束标记END_HEADERS
。PUSH_PROMISE
或CONTINUATION
序列的最后一帧设置了报头结束标记END_HEADERS
。这使得报头块在逻辑上等同于一帧。
报头块片段(Header block fragments)只能作为HEADERS
或PUSH_PROMISE
帧或CONTINUATION
帧的有效负载发送,因为这些帧携带的数据可以修改接收器维护的压缩上下文。接收HEADERS
或PUSH_PROMISE
帧或CONTINUATION
帧的端点需要重新组装报头块并执行解压缩,即使这些帧要被丢弃。如果接收方没有解压缩报头块,则必须以类型为COMPRESSION_ERROR
的连接错误(章节5.4.1)终止连接。
5.流和多路复用
“流”是在HTTP/2连接中客户端和服务器之间交换的独立的、双向的帧序列。流有几个重要的特征:
- 一个HTTP/2连接可以包含多个同时打开的流,任意一个端点交互来自多个流的帧。
- 流可以单独建立和使用,也可以由客户端或服务器共享。
- 任何端点都可以关闭流。
- 帧在流上发送的顺序是重要的。接收方按照接收帧的顺序处理帧。特别是,
HEADERS
和DATA
帧的顺序在语义上很重要。 - 流由一个整数标识。流标识符由初始化流的端点分配给流
5.1流状态
+--------+
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: endpoint sends this frame
recv: endpoint receives this frame
H: HEADERS frame (with implied CONTINUATIONs) 6
PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
ES: END_STREAM flag
R: RST_STREAM frame
idle:空闲
所有流都在“空闲”状态下启动。
以下转换在此状态下有效:
- 发送或接收
HEADERS
帧导致流变成“打开(open)”。按照第5.1.1节的描述选择流标识符。同样的HEADERS
也会导致流立即变成“半关闭(half-closed)”。 - 在另一个流上发送
PUSH_PROMISE
帧会保留标识为以后使用的空闲流。保留流的流状态转换为“保留(本地)”,reserved (local)。 - 在另一个流上接收
PUSH_PROMISE
帧将保留一个空闲流,该流将被标识供以后使用。保留流转换为“保留(远程)”即 “reserved (remote)”的流状态。 - 注意,
PUSH_PROMISE
帧不是在空闲流上发送的,而是在承诺流ID( Promised Stream ID )字段中引用新保留的流。
在这种状态下,接收流中除HEADERS
或PRIORITY
帧以外的任何帧必须被视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
reserved (local):保留(本地)
处于“保留(本地)”状态的流是通过发送PUSH_PROMISE
帧承诺的流。PUSH_PROMISE
帧通过将流与远程对等方发起的开放流相关联来保留空闲流(参见8.2节)。
在这种状态下,只有以下转换是可能的:
- 端点可以发送
HEADERS
帧。这将导致流以“半关闭(远程)”状态打开。 - 任何一个端点都可以发送一个
RST_STREAM
帧来导致流变成“关闭”。这将释放流保留。 - 在这种状态下,除了
HEADERS
、RST_STREAM
或PRIORITY
之外,端点不能发送任何类型的帧。
在这种状态下可能会接收到一个PRIORITY
或WINDOW_UPDATE
帧。在这种状态下,接收到RST_STREAM
、PRIORITY
或WINDOW_UPDATE
以外的任何类型的帧必须被视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
reserved (remote):保留(远程)
处于“保留(远程)”状态的流被远程对等点(remote peer)保留。
在这种状态下,只能有以下转换:
- 接收
HEADERS
帧导致流转换为“半关闭(本地)”。 - 任何一个端点都可以发送一个
RST_STREAM
帧来导致流变成“关闭”(closed)。这将释放流保留。
端点可以在此状态下发送PRIORITY
帧来重定保留流的优先级。在此状态下,端点不能发送RST_STREAM
、HEADERS
或PRIORITY
以外的任何类型的帧。
在这种状态下,接收到除HEADERS
、RST_STREAM
或PRIORITY
以外的任何类型的帧必须被视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
open:打开
处于“打开”状态的流可以被两个对等方(peer)用来发送任何类型的帧。在这种状态下,发送的对等点会观察已公布的流级流量控制限制(stream-level flow-control limits )(章节5.2)。
在这种状态下,任何一个端点都可以发送一个设置了END_STREAM
标志的帧,这会导致流转换为一种“半关闭”状态。端点发送END_STREAM
标志会导致流状态变成“半关闭(本地)”;接收END_STREAM
标志的端点将导致流状态变为“半关闭(远程)”。
任何一个端点都可以从这个状态发送一个RST_STREAM帧,导致它立即转换为“closed”。
half-closed (local):半关闭(本地)
处于“半关闭(本地)”状态的流不能用于发送WINDOW_UPDATE
、PRIORITY
和RST_STREAM
以外的帧。
当接收到一个包含END_STREAM
标志的帧或者当任何一个对等端发送一个RST_STREAM
帧时,流从这个状态转换为“关闭”状态。
在这种状态下,端点可以接收任何类型的帧
。使用WINDOW_UPDATE
帧提供流控制信用是继续接收流控制帧的必要条件。在这种状态下,接收方可以忽略WINDOW_UPDATE
帧,该帧可能会在带有END_STREAM
标记的帧发送后短时间内到达。
在这种状态下接收到的优PRIORITY
帧用于根据已识别的流对流进行优先级调整。
half-closed (remote):半关闭(远程)
“半关闭(远程)”的流不再被对等(peer)端用于发送帧。在这种状态下,端点不再有义务维护接收器流控制窗口。
如果一个端点接收到WINDOW_UPDATE
、PRIORITY
和RST_STREAM
之外的其他帧,对于处于这种状态的流,它必须响应类型为STREAM_CLOSED
的流错误(章节5.4.2)。
一个“半关闭(远程)”的流可以被(local)端点用来发送任何类型的帧。在这种状态下,端点继续观察所公布的流级流量控制限制(章节5.2)。
流可以通过发送一个包含END_STREAM
标志的帧,或者当某个对等端发送RST_STREAM
帧时,从这种状态转换为“关闭”状态。
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
帧。在远程对等端接收并处理RST_STREAM
或带有END_STREAM
标记的帧之前,它可能会发送这些类型的帧。端点必须忽略在这种状态下接收到的WINDOW_UPDATE
或RST_STREAM
帧,尽管端点可以选择将发送END_STREAM
后较长时间到达的帧作为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)处理。
PRIORITY帧可以在已关闭的流上发送,以对依赖于已关闭流的流进行优先级排序。端点应该处理PRIORITY
帧,如果流已经从依赖树中删除,它们可以被忽略(参见5.3.4节)。
如果由于发送RST_STREAM
帧而达到此状态,则接收RST_STREAM
的对等方可能已经在流上发送(或排队等待发送)无法撤回的帧。在发送了RST_STREAM
帧之后,端点必须忽略它在关闭流上接收到的帧。端点可以选择限制忽略帧的时间,并将在此时间之后到达的帧视为错误。
在发送RST_STREAM
后接收到的流控制帧(如DATA
)被计算到连接流控制窗口中。即使这些帧可能被忽略,因为它们是在发送方接收到RST_STREAM
之前发送的,发送方也会认为这些帧不利于流控制窗口。
端点在发送RST_STREAM
之后可能会收到PUSH_PROMISE
帧。PUSH_PROMISE
会导致流变为“保留”,即使关联的流已经被重置。因此,需要一个RST_STREAM
来关闭不需要的承诺流。
在本文档其他地方没有更具体的指导的情况下,实现应该将接收状态描述中不明确允许的帧视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。注意PRIORITY
可以在任何流状态下发送和接收。未知类型的帧被忽略。
HTTP请求/响应交换的状态转换示例可以在8.1节中找到。服务器推送的状态转换示例可以在8.2.1和8.2.2节中找到。
5.1.1流标识符
流由31位无符号整数标识。客户端发起的流必须使用奇数流标识符;由服务器发起的流标识符必须是偶数。流标识符为0 (0x0)用于连接控制消息;流标识符为0不能用于建立新流。
HTTP/1.1升级到HTTP/2(参见3.2节)的请求将以流标识符1 (0x1)响应。升级完成后,流0x1对客户端是“半关闭(本地)”的。因此,从HTTP/1.1升级的客户端不能选择流0x1作为新的流标识符。
新建立的流的标识符在数字上必须大于初始化端点打开或保留的所有流。它管理使用HEADERS
帧打开的流和使用PUSH_PROMISE
保留的流。接收到意外流标识符的端点必须响应类型为PROTOCOL_ERROR
的连接错误(章节5.4.1)。
新流标识符的第一次使用隐式地关闭了所有处于“空闲”状态有较低值流标识符(peer发起)的流。例如,如果一个客户端在流7上发送了一个HEADERS
帧而没有在流5上发送一个帧,那么当流7的第一个帧被发送或接收时,流5转换到“关闭”状态。
流标识符不能重用。长期存在的连接可能导致端点耗尽流标识符的可用范围。无法建立新流标识符的客户端可以为新流建立新连接。无法建立新流标识符的服务器可以发送一个GOAWAY
帧,这样客户端就被迫为新流打开一个新连接。
5.1.2流并发
对等点可以使用设置帧中的SETTINGS_MAX_CONCURRENT_STREAMS
参数(参见6.5.2节)限制并发活动流的数量。最大并发流设置是特定于每个端点的,并且仅应用于接收该设置的对等点。也就是说,客户端指定服务器可以发起的最大并发流数量,服务器指定客户端可以发起的最大并发流数量。
处于“打开”状态或处于“半关闭”状态的流计算到端点允许打开的流的最大数量种。这三种状态中的任何一种流都将计算到SETTINGS_MAX_CONCURRENT_STREAMS
设置中规定的限制。处于任何一种“保留(reserved)”状态的流都不计入流限制。
端点不能超过其对等点设置的限制。如果端点接收到一个报头帧,导致其所设置的并发流限制被超过,那么这个端点必须将其视为类型为PROTOCOL_ERROR
或REFUSED_STREAM
的流错误(章节5.4.2)。错误代码的选择决定了端点是否希望启用自动重试(参见8.1.4节)。
希望将SETTINGS_MAX_CONCURRENT_STREAMS
的值减少到低于当前打开流数量的值的端点可以关闭超过新值的流或允许流完成
5.2流控制
使用流进行多路复用引入了TCP连接使用的争用,导致流阻塞。流控制方案确保同一连接上的流不会相互干扰。流控制既用于单个流,也用于作为一个整体的连接。
HTTP/2通过使用WINDOW_UPDATE
帧(章节6.9)提供流控制。
5.2.1 流控制原则
HTTP/2流流控制的目的是允许各种流控制算法的使用,而不需要协议的改变。HTTP/2中的流量控制具有以下特点:
- 流控制是特定于连接的。两种类型的流控制都在单个跃点的端点(hop)之间,而不是在整个端到端路径上。
- 流控制基于
WINDOW_UPDATE
帧。接收方公布它们准备在一个流和整个连接上接收多少个字节。这是一项基于信用的计划。 - 流量控制是定向的,总体控制由接收器提供。接收方可以选择为每个流和整个连接设置所需的任何窗口大小。发送方必须尊重接收方施加的流量控制限制。客户端、服务器和中介都独立地将其流控制窗口作为接收方发布,并在发送时遵守对等方设置的流控制限制。 对于新的流和整个连接,流控制窗口的初始值都是65,535个字节。
- 帧类型决定流控制是否应用于一个帧。在本文件所指定的帧中,只有
DATA
帧受流量控制;所有其他帧类型都不会占用流控制窗口中的空间。这确保了重要的控制帧不会被流控制阻塞。 - 无法禁用流控制。
- HTTP/2只定义了
WINDOW_UPDATE
帧的格式和语义(章节6.9)。这个文档没有规定接收者如何决定何时发送这个帧或它发送的值,也没有规定发送者如何选择发送数据包。实现能够选择任何适合他们需要的算法。
实现还负责管理如何根据优先级发送请求和响应,选择如何避免请求头的线性阻塞(head-of-line blocking),以及管理新流的创建。这些算法的选择可以与任何流量控制算法交互。
5.2.2适当使用流量控制
定义流控制是为了保护在资源约束下操作的端点。例如,代理需要在许多连接之间共享内存,并且可能有一个较慢的上游连接和一个较快的下游连接。流控制解决了接收方无法处理一个流上的数据,但希望在同一连接中继续处理其他流的情况。
不需要此功能的部署可以发布最大大小(2^31-1)的流控制窗口,并可以在接收到任何数据时通过发送WINDOW_UPDATE
帧来维护此窗口。这将有效地禁用该接收方的流控制。相反,发送方总是受制于接收方所通知的流控制窗口。
资源受限(例如,内存)的部署可以使用流控制来限制对等点可以消耗的内存量。但是请注意,如果在不知道带宽延迟的情况下启用流控制,这可能导致对可用网络资源的次优使用(参见[RFC7323])。
即使完全了解当前的带宽延迟,实现流量控制也很困难。在使用流控制时,接收方必须及时地从TCP接收缓冲区中读取数据。如果不这样做,就会在没有读取和操作关键帧(如WINDOW_UPDATE)时导致死锁。
5.3流的优先级
客户端可以通过在打开流的HEADERS
帧(6.2节)中包含优先级信息来为新流分配优先级。在其他任何时候,PRIORITY
帧(章节6.3)都可以用来改变流的优先级。
优先级的目的是允许端点在管理并发流时,它希望它的对等点如何分配资源。最重要的是,优先级可以用来选择流传输帧的优先级,当发送能力有限时。
流可以通过将它们标记为依赖于其他流的完成来确定优先级(第5.3.1节)。每个依赖项都被分配一个相对权重,这个数字用来确定分配给依赖于同一流的流的可用资源的相对比例。
为流显式设置优先级将被输入到优先级处理流程中。它不保证该流相对于任何其他流的任何特定处理或传输顺序。端点不能使用优先级强制对等点按特定顺序处理并发流。因此,表示优先只是一种建议(但会影响资源的分配比例)。
优先级信息可以从消息中省略。省略则使用默认值(章节5.3.5)。
5.3.1流依赖关系
可以为每个流提供对另一个流的显式依赖。包含依赖关系表示优先将资源分配给已识别的流,而不是分配给依赖的流。
不依赖于任何其他流的流的依赖关系为0x0。换句话说,不存在的流0形成了树的根。
依赖于另一个流的流称为依赖流。流所依赖的流是父流。对当前不在树中的流的依赖——比如处于“空闲”状态的流——会导致该流被给予默认优先级(章节5.3.5)。
在为另一个流分配依赖项时,该流被添加为父流的新依赖项。共享相同父元素的依赖流之间没有相互排序。例如,如果流B和C依赖于流A,如果流D创建时依赖于流A,这将导致依赖顺序为A后接B、C和D,任何顺序都可以。
A A
/ \ ==> /|\
B C B D C
独占标志允许插入新级别的依赖关系。独占标志使流成为其父流的唯一依赖项,从而导致其他依赖项依赖于独占流。在前面的例子中,如果流D在创建时对流A有排他性依赖,这将导致D成为B和C的依赖父类。
A
A |
/ \ ==> D
B C / \
B C
在依赖关系树中,只有当依赖流所依赖的所有流(直到0x0的父流链)都关闭了,或者无法对它们进行处理时,才应该为依赖流分配资源。
流不能依赖自己。端点必须将其视为类型为PROTOCOL_ERROR
的流错误(章节5.4.2)。
5.3.2依赖权重
所有依赖的流被分配一个介于1到256(包括在内)之间的整数权重。
具有相同父级的流应该根据其权重按比例分配资源。因此,如果流B权重为4依赖于流A,流C权重为12依赖于流A,并且在流A上没有进展,那么在理想情况下,流B获得分配给流C的三分之一的资源。
5.3.3变更优先顺序
流优先级使用PRIORITY
帧来更改。设置依赖关系将导致流依赖于已标识的父流。
如果父流被重新确定优先级,依赖的流将与父流一起变动。为重新排序的流设置排他标志的依赖关系将导致新父流的所有依赖关系都依赖于重新排序的流。
如果一个流依赖于它自己的一个依赖项,那么先前依赖的流将首先被移动到依赖于重新排序的流的前父元素。已移动的依赖项保留其权重。
例如,考虑一个原始依赖树,B和C依赖于A;D和E取决于C;F依赖于D ,如果D取代A。
所有其他依赖关系保持不变,若F变得依赖A且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
(intermediate) (non-exclusive) (exclusive)
5.3.4优先级状态管理
当一个流从依赖树中移除时,它的依赖项可以移动到依赖于关闭流的父类。重新计算新的依赖关系的权重。
从依赖关系树中删除的流会导致一些优先级信息丢失。资源在具有相同父流的流之间共享,这意味着如果该集合中的流关闭或阻塞,则分配给流的任何空闲容量都将分配给该流的近邻。但是,如果从树中删除了公共依赖项,那么这些流将与下一个最高级别的流共享资源。
例如,假设流A和B分享一个父级,并且流C和D都依赖于流A。在流A被移除之前,如果流A和D无法继续(如阻塞),那么流C将接收所有专用于流A的资源。如果从树中删除流A,则流A的权重被划分为流C和流D。如果流D仍然无法继续,则会导致流C接收到的资源比例减少。对于相同的起始权重,C接收可用资源的三分之一,而不是一半。
当创建对该流依赖的优先级信息在传输时,流可能会关闭。如果在依赖项中识别的流没有相关的优先级信息,那么依赖流被分配一个默认优先级(章节5.3.5)。这可能会产生次优的优先级,因为流可能被给予的优先级与预期的不同。
为了避免这些问题,端点应该在流关闭后一段时间内保持流优先级状态。保留的状态越长,流被分配不正确或默认优先级值的几率就越低。
类似地,处于“空闲”状态的流可以被分配优先级或成为其他流的父流。这允许在依赖树中创建一个分组节点,从而支持更灵活的优先级表达式。空闲流从一个默认优先级开始(章节5.3.5)。
对于未计算到通过SETTINGS_MAX_CONCURRENT_STREAMS
设置的限制的流统计范畴,优先级信息的保留可能会给端点造成很大的状态负担。因此,保留的优先级状态的数量可能是有限的。
端点为优先级维护的附加状态数量取决于负载;在高负载下,可以丢弃优先级状态以限制资源承诺。在极端情况下,端点甚至可以丢弃活动流或保留流的优先级状态。如果应用了限制,端点应该至少维护其设置为SETTINGS_MAX_CONCURRENT_STREAMS
所允许的流的状态。实现还应该尝试保留优先级树中处于活动状态的流的状态。
如果端点保留了足够的状态,那么接收到更改已关闭流优先级的PRIORITY
帧的端点应该更改依赖于它的流的依赖关系。
5.3.5默认优先级
所有流最初都在流0x0上分配一个非独占依赖关系。推送流(8.2节)最初依赖于它们相关联的流。在这两种情况下,流的默认权重都为16。
5.4错误处理
HTTP/2帧允许两类错误:
- 导致整个连接不可用的是
连接错误
。 - 单个流中的错误称为
流错误
。
在第7节中包含了错误代码的列表。 Section 7
5.4.1连接错误处理
连接错误是阻止帧层进一步处理或破坏任何连接状态的任何错误。
遇到连接错误的端点应该首先发送一个GOAWAY
帧(章节6.8),其中包含它成功从它的对等端接收到的最后一个流的流标识符。GOAWAY
帧包含一个错误代码,该代码指示为什么连接正在终止。在发送了错误条件的GOAWAY
帧之后,端点必须关闭TCP连接。
接收端点可能无法可靠地接收GOAWAY
([RFC7230],第6.6节描述了即时连接关闭如何导致数据丢失)。在出现连接错误的情况下,GOAWAY
只提供了与对等方就连接被终止的原因进行通信的最佳尝试。
端点可以在任何时候结束连接。特别是,端点可以选择将流错误视为连接错误。端点在结束连接时应该发送一个GOAWAY
帧,如果环境允许的话。
5.4.2流错误处理
流错误是与某个特定流相关的不影响其他流处理的错误。
检测流错误的端点发送一个RST_STREAM
帧(6.4节),其中包含发生错误的流的流标识符。RST_STREAM
帧包含一个错误代码,它指出错误的类型。
RST_STREAM
是端点可以在流上发送的最后一帧。发送RST_STREAM
帧的对等端必须准备好接收远程对等端发送或排队等待发送的任何帧。这些帧可以被忽略,除非它们修改连接状态(例如头压缩(章节4.3)或流控制所维护的状态)。
通常,端点不应该为任何流发送一个以上的RST_STREAM
帧。但是,如果一个端点在超过一个往返时间之后接收到一个关闭流上的帧,那么它可以发送额外的RST_STREAM
帧。允许这种行为处理行为不正常的实现。
为了避免循环,端点不能发送RST_STREAM
来响应RST_STREAM
帧
5.4.3连接终止
如果TCP连接关闭或重置而流保持“打开”或“半关闭”状态,那么受影响的流不能自动重试(详见8.1.4节)。
5.5扩展HTTP / 2
HTTP/2允许对协议进行扩展。在本节所述的限制范围内,协议扩展可用于提供附加服务或更改协议的任何方面。扩展仅在单个HTTP/2连接的范围内有效。
这适用于本文档中定义的协议元素。这并不影响扩展HTTP的现有选项,如定义新方法、状态码或头字段。
扩展允许使用新的帧类型(章节4.1)、新的设置(章节6.5.2)或新的错误代码(章节7)。为管理这些扩展点建立了注册:帧类型(章节11.2)、设置(章节11.3)和错误代码(章节11.4)。
实现必须忽略所有可扩展协议元素中的未知值或不受支持的值。实现必须丢弃具有未知或不支持类型的帧。这意味着扩展可以安全地使用这些扩展点,而无需事先安排或协商。然而,扩展帧出现在头块的中间(章节4.3)是不允许的;这些必须作为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)处理。
可能改变现有协议组件语义的扩展必须在使用之前进行协商。例如,在对等方给出可以接受的积极信号之前,不能使用更改HEADERS
帧布局的扩展。在这种情况下,当修改后的布局生效时,也可能需要进行协调。注意,将数据帧以外的任何帧作为流控制是一种语义上的改变,只能通过协商来实现。
本文档没有规定讨论使用扩展的具体方法,但指出可以为此使用设置(第6.5.2节)。如果两个对等点都设置了一个表示愿意使用扩展的值,那么就可以使用扩展。如果将设置用于扩展协商,则必须以一种最初禁用扩展的方式定义初始值。
6.帧定义
该规范定义了许多帧类型,每一种都由一个惟一的8位类型代码标识。在建立和管理作为一个整体或单个流的连接时,每种帧类型都有不同的用途。
特定帧类型的传输可以改变连接的状态。如果端点未能维护连接状态的同步视图,则连接内的通信将不再可能成功。因此,端点对于使用任何给定帧如何影响状态有一个共享的理解是很重要的。
6.1 DATA 数据帧
DATA
帧(type=0x0)传输与流相关联的任意、可变长度的字节序列。例如,使用一个或多个DATA
帧来携带HTTP请求或响应有效负载。
DATA
帧也可能包含填充(Padding)。可以在数据帧中添加填充来模糊消息的大小。填充是一种安全特性;参见10.7节。
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
数据帧包含以下字段:
Pad Length:一种8位字段,以字节为单位,包含帧填充的长度。这个字段是有条件的(如图中的“?”所示),并且只有在设置了填充标志时才会出现。
Data:应用程序数据。数据量是帧有效负载减去其他字段的长度后剩下的数据量。
Padding:填充不包含应用程序语义值的八字节。发送时必须将填充八进制设置为零。接收方没有义务验证填充,但可以将非零填充视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
数据帧定义了以下标志:
END_STREAM (0x1):当设置时,Flag 第0位表示该帧是端点将为识别的流发送的最后一个帧。设置此标志将导致流进入“半关闭”状态或“关闭”状态(5.1节)。
PADDED (0x8):当设置时,Flag 第3位表示填充长度字段和它所描述的任何填充都存在。
数据帧必须与流相关联。如果接收到流标识符字段为0x0的数据帧,接收方必须响应类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。标示为0x0的流是与连接相关的。而不是某个流。
数据帧受流控制,只能在流处于“开放”或“半关闭(远程)”状态时发送。整个数据帧有效负载包括在流控制,包Padding长度和填充字段(如果存在)。如果接收到的数据帧的流不是处于“打开”或“半关闭(本地)”状态,接收方必须响应类型为STREAM_CLOSED
的流错误(章节5.4.2)。
Padding字节数的总数由填充长度字段的值决定。如果填充的长度是帧有效负载的长度或更大,接收方必须将此作为类型为PROTOCOL_ERROR
的连接错误(章节5.4.1)处理。
注意:一个帧的大小可以增加一个八位元(1字节),通过包含一个值为零的Pad长度字段。
6.2 HEADERS 报头帧
HEADERS
帧(type=0x1)用于打开流(5.1节),另外还携带一个header块片段。HEADERS
帧可以在“空闲”、“保留(本地)”、“打开”或“半关闭(远程)”状态下通过流发送。
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
Figure 7: HEADERS Frame Payload
HEADERS
帧有效负载有以下字段:
Pad Length:一种8 bit 字段,以字节为单位,包含帧填充的长度。此字段仅在设置填充标志时出现。
E(exclusive):指示流依赖是排他的单位标志(参见5.3节)。该字段仅在 仅在设置了优先级标志时出现。
Stream Dependency:此流所依赖的流的31位流标识符(参见5.3节)。该字段仅在设置了优先级标志时出现。
Weight:8位无符号整数(见5.3节)。范围11到256之间的权重。该字段仅在设置了优先级标志时出现。
Header Block Fragment:报头块片段(章节4.3)。
Padding:填充的字节。
HEADERS
帧定义了以下标志:
END_STREAM (0x1):当设置时,第0位表示报头块(4.3节)是端点将为标识流发送的最后一个。HEADERS
帧携带END_STREAM标志,表示流结束。但是,带有END_STREAM标记集的HEADERS
帧后面可以跟着同一流上的CONTINUATION
帧。从逻辑上讲,CONTINUATION
帧是头帧的一部分。
END_HEADERS (0x4):当设置时,第2位表示该帧包含整个(http2)报头块(章节4.3),并且后面没有任何CONTINUATION
帧。没有END_HEADERS标记设置的HEADERS
帧后面必须跟一个同一流的CONTINUATION
帧。接收方必须将接收到的任何其他类型的帧或不同流上的帧视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
PADDED (0x8):当设置时,第3位表示填充长度字段和它所描述的任何填充都存在。
PRIORITY (0x20):当设置时,第5位表示存在排他标志(E)、流依赖项和权重字段;参见5.3节。
报头帧的有效载荷包含报头块片段(章节4.3)。不在HEADERS
帧内的报头块在CONTINUATION
帧中继续(第6.10节)。
HEADERS
帧必须与流相关联。如果接收到流标识符字段为0x0的HEADERS
帧,接收方必须响应类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
HEADERS
帧改变了4.3节中描述的连接状态。
HEADERS
帧可以包括填充,填充字段和标志与为数据帧定义的字段和标志相同(第6.1节)。Padding超过头块片段剩余大小的必须作为PROTOCOL_ERROR处理。
HEADERS
帧中的优先级信息在逻辑上等同于一个单独的PRIORITY
帧,但是包含在HEADERS
中可以避免在创建新流时出现流优先级混乱的可能性。优先级字段在流的第一个头帧之后,重新对流进行优先级排序(章节5.3.3)。
6.3 PRIORITY 优先级帧
PRIORITY
帧(type=0x2)指定了流的发送者建议的优先级(章节5.3)。它可以以任何流状态发送,包括空闲或关闭的流。
+-+-------------------------------------------------------------+
|E| Stream Dependency (31) |
+-+-------------+-----------------------------------------------+
| Weight (8) |
+-+-------------+
一个优先帧的有效负载包含以下字段:
E:(exclusive)指示流依赖是排他的单位标志(参见5.3节)。
Stream Dependency:**此流所依赖的流的31位流标识符(参见5.3节)。
Weight:表示流优先级权重的8位无符号整数(见5.3节)。范围1到256之间的权重。
优先级帧没有定义任何标志。
优先级帧总是标识一个流。如果接收到流标识符为0x0的优先帧,接收方必须响应类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
优先级帧可以在流中以任何状态发送,尽管它不能在包含单个头块的连续帧之间发送(章节4.3)。请注意,此帧可能在处理或帧发送完成后到达,这将导致它对已识别的流没有影响。对于处于“半关闭(远程)”或“关闭”状态的流,该帧只能影响已识别的流及其相关流的处理;它不影响该流上的帧传输。
优先级帧可以被发送给处于“空闲”或“关闭”状态的流。这允许通过改变未使用或关闭的父流的优先级来重新确定一组依赖流的优先级。
一个长度不超过5字节的优先帧必须作为类型为FRAME_SIZE_ERROR
的流错误(章节5.4.2)处理。
6.4 RST_STREAM
RST_STREAM
帧(type=0x3)允许立即终止流。RST_STREAM
发送的目的是请求取消流,或指示发生了错误条件。
+---------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
RST_STREAM
帧包含一个32位的无符号整数,用于识别错误代码(第7节)。错误代码表示流被终止的原因。
RST_STREAM
帧没有定义任何标志。
RST_STREAM
帧完全终止引用的流,并使其进入“关闭”状态。接收到流上的RST_STREAM
后,接收方不能为该流发送额外的帧,但优先级除外。但是,在发送RST_STREAM
之后,发送端点必须准备好接收和处理流上发送的其他帧,这些帧可能在RST_STREAM
到达之前由对等端发送。
RST_STREAM
帧必须与流相关联。如果接收到一个流标识符为0x0的RST_STREAM
帧,接收方必须将其视为类型为PROTOCOL_ERROR
的连接错误(章节5.4.1)。
对于处于“空闲”状态的流,不能发送RST_STREAM
帧。如果接收到标识空闲流的RST_STREAM
帧,接收方必须将其视为类型为PROTOCOL_ERROR
的连接错误(章节5.4.1)。
长度不超过4个字节的RST_STREAM
帧必须被视为类型为FRAME_SIZE_ERROR
的连接错误(章节5.4.1)。
6.5 SETTINGS 设置帧
SETTINGS
帧(type=0x4)传递影响端点通信方式的配置参数,例如对对等行为的首选项和约束。SETTINGS
帧还用于确认接收到这些参数。单独来说,SETTINGS
参数也可以称为“设置”。
设置参数不是协商;它们描述了接收端使用的发送端特征。同一参数的不同值可以由每个对等点发布。例如,客户端可能设置较高的初始流控制窗口,而服务器可能设置较低的值以节省资源。
SETTINGS
帧必须在连接开始时由两个端点发送,并且可以在连接生命周期的任何其他时间由任一端点发送。实现必须支持本规范定义的所有参数。
SETTINGS
帧中的每个参数将替换该参数的任何现有值。参数按照它们出现的顺序进行处理,SETTINGS
帧的接收方不需要维护其参数的当前值以外的任何状态。因此,设置参数的值是接收方看到的最后一个值。
SETTINGS
帧参数由接收方确认。要启用它,SETTINGS
帧定义了以下标志:
ACK (0x1):当设置时,第0位表示该帧确认接收并应用了对方的设置帧。当设置此位时,设置帧的有效负载必须为空。收到设置帧的ACK标志设置和长度字段值不是0必须被视为类型为FRAME_SIZE_ERROR
的连接错误(章节5.4.1)。更多信息,请参见第6.5.3节(“设置同步”)。
SETTINGS
帧总是应用于连接,而不是单个流。设置帧的流标识符必须为0 (0x0)。如果一个端点接收到一个设置帧,其流标识符字段不是0x0,那么这个端点必须响应一个类型为PROTOCOL_ERROR
的连接错误(章节5.4.1)。
设置帧影响连接状态。格式不好或不完整的设置帧必须被视为类型为PROTOCOL_ERROR
的连接错误(章节5.4.1)。
如果设置帧的长度不是6个字节的倍数,则必须被视为类型为FRAME_SIZE_ERROR
的连接错误(章节5.4.1)。
6.5.1 SETTINGS 帧格式
设置帧的有效负载由零个或多个参数组成,每个参数由一个无符号的16位设置标识符和一个无符号的32位值组成。
+-------------------------------+
| Identifier (16) |
+-------------------------------+-------------------------------+
| Value (32) |
+---------------------------------------------------------------+
6.5.2 定义 SETTINGS 参数
注意:这些SETTINGS 参数,影响的是整个连接,而不是单个流
定义了以下参数:
SETTINGS_HEADER_TABLE_SIZE (0x1):允许发送方通知远程端点用于解码(http2)报头块的报头压缩表的最大大小(以字节为单位)。编码器可以选择任何大小等于或小于这个值,通过使用特定于报头块中的报头压缩格式的信令(见[压缩])。初始值为4,096个字节。
SETTINGS_ENABLE_PUSH (0x2):此设置可用于禁用服务器推送(章节8.2)。如果端点接收到设置为0的参数,则不能发送PUSH_PROMISE
帧。一个已经将该参数设置为0并已确认的端点必须将接收PUSH_PROMISE
帧视为类型为PROTOCOL_ERROR
的连接错误(章节5.4.1)。初始值为1,表示允许服务器推送。0或1以外的任何值都必须被视为类型为PROTOCOL_ERROR
的连接错误(章节5.4.1)。
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(16,384)个字节。端点通知的值必须在这个初始值和允许的最大帧大小(2^24-1或16,777,215个字节)之间。超出此范围的值必须作为类型为PROTOCOL_ERROR
的连接错误(章节5.4.1)处理。
SETTINGS_MAX_HEADER_LIST_SIZE (0x6):此通知设置通知对等点发送方准备接受的(http2)头列表的最大大小(字节)。该值基于未压缩的头字段大小,包括名称和值的长度(以字节单位)加上每个头字段的32个字节的开销。
对于任何给定的请求,可能会执行比所公布的更低的限制。此设置的初始值是无限制的。
端点如果接收到有未知标示符号或不支持的标识符的SETTINGS
帧,则忽略该帧。
6.5.3 SETTINGS 同步
接收者收到SETTINGS
帧(没有设置ACK标志)必须尽快处理。处理完进行回复确认。
设置帧中的值必须按照它们出现的顺序进行处理,在值之间不进行其他帧处理。必须忽略不支持的参数。一旦所有的值都被处理,接收方必须立即发出有ACK标志的设置帧。一旦收到有ACK标志的设置帧,则发送方SETTINGS
参数生效。
如果设置帧的发送方在合理的时间内没有收到确认,它可能会发出类型为SETTINGS_TIMEOUT
的连接错误(节5.4.1)。
6.6 PUSH_PROMISE 推送帧
PUSH_PROMISE
帧(type=0x5)用于在发送方打算发起的流之前通知对等端点。PUSH_PROMISE
帧包含端点计划创建的流的31位无符号标识符,以及一组为流提供附加上下文的报头。第8.2节详细描述了PUSH_PROMISE
帧的使用。
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|R| Promised Stream ID (31) |
+-+-----------------------------+-------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
PUSH_PROMISE
帧有效负载有以下字段:
Pad Length:一种8位字段,字节为单位,包含帧填充的长度。此字段仅在设置填充标志时出现。
R:一个保留位。
Promised Stream ID:一个31位无符号整数,用于标识PUSH_PROMISE
帧保留的流。承诺的流标识符必须是发送方发送的下一个流的有效选择(参见5.1.1节中的“新流标识符”)。
Header Block Fragment:包含请求报头字段的报头块片段(4.3节)。
Padding:填充的字节。
PUSH_PROMISE
帧定义了以下标志:
-
END_HEADERS (0x4):当设置时,Flag第2位表示该帧包含整个报头块(章节4.3),并且后面没有任何
CONTINUATION
帧。没有设置END_HEADERS标记的PUSH_PROMISE
帧后面必须跟一个CONTINUATION
帧。接收方必须将接收到的任何其他类型的帧或不同流上的帧视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。 -
PADDED (0x8):flag当设置时,第3位表示填充长度字段和它所描述的任何填充都存在。
PUSH_PROMISE
帧只能在对等发起的流上发送,该流要么是“打开”的,要么是“半关闭(远程)”的。PUSH_PROMISE
帧的流标识符指示与它相关联的流。如果流标识符字段指定了值0x0,接收方必须响应类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
承诺的流不需要按照承诺的顺序使用。PUSH_PROMISE
只保留流标识符供以后使用。
如果对等端点的SETTINGS_ENABLE_PUSH
设置设置为0,则不能发送PUSH_PROMISE
。已经设置了此设置并收到确认的端点必须将接收到的PUSH_PROMISE
帧视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
推送承诺帧的接收方可以通过返回一个RST_STREAM来引用推送承诺的流标识符来选择拒绝推送承诺的流。
PUSH_PROMISE
帧以两种方式修改连接状态。首先,包含报头块(章节4.3)可能会修改报头压缩的状态。其次,PUSH_PROMISE还保留一个流供以后使用,从而导致承诺的流进入“保留”状态。发送方不能在流上发送推送承诺(PUSH_PROMISE
),除非流是“开放的”或“半封闭的(远程的)”;发送方必须确保承诺的流是一个新的流标识符的有效选择(即,承诺的流必须处于“空闲”状态)。
因为PUSH_PROMISE
保留了一个流,忽略PUSH_PROMISE
帧会导致流状态变得不确定。接收方必须将接收到的PUSH_PROMISE
处理为类型为PROTOCOL_ERROR的连接错误(章节5.4.1),该流既不是“打开的”,也不是“半关闭的(本地)”。但是,在相关流上发送了RST_STREAM
的端点必须处理PUSH_PROMISE
帧,该帧可能在接收和处理RST_STREAM
帧之前创建。
接收方必须将接收到的承诺非法流标识符的PUSH_PROMISE(章节5.1.1)视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。请注意,非法流标识符是当前不处于“空闲”状态的流的标识符。
PUSH_PROMISE
帧可以包含padding, padding字段和flags标志与为DATA
帧定义的相同(第6.1节)。
6.7 PING
PING
帧(type=0x6)是一种机制,用于度量从发送方到PING方的最小往返时间,以及确定空闲连接是否仍然有效。PING帧可以从任何端点发送。
+---------------------------------------------------------------+
| |
| Opaque Data (64) |
| |
+---------------------------------------------------------------+
除了帧头之外,PING
帧必须在有效负载中包含8个字节的不透明数据(Opaque Data)。发送方可以包含它选择的任何值,并以任何方式使用这些字节。
不包含ACK标志的PING帧的接收方必须以相同的有效负载发送响应中设置了ACK标志的PING
帧。PING响应应该比其他任何帧具有更高的优先级。
PING帧定义了以下标志:
ACK (0x1):当设置时,flag第0位表示这个PING帧是一个PING响应。端点必须在PING响应中设置此标志。端点必须不响应包含此标志的PING帧。 PING帧不与任何单独的流相关联。如果接收到的PING帧的流标识符字段值不是0x0,那么接收方必须响应类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
接收长度字段值不是8的PING帧必须被视为类型为FRAME_SIZE_ERROR的连接错误(章节5.4.1)。
6.8 GOAWAY (翻译的不好)
GOAWAY
帧(type=0x7)用于启动一个连接的关闭或发出严重错误条件的信号。GOAWAY
允许端点优雅地停止接受新流,同时仍然完成对之前建立的流的处理。这支持管理操作,比如服务器维护。
在启动新流的端点和发送GOAWAY
帧的远程服务器之间存在固有的竞争条件。为了处理这种情况,GOAWAY
包含在此连接中的发送端点上处理或可能处理的最后一个对等发起流的流标识符。例如,如果服务器发送一个GOAWAY
帧,所标识的流就是客户端发起的编号最高的流。
一旦发送,如果流的标识符高于包含的最后一个流标识符,发送方将忽略在接收方发起的流上发送的帧。尽管可以为新流建立新连接,但是GOAWAY
帧的接收者不能在连接上打开额外的流。
如果GOAWAY
的接收方在流上发送的数据具有比GOAWAY
帧中指示的更高的流标识符,那么这些流将不被处理或不会被处理。GOAWAY
帧的接收方可以像对待从未创建过的流一样对待这些流,从而允许稍后在新的连接上重试这些流。
端点应该总是在关闭连接之前发送一个GOAWAY
帧,这样远程对等点就可以知道流是否已经被部分处理。例如,如果HTTP客户端在服务器关闭连接的同时发送了一个POST,如果服务器没有发送GOAWAY
帧来指示它可能对哪些流执行操作,客户端就无法知道服务器是否已经开始处理POST请求。
端点可以选择关闭连接,而不向行为不正常的节点发送GOAWAY
。
一个GOAWAY
帧可能不会立即在连接关闭之前出现;对于不再用于连接的GOAWAY
的接收者,在终止连接之前仍应发送GOAWAY
帧。
+-+-------------------------------------------------------------+
|R| Last-Stream-ID (31) |
+-+-------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
| Additional Debug Data (*) |
+---------------------------------------------------------------+
GOAWAY帧没有定义任何标志。
GOAWAY帧应用于连接,而不是特定的流。端点必须将流标识符不是0x0的GOAWAY
帧视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
GOAWAY
帧中的最后一个流标识符包含编号最高的流标识符,GOAWAY
帧的发送方可能已经对该标识符采取了操作,或者可能还在对该标识符采取了操作。包括已识别流在内的所有流都可能以某种方式被处理。如果没有处理流,可以将最后一个流标识符设置为0。
注意:在此上下文中,“处理”意味着流中的一些数据被传递到某个软件的更高层,该软件可能已经采取了一些操作。
如果连接在没有GOAWAY
帧的情况下终止,那么最后一个流标识符实际上是可能的最高的流标识符。
对于在连接关闭前未完全关闭的低编号标识符或等编号标识符的流,不可能重新尝试请求、事务或任何协议活动,但HTTP GET、PUT或DELETE等幂等操作除外。使用较高编号流的任何协议活动都可以使用新连接安全地重试。
在编号低于或等于最后一个流标识符的流上的活动仍然可能成功完成。GOAWAY
帧的发送方可以通过发送一个GOAWAY
帧优雅地关闭连接,将连接保持在“打开”状态,直到所有正在进行的流完成。
如果环境发生变化,端点可以发送多个GOAWAY
帧。例如,在优雅关闭期间发送带有NO_ERROR的GOAWAY
的端点随后可能会遇到需要立即终止连接的情况。接收到的最后一个GOAWAY
帧的最后一个流标识符表示可以对哪些流进行操作。端点不能增加它们在最后一个流标识符中发送的值,因为对等点可能已经在另一个连接上重试了未处理的请求。
当服务器关闭连接时,无法重试请求的客户端将丢失所有正在运行的请求。对于那些可能不使用HTTP/2为客户端提供服务的中介体来说尤其如此。试图优雅地关闭连接的服务器应该发送一个初始的GOAWAY
帧,最后一个流标识符设置为2^31-1和一个NO_ERROR代码。这将向客户端发出关闭即将来临的信号,并且禁止启动进一步的请求。在允许任何正在运行的流创建时间(至少一次往返时间)之后,服务器可以发送另一个带有更新后的last流标识符的GOAWAY帧。这确保了可以在不丢失请求的情况下干净地关闭连接。
在发送一个GOAWAY帧之后,发送方可以丢弃由接收方发起的标识符高于最后标识符的流的帧。但是,不能完全忽略任何改变连接状态的帧。例如,HEADERS、PUSH_PROMISE和CONTINUATION帧必须进行最低限度的处理,以确保头文件压缩所维护的状态是一致的(参见4.3节);类似地,数据帧必须计算到连接流控制窗口。处理这些帧失败会导致流控制或头压缩状态变得不同步。
GOAWAY帧还包含一个32位错误代码(第7节),该代码包含关闭连接的原因。
端点可以将不透明数据附加到任何GOAWAY帧的有效负载。其他调试数据仅用于诊断目的,不携带任何语义值。调试信息可能包含对安全性或隐私敏感的数据。日志或持久存储的调试数据必须有足够的安全措施来防止未经授权的访问。
6.9 WINDOW_UPDATE
WINDOW_UPDATE
帧(类型=0x8)用于实现流控制;请参阅第5.2节以获得概述。
流控制在两个级别上运行:在每个单独的流上和在整个连接上。
这两种类型的流控制都是逐跳的,也就是说,只在两个端点之间进行。中介不转发依赖连接之间的WINDOW_UPDATE
帧。然而,节流任何接收器的数据传输可能间接导致流控制信息向原始发送方传播。
流控制仅适用于被识别为受流控制的帧。在本文档中定义的帧类型中,只包括数DATA
帧。必须接受和处理不受流控制的帧,除非接收方无法分配资源来处理该帧。如果接收方不能接受帧,接收方可能会响应流错误(第5.4.2节)或类型为FLOW_CONTROL_ERROR
的连接错误(第5.4.1节)。
+-+-------------------------------------------------------------+
|R| Window Size Increment (31) |
+-+-------------------------------------------------------------+
WINDOW_UPDATE
帧的有效负载是一个保留位加上一个31位无符号整数,该整数表示发送方除了现有流控制窗口之外可以传输的字节数。流控制窗口的增量的合法范围是1到2^31-1(2,147,483,647)字节。
WINDOW_UPDATE
帧没有定义任何标志。
WINDOW_UPDATE
帧可以特定于流或整个连接。在前一种情况中,帧的流标识符表示受影响的流;在后者中,值“0”表示整个连接是帧的主题。
接收方必须将接收到的带有流控制窗口增量为0的WINDOW_UPDATE
帧视为类型为PROTOCOL_ERROR的流错误(章节5.4.2);连接流控制窗口上的错误必须被视为连接错误(章节5.4.1)。
WINDOW_UPDATE
可以由发送带有END_STREAM标记的帧的对等端发送。这意味着接收方可以接收“半关闭(远程)”或“关闭”流上的WINDOW_UPDATE
帧。接收方不能将其视为错误(见5.1节)。
接收流控制帧的接收方必须总是考虑它对连接流控制窗口的贡献,除非接收方将其视为连接错误(章节5.4.1)。这是必要的,即使帧是错误的。发送方将帧计数到流控制窗口,但如果接收方没有计数,发送方和接收方的流控制窗口可能会不同。
如果一个 WINDOW_UPDATE
帧的长度不是4个字节,则必须被视为类型为 FRAME_SIZE_ERROR 的连接错误(章节5.4.1)。
6.9.1 流控窗口
HTTP/2中的流控制是通过每个发送方在每个流上保存一个窗口来实现的。流控制窗口是一个简单的整数值,指示允许发送方传输多少个字节的数据;因此,它的大小是接收器的缓冲能力的度量。
两个流控制窗口适用:流控制窗口和连接流控制窗口。发送方发送的流控制帧的长度不能超过接收方所通知的流控制窗口中任何一个窗口的可用空间。如果流控制窗口中没有可用的空间,那么END_STREAM标志设置为零长度的帧(即空数据帧)可以被发送
。
对于流量控制计算,9字节的帧头(header)不被计数在内。
在发送一个流控制的帧之后,发送者通过传输帧的长度减少两个窗口中的可用空间。
当它消耗数据并释放流控制窗口中的空间时,帧的接收者发送一个WINDOW_UPDATE
帧。分别为流级和连接级流控制窗口发送WINDOW_UPDATE
帧。
接收到WINDOW_UPDATE
帧的发送方会按照帧中指定的数量更新相应的窗口。
发送方不能允许流控制窗口超过2^31-1字节。如果发送方接收到导致流控制窗口超过这个最大值的WINDOW_UPDATE
,它必须适当地终止流或连接。对于流,发送方发送一个带有错误代码FLOW_CONTROL_ERROR
的RST_STREAM
;对于连接,发送一个带有错误代码FLOW_CONTROL_ERROR
的GOAWAY
帧。
来自发送方的流控制帧和来自接收方的WINDOW_UPDATE
帧彼此之间是完全异步的。此属性允许接收方主动更新发送方保留的窗口大小,以防止流停止。
6.9.2 初始流控制窗口大小
当HTTP/2连接首次建立时,将创建新的流,初始流控制窗口大小为65,535字节。连接流控制窗口也是65,535字节。两个端点都可以通过在SETTINGS
帧中包含SETTINGS_INITIAL_WINDOW_SIZE
的值来调整新流的初始窗口大小,该SETTINGS
帧构成连接序言的一部分。连接流控制窗口只能使用WINDOW_UPDATE
帧来更改。
在接收SETTINGS
帧(设置帧为SETTINGS_INITIAL_WINDOW_SIZE设置值)之前,端点只能在发送流控制帧时使用默认的初始窗口大小。类似地,连接流控制窗口被设置为默认的初始窗口大小,直到收到一个WINDOW_UPDATE
帧。
除了更改尚未活动的流的流控制窗口外,SETTINGS
帧还可以更改具有活动流控制窗口(即处于“打开”或“半关闭(远程)”状态的流)的初始流控制窗口大小。当SETTINGS_INITIAL_WINDOW_SIZE
的值发生变化时,接收方必须根据新值和旧值之间的差异来调整它维护的所有流流控制窗口的大小。
对SETTINGS_INITIAL_WINDOW_SIZE
的更改可能导致流控制窗口中的可用空间变为负值。发送方必须跟踪负流控制窗口,并且在收到导致流控制窗口变为正流控制窗口的WINDOW_UPDATE
帧之前,不能发送新的流控制帧。
例如,如果客户端在建立连接时立即发送60 KB,而服务器将初始窗口大小设置为16 KB,那么客户端将在收到设置帧时重新计算可用的流控制窗口为-44 KB。客户端保留一个负流控制窗口,直到WINDOW_UPDATE
帧将窗口恢复为正,之后客户端可以继续发送。
“设置”帧无法更改连接流控制窗口。
端点必须将导致流控制窗口超过最大大小的SETTINGS_INITIAL_WINDOW_SIZE
更改视为类型为FLOW_CONTROL_ERROR的连接错误(节5.4.1)。
6.9.3 减少流窗口大小
希望使用比当前大小更小的流控制窗口的接收器可以发送一个新的设置(SETTINGS
)帧。然而,接收方必须准备好接收超过此窗口大小的数据,因为发送方可能在处理SETTINGS
帧之前发送超过下限的数据。
在发送减小初始流控制窗口大小的SETTINGS
帧之后,接收器可以继续处理超过流控制限制的流。允许流继续并不允许接收方立即减少为流控制窗口保留的空间。这些流的进度也会暂停,因为需要WINDOW_UPDATE
帧来允许发送方继续发送。相反,接收方可以为受影响的流发送带有FLOW_CONTROL_ERROR错误代码的RST_STREAM
。
6.10 CONTINUATION 延续帧
CONTINUATION
帧(type=0x9)用于继续HTTP2头块片段序列(章节4.3)。可以发送任意数量的CONTINUATION
帧,只要前一帧在同一流上,并且是HEADERS
、PUSH_PROMISE
, END_HEADERS
且标记的CONTINUATION
帧。
+---------------------------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
延续帧有效负载包含标题块片段(章节4.3)。
CONTINUATION
帧定义了以下标志:
END_HEADERS (0x4):当设置时,第2位表示该帧结束报头块(章节4.3)。
如果未设置END_HEADERS位,则必须在该帧后面跟着另一个CONTINUATION
帧。接收方必须将接收到的任何其他类型的帧或不同流上的帧视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
CONTINUATION
帧更改4.3节中定义的连接状态。
CONTINUATION
帧必须与流相关联。如果接收到流标识符字段为0x0的延续帧,接收方必须响应类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
一个CONTINUATION
帧之前必须有一个HEADERS
、PUSH_PROMISE
或不设置END_HEADERS标记的CONTINUATION
帧。如果一个接收方发现违反了这个规则,就必须响应一个类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
7.错误码
错误码是在RST_STREAM
和GOAWAY帧中使用的32位字段,用于表示流或连接错误的原因。
错误代码共享一个公共代码空间。一些错误代码仅应用于流或整个连接,并且在其他上下文中没有定义语义。
定义了以下错误代码:
NO_ERROR (0x0): 相关的条件不是错误的结果。例如,GOAWAY可能包括以下代码来指示连接的优雅关闭。
PROTOCOL_ERROR (0x1): 端点检测到一个非特定的协议错误。此错误用于无法获得更具体的错误代码时
。
INTERNAL_ERROR (0x2): 端点遇到意外的内部错误
。
FLOW_CONTROL_ERROR (0x3): 端点检测到它的对等方违反流控制协议
。
SETTINGS_TIMEOUT (0x4): 端点发送了设置帧,但未及时收到响应
。参见第6.5.3节(“设置同步”)。
STREAM_CLOSED (0x5): 在流被半关闭后,端点接收到一个帧。
FRAME_SIZE_ERROR (0x6): 端点接收到一个大小无效的帧。
REFUSED_STREAM (0x7): 端点在执行任何应用程序处理之前拒绝流(详见8.1.4节)。
CANCEL (0×8): 由端点使用,指示不再需要流。
COMPRESSION_ERROR (0x9): 端点无法维护连接的头压缩上下文。
CONNECT_ERROR (0xa): 为响应连接请求(章节8.3)而建立的连接被重置或非正常关闭
。
ENHANCE_YOUR_CALM (0xb): 端点检测到它的对等端表现出可能产生过度负载的行为
。
INADEQUATE_SECURITY (0xc): 基础传输具有不满足最低安全要求的属性(见第9.2节)。
HTTP_1_1_REQUIRED (0xd): 端点要求使用HTTP/1.1而不是HTTP/2。
未知或不支持的错误代码不能触发任何特殊行为。实现可以将它们视为等同于 INTERNAL_ERROR。
8. HTTP/2 消息交换
HTTP/2的目的是尽可能与当前使用的HTTP兼容。这意味着,从应用程序的角度来看,协议的特性在很大程度上没有改变。为了实现这一点,保留了所有请求和响应语义,尽管传递这些语义的语法已经改变。
因此HTTP/1.1的语义和内容 RFC7232 、条件请求 RFC7232 、范围请求 RFC7233 、缓存RFC7234、认证RFC7235的规范和要求适用于HTTP/2。HTTP/1.1消息语法和路由RFC7230的选定部分,比如HTTP和HTTPS URI方案,也适用于HTTP/2,但是这些协议的语义表达式在下面的小节中定义。
8.1 HTTP 请求响应交换
客户端使用以前未使用的流标识符(章节5.1.1)对新的流发送HTTP请求。服务器在与请求相同的流上发送HTTP响应。
一个HTTP消息(请求或响应)包括: (HEADERS
+ CONTINUATION
+ DATA
)
-
对于响应,有零个或多个
HEADERS
帧(每个后面跟着零个或多个CONTINUATION
延续帧),这个HEADERS
帧中包含HTTP响应的头信息。 -
一个
HEADERS
帧(后面跟着零个或多个连续帧),包含消息报头(见[RFC7230],第3.2节), -
零个或多个包含有效载荷(payload)的
DATA
帧(见[RFC7230],第3.3节),和 -
可选地,一个
HEADERS
帧,后面跟着零个或多个包剩余部分的CONTINUATION
帧(如果存在的话)。
序列中的最后一帧带有END_STREAM标志,注意到带有END_STREAM标志HEADERS
帧后面可以跟着带有报头块剩余部分的CONTINUATION
帧。逻辑上,CONTINUATION
相当于HEADERS
的一部分。如果HEADERS
帧设置了END_HEADERS (0x4),则后面不可以跟CONTINUATION
帧。
其他帧(来自任何流)不能出现在HEADERS
帧和任何CONTINUATION
帧(如果有的话)之间。
HTTP/2使用DATA
帧来携带消息有效负载。HTTP/2中不能使用RFC7230第4.1节中定义的分块传输编码(Chunked Transfer Coding)。
尾随 header 字段包含在终止流的头块中。这样的报头块是一个序列,以HEADERS
帧开始,接着是零个或多个CONTINUATION
帧,其中HEADERS
帧带有END_STREAM标志。在第一个未终止流的头块之后的头块不属于HTTP请求或响应的一部分。
HEADERS
帧(和相关的CONTINUATION
帧)只能出现在流的开始或结束。在接收到最终的(非信息的)状态码后,接收到没有END_STREAM标记设置的报头帧的端点必须将相应的请求或响应视为格式错误(章节8.1.2.6)。
HTTP请求/响应交换完全使用单个流。请求从HEADERS帧开始,该帧将流置于“打开”状态。请求以带有END_STREAM的帧结束,这将导致流对客户端变成“半关闭(本地)”,对服务器变成“半关闭(远程)”。响应以HEADERS
帧开始,以带有END_STREAM的帧结束,该帧将流置于“关闭”状态。
HTTP响应在服务器发送(或者客户端接收)设置了END_STREAM标志的帧(包括完成报头块所需的任何CONTINUATION
帧)之后完成。如果响应不依赖于尚未发送和接收的请求的任何部分,则服务器可以在客户端发送整个请求之前发送完整响应。当此为真时,服务器可以在发送完整响应(即带有END_STREAM标志的帧)后,通过发送一个错误代码为NO_ERROR的RST_STREAM
,请求客户端中止无错误的请求传输。客户端不能因为接收到这样的RST_STREAM
而放弃响应,尽管客户端总是可以根据自己的判断因为其他原因而放弃响应。(ps:这段翻译的不好,应该转为更符合中文的逻辑习惯)
8.1.1 从HTTP/2升级
HTTP/2删除了对101(交换协议)信息状态码([RFC7231],第6.2.2节)的支持。
101(交换协议)的语义不适用于多路复用协议。备选协议可以使用与HTTP/2相同的机制来协商它们的使用(参见第3节)。
8.1.2 HTTP 头字段
HTTP头字段以一系列键值对的形式携带信息。有关已注册HTTP报头的清单,请参阅 Message Header Field
就像HTTP/1一样。头字段名是ASCII字符的字符串,以不区分大小写的方式进行比较。但是,在HTTP/2中,头字段名在编码之前必须转换为小写。包含大写头字段名的请求或响应必须被视为格式错误(章节8.1.2.6)。
8.1.2.1 Pseudo-Header 字段
虽然HTTP / 1。x使用消息起始行(参见[RFC7230], 3.1节)来传递目标URI、请求的方法和响应的状态代码,HTTP/2使用特殊的伪头字段,以’:’字符(ASCII 0x3a)开头。
伪头字段(Pseudo-Header fields)不是HTTP头字段。端点不能生成本文档中定义的以外的伪头字段。
伪头字段仅在定义它们的上下文中有效。为请求定义的伪头字段不能出现在响应中;为响应定义的伪头字段不能出现在请求中。伪头字段不能出现在尾部( trailers)。端点必须将包含未定义或无效伪头字段的请求或响应视为格式错误(章节8.1.2.6)。
所有伪标头字段必须出现在头块中常规头字段之前。任何包含伪头字段的请求或响应,如果该伪头字段出现在头块中一个常规标头字段之后,则必须被视为格式错误(章节8.1.2.6)。
8.1.2.2 Connection-Specific 头字段
HTTP/2不使用连接头字段来指示连接特定的头字段;在这个协议中,特定于连接的元数据通过其他方式传递。端点不能生成包含特定连接头字段的HTTP/2消息;任何包含特定连接头字段的消息都必须被视为格式错误(章节8.1.2.6)。
唯一的例外是TE头字段,它可能出现在HTTP/2请求中;当它是,它不能包含任何值除了“trailers”。
这意味着一个中介转换HTTP/1.x 消息的到HTTP/2将需要删除任何由连接头字段(Connection-Specific)指定的头字段,连同连接头字段本身。这样的中介还应该删除其他特定于连接的头字段,比 Keep-Alive
、 Proxy-Connection
、Transfer-Encoding
和Upgrade
,即使它们没有被连接头字段指定。
注意:HTTP/2不支持升级到其他协议。第3节中描述的握手方法被认为足以协商替代协议的使用。
8.1.2.3 请求伪随机头字段(Pseudo-Header Fields)
下面的伪头字段是为HTTP/2请求定义的:
-
:method 伪头字段包括HTTP方法([RFC7231],第4节)。
-
:scheme 伪头字段包括目标URI的scheme部分([RFC3986],第3.1节)。
:scheme不限于http和https schemed的uri。代理或网关可以为非HTTP模式(schemes)转换请求,从而允许使用HTTP与非HTTP服务进行交互。
-
:authority URI的权限部分([RFC3986],章节3.2)。该权限不能包括http或https schemed uri的已弃用的userinfo子组件。
为了确保HTTP/1.1请求行可以被准确地复制,当翻译一个HTTP/1.1请求时必须省略这个伪头字段,该请求的请求目标是原始的或星号形式的(参见[RFC7230],第5.3节)。直接生成HTTP/2请求的客户端应该使用:authority伪头字段而不是主机头(Host header)字段。将HTTP/2请求转换为HTTP/1.1的中介必须创建一个主机头字段(如果请求中没有这个字段),方法是复制:authority伪头字段的值。
-
:path伪头字段包括目标URI的路径和查询部分(路径绝对生产和可选的’?’字符,后面跟着查询生成(参见[RFC3986]第3.3和3.4节)。星号形式的请求包含:path伪头字段的值’*‘。
对于http或https uri,此伪头字段不能为空;不包含路径组件的http或https uri必须包含一个值’/’。此规则的例外是对不包含路径组件的http或https URI的选项请求;这些必须包括一个值为’*‘的:path伪头字段(参见[RFC7230],第5.3.4节)。
所有HTTP/2请求必须包含 :method、 :scheme 和 :path 伪头字段的一个有效值,除非它是一个连接请求(章节8.3)。省略强制性伪头字段的HTTP请求格式不正确(章节8.1.2.6)。
HTTP/2没有定义携带包含在HTTP/1.1请求行中的版本标识符的方法。
8.1.2.4 响应伪随机字段(Pseudo-Header Fields)
对于HTTP/2响应,定义了一个单一的 :status 伪头字段,它携带HTTP状态代码字段(见[RFC7231],第6节)。否则,响应是格式错误的(8.1.2.6节)。
HTTP/2没有定义一种方式来携带包含在HTTP/1.1状态行中的版本或原因短语(reason phrase)。
8.1.2.5 压缩 Cookie 头字段
Cookie头字段[Cookie]使用分号(“;”)来分隔Cookie键值对(或”crumbs”)。这个头字段不遵循HTTP中的列表构造规则(参见[RFC7230],第3.2.2节),该规则防止cookie-键值对被分隔成不同的名称-值对。当单个cookie-键值对被更新时,这会显著降低压缩效率。
为了实现更好的压缩效率,Cookie报头字段可以分割为单独的报头字段,每个字段都有一个或多个Cookie对。如果在解压后有多个Cookie头字段,这些字段必须使用0x3B, 0x20 (ASCII字符串”;),然后传入一个非HTTP/2上下文,例如HTTP/1.1连接,或一个通用的HTTP服务器应用程序。
因此,下面两个Cookie头字段列表在语义上是等价的。
cookie: a=b; c=d; e=f
cookie: a=b
cookie: c=d
cookie: e=f
8.1.2.6 格式错误(Malformed)的请求或响应
一个错误格式的请求或响应是一个有效的HTTP/2帧序列,但由于存在无关的帧,被禁止的头字段,没有强制性的头字段,或包含大写头字段名而无效等原因而格式错误。
包含有效负载主体的请求或响应可以包含内容长度标题字段。如果内容长度头字段的值不等于构成主体的数据帧有效负载长度的总和,则请求或响应的格式也会出现错误。如[RFC7230]第3.3.2节所述,被定义为没有有效负载的响应可以有一个非零内容长度的头字段,即使数据帧中不包含任何内容。
处理HTTP请求或响应的中介(即不充当隧道的任何中介)不能转发格式不正确的请求或响应。检测到的格式不正确的请求或响应必须被视为类型为PROTOCOL_ERROR的流错误(章节5.4.2)。
对于格式不正确的请求,服务器可能会在关闭或重置流之前发送HTTP响应。客户端不能接格式错误的响应。请注意,这些要求旨在防止针对HTTP的几种常见攻击;它们是故意严格的,因为宽松的实现可能暴露于这些漏洞。
8.1.3 示例
本节展示HTTP/1.1请求和响应,并举例说明等效的HTTP/2请求和响应。
HTTP GET请求包括请求头字段,而没有有效负载主体,因此作为单个HEADRS
帧传输,后面跟着0个或多个包含请求头字段序列化块的CONTINUATION
帧。下面的HEADRS
帧设置了END_HEADERS和END_STREAM标志;没有CONTINUATION
被发送。(设置了END_HEADERS标志的HEADRS
帧,后面不能接CONTINUATION
帧)
GET请求
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/jpe
类似地,只包含响应头字段的响应被传输为包含响应头字段的序列化块的HEADRS
帧(同样,后面跟着零个或多个CONTINUATION
帧)。
只包含响应头的响应
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请求,包括请求头字段和负载数据传输作为一个标题,紧随其后的是零个或多个CONTINUATION
帧包含请求头字段,紧随其后的是一个或多个DATA
帧,最后CONTINUATION
(或者标题)帧设置有END_HEADERS标志和最终的数据帧设置了END_STREAM标志:
POST请求
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}
注意,任何给定头字段的数据都可以在头块片段之间分散。在这个例子中,头字段分配给帧只是一个说明。
包含报头字段和有效负载数据的响应作为HEADRS
帧传输,后面跟着0个或多个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}
使用1xx状态码而不是101的信息响应作为HEADRS
帧传输,后面跟着零个或多个延续帧(CONTINUATION
)。
尾部头字段在请求或响应标头块和所有数据帧都已发送之后作为头块发送。启动尾部头块的HEADRS
帧设置了END_STREAM标志。
下面的示例包括一个100 (Continue)状态码,该状态码是发送给一个包含“100- Continue” token 的请求的,以及头字段,一个尾部头字段:
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
8.1.4 HTTP/2中的请求可靠性机制
在HTTP/1.1中,HTTP客户端无法在错误发生时重试非幂等请求,因为无法确定错误的性质。有可能在错误之前发生了某些服务器处理,如果重新尝试请求,可能会导致不希望的结果。
HTTP/2提供了两种机制来保证客户端请求没有被处理:
-
GOAWAY
帧表示可能已经处理的最高流数。因此,流上的请求具有较高的数字,因此可以保证重试是安全的。 -
REFUSED_STREAM 错误代码可以包含在
RST_STREAM
帧中,以指示流在任何处理发生之前被关闭。在重置流上发送的任何请求都可以安全地重试。
未被处理的请求不会失败;客户端可能会自动重试它们,即使是那些使用非幂等方法的。
除非服务器能够保证,否则它不能表示流未被处理。如果流上的帧被传递到任何流的应用层,那么REFUSED_STREAM不能被用于该流,并且一个GOAWAY
帧必须包含一个大于或等于给定流标识符的流标识符。
除了这些机制之外,PING
帧还为客户端提供了一种简单测试连接的方法。保持空闲的连接可能会中断,因为一些中间件(例如,网络地址转换器NAT或负载均衡器)会静默地放弃连接绑定。PING
帧允许客户端在不发送请求的情况下安全地测试连接是否仍然处于活动状态。
8.2 服务器推送
HTTP/2允许服务器预先将与客户端发起的请求相关的资源预先发送(或“推”)给客户端。当服务器知道客户端需要这些响应内容时,这可能非常有用。
客户端可以请求禁用服务器推送,但这是针对每一跳单独协商的。可以将SETTINGS_ENABLE_PUSH设置设置为0,以指示服务器推送被禁用。
承诺的请求必须是可缓存的(参见[RFC7231],第4.2.3节),必须是安全的(参见[RFC7231],第4.2.1节),并且必须不包含请求主体。如果客户端接收一个承诺的请求,是不可缓存的,不知道是安全的,包含一个请求主体,则必须重置承诺的流通过返回类型PROTOCOL_ERROR的错误(节5.4.2)。注意,如果客户端不认为新定义的方法是安全的,这可能导致承诺的流被重置。
如果客户端实现了HTTP缓存,则可缓存的服务端推送可缓存响应(参见[RFC7234],第3节)。当由承诺流ID标识的流仍然是打开的,推送响应被认为在原始服务器上成功验证(例如,如果“无缓存”缓存响应指令存在([RFC7234],第5.2.2节))。
不能缓存的推送的响应不能被任何HTTP缓存存储。
服务器必须在:authority伪头字段中包含一个服务器是权威的值(参见10.1节)。客户端必须将服务器不权威的PUSH_PROMISE
处理为类型为PROTOCOL_ERROR的流错误(章节5.4.2)。
中介可以接收来自服务器的推送,并选择不将它们转发给客户端。换句话说,如何利用推送的信息取决于中介。同样,中介可以选择向客户端进行额外的推送,而服务器不需采取任何操作。
客户不能推送。因此,服务器必须将接收到的PUSH_PROMISE
帧视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。客户端必须拒绝将SETTINGS_ENABLE_PUSH设置更改为0以外的值的任何尝试,方法是将该消息视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。
8.2.1 推送请求
服务器推送在语义上等同于响应请求的服务器;然而,在本例中,该请求也是由服务器以PUSH_PROMISE
帧的形式发送的。
PUSH_PROMISE
帧包含一个头块,该头块包含服务器为请求设置的完整的请求头字段集。不可能将响应推送到包含请求主体的请求。
推送响应总是与来自客户端的显式请求相关联。服务器发送的PUSH_PROMISE
帧在该显式请求流上发送。PUSH_PROMISE
帧还包括一个流标识符,该标识符是从服务器可用的流标识符中选择的(参见5.1.1节)。
PUSH_PROMISE
中的头字段和任何后续CONTINUATION
帧必须是一个有效的、完整的请求头字段集(章节8.1.2.3)。服务器必须在:method伪头字段中包含一个安全且可缓存的方法。如果客户端接收到的PUSH_PROMISE
没有包含一个完整有效的头字段集,或者:method伪头字段标识了一个不安全的方法,它必须响应类型为PROTOCOL_ERROR的流错误(章节5.4.2)。
服务器应该在发送任何引用承诺响应的帧之前发送PUSH_PROMISE
(第6.6节)帧。这样就避免了客户端在接收任何PUSH_PROMISE
帧之前发出请求的竞争。(意思就是客户端你不用请求了,我服务端直接提前推给你了,不用多此一举了,嗯)
例如,如果服务器收到一个请求文档,其中包含嵌入式链接到多个图像文件,服务器选择把这些额外的图片给客户端,发送PUSH_PROMISE
帧包含图像链接的数据帧之前,确保客户能够看到一个资源发现之前将推动嵌入式链接。类似地,如果服务器推送头块引用的响应(例如,在链接头字段中),那么在发送头块之前发送一个PUSH_PROMISE
可以确保客户端不会请求这些资源。
PUSH_PROMISE
帧不能被客户端发送。
PUSH_PROMISE
帧可以由服务器发送来响应
任何客户端发起的流,但是流必须处于“打开”或“半关闭(远程)”状态。PUSH_PROMISE
帧与组成响应的帧穿插在一起,尽管它们不能与组成单个头块的HEADRS
和CONTINUATION
帧穿插在一起。
发送一个PUSH_PROMISE
帧将创建一个新的流,并将该流放入服务器的“保留(本地)”状态和客户端的“保留(远程)”状态。
8.2.2。推送响应
在发送推PUSH_PROMISE
帧之后,服务器可以使用承诺流标识符的流上将推送的响应。服务器使用此流传输HTTP响应,使用与8.1节中定义的相同的帧序列。在发送初始HEADRS
帧之后,该流对客户端“半关闭”(5.1节)。
一旦客户端接收到PUSH_PROMISE
帧并选择接受推送的响应,在承诺的流关闭之前,客户端不应该对承诺的响应发出任何请求。
如果客户端不希望收到服务器推送响应或者服务器推送时间太长,客户端可以发送RST_STREAM
帧,使用CANCEL或REFUSED_STREAM代码和引用推流的标识符。
客户端可以使用SETTINGS_MAX_CONCURRENT_STREAMS设置来限制服务器可以并发推送的响应数量。设置SETTINGS_MAX_CONCURRENT_STREAMS值为零会阻止服务器创建必要的流,从而禁用服务器推。这并不能禁止服务器发送PUSH_PROMISE
帧;客户端需要重置任何不需要的承诺流。
接收推送响应的客户端必须验证服务器是否权威(见10.1节),或者验证提供推送响应的代理是否为相应的请求配置。例如,服务端提供一个针对域名 example.com 的证书,公共名称的是不允许为 https://www.example.org/doc
推送响应。
PUSH_PROMISE
流的响应以HEADRS
帧开始,HEADRS
帧立即将流置于服务器的“半关闭(远程)”状态和客户机的“半关闭(本地)”状态,并以承载END_STREAM的帧结束,该帧将流置于“关闭”状态。
ps: 定义 END_STREAM 标志位的帧,可以是 DATA
帧或者 HEADRS
帧。
注意:客户端从不为服务器推送发送带有END_STREAM标志的帧。
8.3 连接方法
在HTTP/1.x,伪方法 ( pseudo-method ) CONNECT ([RFC7231],第4.3.6节)用于将HTTP连接转换为到远程主机的隧道。CONNECT主要与HTTP代理一起使用,用于与源服务器建立TLS会话,以便与https资源交互。
在HTTP/2中,CONNECT方法用于在单个HTTP/2流上建立到远程主机的隧道,以实现类似的目的。HTTP头字段映射如8.1.2.3节(“请求伪头字段”)中定义的那样工作,但有一些区别。具体地说:
-
:method 伪头字段被设置为CONNECT。
-
必须省略 :scheme 和 :path 伪头字段。
-
:authority 伪头字段包含要连接的主机和端口(相当于connect请求的请求-目标的授权形式(参见[RFC7230],第5.3节))。
不符合这些限制的连接请求是格式错误的(章节8.1.2.6)。
支持CONNECT的代理建立到:authority伪头字段中标识的服务器的TCP连接[TCP]。一旦这个连接成功建立,代理发送一个包含2xx系列状态码的HEADRS
帧给客户端,这在[RFC7231],第4.3.6节中定义。
在每个对等点发送初始HEADRS
帧之后,所有后续DATA
帧都对应于TCP连接上发送的数据。客户端发送的任何DATA
帧的有效载荷由代理传输到TCP服务器;从TCP服务器接收到的数据由代理组装成DATA
帧。数据或流管理帧以外的帧类型(RST_STREAM
, WINDOW_UPDATE
,和PRIORITY
)不能在连接的流上发送,如果接收到,必须作为流错误处理(章节5.4.2)。
TCP连接可以由任一对等点关闭。数据帧上的END_STREAM标志被视为等同于TCP FIN位。客户端接收到带有END_STREAM标志的帧后,期望发送一个设置了END_STREAM标志的DATA
帧。接收带有END_STREAM标志集的DATA
帧的代理发送带有最后一个TCP段上设置的FIN位的附加数据。接收一个设置了FIN位的TCP段的代理会发送一个设置了END_STREAM标志的DATA
帧。注意,最终的TCP段或DATA
帧可能是空的。
TCP连接错误用RST_STREAM
发出信号。代理将TCP连接中的任何错误(包括接收设置了RST位的TCP段)视为类型为CONNECT_ERROR
的流错误(章节5.4.2)。相应地,如果代理检测到流或HTTP/2连接的错误,它必须发送设置了RST位的TCP段
9 额外的 HTTP 要求/注意事项
本节概述HTTP协议的属性,这些属性可以提高互操作性,减少已知安全漏洞的暴露,或减少实现变化的可能性。
9.1 连接管理
HTTP/2连接是持久的。为了获得最佳性能,在确定不需要与服务器进行进一步通信之前(例如,当用户从特定的web页面导航离开时),或者在服务器关闭连接之前,客户端应该不会关闭连接。
对于给定主机和端口,客户端不应该打开超过一个HTTP/2连接,其中主机派生自URI、选择的替代服务[ALT-SVC]或配置的代理。
客户端可以创建额外的连接作为替换,以替换接近耗尽可用流标识符空间的连接(章节5.1.1),刷新TLS连接的认证信息,或者替换遇到错误的连接(章节5.4.1)。
客户端可以创建多个连接到同一个IP地址和端口(使用不同的服务器名称指示[TLS- ext]值),或者提供不同的TLS客户端证书,但是应该避免使用相同的配置创建多个连接。
鼓励服务器尽可能长时间地保持开放连接,但允许在必要时终止空闲连接。当任何一个端点选择关闭传输层TCP连接时,终止端点应该首先发送一个GOAWAY
帧,以便两个端点能够可靠地确定之前发送的帧是否已被处理并优雅地完成,或者是否终止任何必要的剩余任务。
9.1.1 连接重用
直接或通过使用CONNECT方法创建的隧道(8.3节)连接到原始服务器的连接,可以被具有多个不同URI权限组件的请求重用。只要源服务器是权威的,就可以重用连接(第10.1节)。对于没有TLS的TCP连接,这取决于主机已解析到相同的IP地址。
对于https资源,连接重用还依赖于拥有对URI中的主机有效的证书。服务器提供的证书必须满足客户端在为URI中的主机形成新的TLS连接时执行的任何检查。
源服务器可能提供具有多个 subjectAltName 属性或带有通配符的名称的证书,其中一个通配符对URI中的权威有效。例如,一个 subjectAltName 为*.example.com 的证书可能允许对以 https://a.example.com/ 和 https://b.example.com/ 开始的uri请求使用相同的连接。
在某些部署中,为多个源重用连接可能会导致请求被定向到错误的源服务器。例如,TLS终止可能由使用TLS服务器名称指示(SNI) [ls - ext]扩展名选择源服务器的中间件执行。这意味着客户端有可能将机密信息发送到可能不是请求预期目标的服务器,即使服务器在其他方面是权威的。
不希望客户端重用连接的服务器可以通过发送一个421(错误定向请求)状态码来响应该请求(参见9.1.2节)来表明它对该请求不权威。
被配置为使用HTTP/2代理的客户端通过单个连接将请求定向到该代理。也就是说,通过代理发送的所有请求都重用到该代理的连接。
9.1.2。421(定向错误的请求)状态码
421(定向错误的请求)状态码表示请求被定向到无法产生响应的服务器上。这可以由未配置为为包含在请求URI中的 scheme 和 authority 组合生成响应的服务器发送。
从服务器接收到421(错误定向请求)响应的客户端可以在不同的连接上重试请求——无论请求方法是否是幂等的。如果一个连接被重用(章节9.1.1)或者选择了另一个服务[ALT-SVC],这是可能的。
此状态码不能由代理生成。
默认情况下,421响应是可缓存的,也就是说,除非方法定义或显式缓存控件另有说明(参见[RFC7234]的4.2.2节)。
9.2 使用TLS特性
HTTP/2的实现必须使用TLS版本1.2 [TLS12]或更高的TLS之上的HTTP/2。应该遵循[TLSBCP]中的一般TLS使用指南,以及一些特定于HTTP/2的附加限制。
TLS实现必须支持TLS的服务器名称指示(SNI) [ls - ext]扩展。HTTP/2客户端在协商TLS时必须指出目标域名。
HTTP/2的部署需要TLS 1.3或更高,只需要支持和使用SNI扩展;TLS 1.2的部署取决于以下部分的要求。我们鼓励实现提供符合的缺省值,但是我们认识到,部署最终要对遵从性负责。
9.2.1 TLS 1.2特性
本节描述可以与HTTP/2一起使用的TLS 1.2特性集上的限制。由于部署限制,当这些限制没有满足时,TLS协商可能失败。如果一个HTTP/2连接不满足这些TLS要求,端点可以立即终止该连接,并出现类型为INADEQUATE_SECURITY的连接错误(章节5.4.1)。
在TLS 1.2上部署HTTP/2必须禁用压缩。TLS压缩会暴露出一些原本不会暴露的信息[RFC3749]。一般的压缩是不必要的,因为HTTP/2提供的压缩特性更能感知上下文,因此可能更适合用于性能、安全性或其他原因。
在TLS 1.2上部署HTTP/2必须禁用重新协商。端点必须将TLS重新协商视为类型为PROTOCOL_ERROR的连接错误(章节5.4.1)。请注意,禁用重新协商可能会导致长期存在的连接变得不可用,因为底层密码套件可以加密的消息数量受到限制。
端点可以使用重新协商来为握手中提供的客户端凭据提供机密性保护,但是任何重新协商必须在发送连接序言之前发生。如果服务器在建立连接后立即看到重新协商请求,则应该请求客户端证书。
这有效地防止了在响应对特定受保护资源的请求时使用重新协商。未来的规范可能会提供一种方法来支持这个用例。或者,服务器可能使用类型HTTP_1_1_REQUIRED的错误(章节5.4)来请求客户端使用支持重新协商的协议。
对于使用临时有限域Diffie-Hellman (DHE) [TLS12]的密码套件,实现必须支持至少2048位的临时密钥交换大小;对于使用临时椭圆曲线Diffie-Hellman (ECDHE) [RFC4492]的密码套件,必须支持至少224位的临时密钥交换大小。客户端必须接受DHE的大小高达4096位。端点可能会将密钥小于下限的协商视为类型为INADEQUATE_SECURITY的连接错误(章节5.4.1)。
9.2.2 TLS 1.2密码套件
HTTP/2在TLS 1.2上的部署不应该使密码用套件黑名单(附录A)中列出的任何密码套件。
如果与黑名单中的某个密码组协商,端点可能会选择生成类型为INADEQUATE_SECURITY的连接错误(章节5.4.1)。选择使用黑名单密码套件的部署可能会引发连接错误,除非已知潜在对等点集接受该密码套件。
实现不能在对不在黑名单上的密码套件进行协商时生成此错误。因此,当客户端提供一个不在黑名单上的密码套件时,他们必须准备使用HTTP/2的密码套件。
黑名单包括TLS 1.2强制规定的密码套件,这意味着TLS 1.2部署可能拥有不交叉的允许密码套件集。为了避免这个问题导致TLS握手失败,使用TLS 1.2的HTTP/2部署必须支持TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 [TLS- ecdhe]和P-256椭圆曲线[FIPS186]。
请注意,客户端可能会通知对黑名单上的密码套件的支持,以便允许连接到不支持HTTP/2的服务器。这允许服务器使用HTTP/2黑名单上的密码套件选择HTTP/1.1。但是,如果应用协议和密码套件是独立选择的,这可能导致HTTP/2与黑名单密码套件进行谈判。
10.安全注意事项
10.1服务器权限
HTTP/2依赖于HTTP/1.1对权威的定义,以确定服务器在提供给定响应时是否具有权威(参见[RFC7230],第9.1节)。这依赖于“http”URI方案的本地名称解析,以及“https”方案的经过身份验证的服务器标识(参见[RFC2818],第3节)。
10.2 跨协议攻击
在跨协议攻击中,攻击者使客户端启动一个协议中的事务到一个要处理不同协议的服务器。攻击者可能会使事务在第二个协议中显示为一个有效的事务。结合web上下文的功能,它可以用于与私有网络中保护不佳的服务器交互。
使用HTTP/2的ALPN标识符完成一次TLS握手可以被认为是对跨协议攻击的充分保护。ALPN提供了一个明确的信号,表明服务器愿意继续使用HTTP/2,这可以防止对其他基于ls的协议的攻击。
TLS中的加密使得攻击者很难控制对明文协议进行跨协议攻击时可能使用的数据。
HTTP/2的明文版本对跨协议攻击有最小的保护。连接序言(3.5节)包含一个字符串,它被设计用来混淆HTTP/1.1服务器,但是没有为其他协议提供特殊保护。如果服务器愿意忽略HTTP/1.1请求中包含升级头字段和客户端连接序言的部分,那么它将面临跨协议攻击。
10.3 中介封装攻击
HTTP/2头字段编码允许出现在HTTP/1.1使用的Internet消息语法中无效的字段。包含无效头字段名的请求或响应必须被视为格式错误(章节8.1.2.6)。因此,中介不能将包含_无效字段名_的HTTP/2请求或响应转换为HTTP/1.1消息。
类似地,HTTP/2允许无效的头字段值。虽然可以编码的大多数值不会改变头字段解析,但是如果逐字翻译的话,回车(CR, ASCII 0xd)、换行(LF, ASCII 0xa)和零字符(NUL, ASCII 0x0)可能会被攻击者利用。任何包含头字段值中不允许的字符的请求或响应必须被视为格式错误(章节8.1.2.6)。有效字符由RFC7230第3.2节中的字段内容ABNF规则定义。
10.4 推送响应的可缓存性
推送的响应没有来自客户端的显式请求;请求由服务器在PUSH_PROMISE
帧中提供。
可以根据源服务器在Cache-Control头字段中提供的指导来缓存推送的响应。但是,如果一个服务器承载了多个租户,这可能会导致问题。例如,服务器可能向多个用户提供其URI空间的一小部分。
当多个租户在同一台服务器上共享空间时,该服务器必须确保租户不能推送它们没有权限的资源的表示形式。如果执行失败,则允许租户提供从缓存中提供的表示,从而覆盖权威租户提供的实际表示。
不能使用或缓存源服务器不权威的推送响应(见10.1节)。
10.5 拒绝服务注意事项
HTTP/2连接比HTTP/1.1连接需要更多的资源投入。头压缩和流控制的使用取决于用于存储更多状态的资源承诺。这些特性的设置确保了对这些特性的内存承诺受到严格限制。
PUSH_PROMISE
帧的数量没有以同样的方式受到限制。接受服务器推送的客户端应该限制允许处于“保留(远程)”状态的流的数量。服务器推送流过多可以被视为类型为ENHANCE_YOUR_CALM的流错误(章节5.4.2)。
处理能力不能像状态能力那样得到有效的保护。
设置帧可能被滥用,导致对等点花费额外的处理时间。这可以通过毫无意义地更改设置参数、设置多个未定义的参数或在同一帧中多次更改相同的设置来实现。WINDOW_UPDATE
或PRIORITY
帧会被滥用,造成不必要的资源浪费。
大量的小帧或空帧会被滥用,导致对等点花费时间处理帧头。但是请注意,有些使用是完全合法的,比如在流的末尾发送空数据或延续帧。
头压缩也提供了浪费处理资源的机会;参见[压缩]的第7节,了解更多关于潜在滥用的细节。
不能立即减少设置参数中的限制,这会使端点暴露给可能超过新限制的对等点的行为。特别是,在建立连接之后,客户端并不知道服务器设置的限制,并且可以在不明显违反协议的情况下超过这些限制。
所有这些特性-例如,设置更改,小帧,头压缩-都有合法的用途。只有在不必要或过度使用时,这些特性才会成为负担。
不监视这种行为的端点将自己暴露在拒绝服务攻击的风险中。实现应该跟踪这些特性的使用,并对它们的使用设置限制。端点可能会将可疑的活动视为类型为ENHANCE_YOUR_CALM的连接错误(章节5.4.1)。
10.5.1 报头块大小的限制
一个大的报头块(章节4.3)会导致一个实现来提交大量的状态。对于路由至关重要的头字段可能出现在头块的末尾,这将阻止头字段流到它们的最终目的地。这种顺序和其他原因(比如确保缓存正确性)意味着端点可能需要缓冲整个头块。由于对报头块的大小没有硬性限制,一些端点可能被迫为报头字段提供大量可用内存。
端点可以使用SETTINGS_MAX_HEADER_LIST_SIZE来通知对等方可能适用于报头块大小的限制。此设置仅是建议性的,因此端点可以选择发送超过此限制的头块,从而可能导致请求或响应被视为格式不正确。此设置是特定于连接的,因此任何请求或响应都可能遇到具有更低、未知限制的跃点。中介可以试图通过传递不同对等方提供的价值来避免这个问题,但他们没有义务这样做。
一个接收到比它愿意处理的更大的报头块的服务器可以发送一个HTTP 431(请求报头字段太大)状态码[RFC6585]。客户端可以丢弃它无法处理的响应。除非连接关闭,否则必须处理头块以确保连接状态一致。
10.5.2 连接问题
CONNECT方法可用于在代理上创建不成比例的负载,因为流的创建相对于TCP连接的创建和维护来说是开销更低的。在关闭携带连接请求的流之后,代理还可能为TCP连接维护一些资源,因为传出的TCP连接保持在TIME_WAIT状态。因此,代理不能仅依赖于SETTINGS_MAX_CONCURRENT_STREAMS来限制连接请求所消耗的资源。
10.6 使用压缩
当加密数据被压缩到与受攻击者控制的数据相同的上下文中时,许攻击者能够恢复加密的数据。HTTP/2允许压缩头字段(章节4.3);下面的问题也适用于HTTP压缩内容编码的使用([RFC7231],第3.1.2.1节)。
有明显的利用网络特征的压缩攻击(例如,[BREACH])。攻击者诱导多个包含不同明文的请求,观察每个请求中产生的密文的长度,当对秘密的猜测正确时,信息泄露。
在安全通道上通信的实现不能压缩包含机密数据和攻击者控制数据的内容,除非对每个数据源使用单独的压缩字典。如果不能可靠地确定数据源,则不能使用压缩。一般的流压缩,例如TLS提供的压缩,不能与HTTP/2一起使用(参见9.2节)。
关于压缩头字段的进一步考虑在[compression]中进行了描述。
10.7 使用 Padding
HTTP/2中的填充并不打算替代一般用途的填充,比如TLS [TLS12]可能提供的填充。多余的填充甚至可能适得其反。正确的应用程序可以依赖于对正在填充的数据有特定的知识。
为了减轻依赖于压缩的攻击,禁用或限制压缩可能比填充更可取。
填充可以用来掩盖帧内容的确切大小,并被提供来减轻HTTP中的特定攻击,例如,攻击压缩内容包括攻击者控制的明文和秘密数据(例如,[BREACH])。
使用填充物可能导致的保护效果不如看上去明显的那么好。在最好的情况下,填充只会使攻击者通过增加必须观察的帧数来推断长度信息更加困难。错误实现的填充方案很容易被击败。特别是,具有可预测分布的随机填充几乎不能提供保护;类似地,将有效负载填充到固定大小将暴露信息,因为有效负载大小跨越固定大小的边界,如果攻击者能够控制明文,这是可能的。
中介体应该保留数据帧的填充,但是可以省略HEADRS
和PUSH_PROMISE
帧的填充。一个合理的理由,中介改变框架的填充数量是为了改善填充提供的保护。
10.8 隐私方面的考虑
HTTP/2的几个特性为观察者提供了一个机会来关联单个客户端或服务器在一段时间内的动作。这些特性包括设置的值、流控制窗口的管理方式、优先级分配给流的方式、对刺激的反应时间以及对由设置控制的任何特性的处理。
根据[HTML5]第1.8节的定义,只要这些技术能在行为上产生明显的差异,就可以作为对特定客户进行指纹识别的基础。
HTTP/2使用单一TCP连接的首选方式允许用户在站点上的活动相互关联。为不同的源重用连接允许跨这些源跟踪。
因为_PING和设置帧要求立即响应_,端点可以使用它们来测量对其对等方的延迟。在某些情况下,这可能涉及隐私问题。
11.IANA的注意事项
在[TLS-ALPN]中建立的“应用层协议协商(ALPN)协议id”注册表中输入标识HTTP/2的字符串。
此文档为帧类型、设置和错误代码建立一个注册表。这些新的注册表出现在新的“超文本传输协议版本2 (HTTP/2)参数”部分。
这个文档注册HTTP2-Settings头字段用于HTTP;它还注册了421(定向错误的请求)状态码。
这个文档注册了HTTP中使用的PRI方法,以避免与连接序言(章节3.5)发生冲突。
11.1 注册HTTP/2标识字符串
本文档在[ls -ALPN]中建立的“应用层协议协商(ALPN)协议id”注册表中创建了两个HTTP/2的标识注册(见3.3节)。
当使用TLS时,”h2”字符串标识HTTP/2:
Protocol: HTTP/2 over TLS
Identification Sequence: 0x68 0x32 ("h2")
Specification: This document
“h2c”字符串识别HTTP/2时使用的cleartext TCP:
Protocol: HTTP/2 over TCP
Identification Sequence: 0x68 0x32 0x63 ("h2c")
Specification: This document
11.2 帧类型注册
这个文档为HTTP/2帧类型代码建立了一个注册表。“HTTP/2帧类型”注册表管理一个8位空间。对于0x00和0xef之间的值,“HTTP/2 Frame Type”注册表在“IETF Review”或“IESG Approval”策略[RFC5226]下运行,其中0xf0和0xff之间的值预留给实验使用。
注册表中的新条目需要以下信息:
Frame Type: 帧类型的名称或标签。 Code: 分配给帧类型的8位代码。 Specification:对规范的引用,其中包括对帧布局、语义和帧类型使用的标志的描述,包括基于标志值有条件地呈现的帧的任何部分。
下表中的帧类型由这个文档注册。
Frame Type | Code | Section |
---|---|---|
DATA | 0x0 | Section 6.1 |
HEADERS | 0x1 | Section 6.2 |
PRIORITY | 0x2 | Section 6.3 |
RST_STREAM | 0x3 | Section 6.4 |
SETTINGS | 0x4 | Section 6.5 |
PUSH_PROMISE | 0x5 | Section 6.6 |
PING | 0x6 | Section 6.7 |
GOAWAY | 0x7 | Section 6.8 |
WINDOW_UPDATE | 0x8 | Section 6.9 |
CONTINUATION | 0x9 | Section 6.10 |
11.3 设置注册表
这个文档为HTTP/2设置建立一个注册表。“HTTP/2设置”注册表管理一个16位空间。对于范围从0x0000到0xefff的值,“HTTP/2 Settings”注册表在“Expert Review”策略[RFC5226]下运行,其中0xf000和0xffff之间的值预留给实验使用。
新注册类型应提供以下信息:
名称: 设置的符号名。指定设置名称是可选的。
代码: 分配给该设置的16位代码。
初始值: 设置的初始值。
说明: 对描述该设置使用的规范的可选引用。
下表中的设置项由这个文档注册。
Name | Code | Initial Value | Specification |
---|---|---|---|
HEADER_TABLE_SIZE | 0x1 | 4096 | Section 6.5.2 |
ENABLE_PUSH | 0x2 | 1 | Section 6.5.2 |
MAX_CONCURRENT_STREAMS | 0x3 | (infinite) | Section 6.5.2 |
INITIAL_WINDOW_SIZE | 0x4 | 65535 | Section 6.5.2 |
MAX_FRAME_SIZE | 0x5 | 16384 | Section 6.5.2 |
MAX_HEADER_LIST_SIZE | 0x6 | (infinite) | Section 6.5.2 |
11.4 错误代码注册
这个文档为HTTP/2错误代码建立一个注册表。“HTTP/2错误代码”注册表管理一个32位的空间。“HTTP/2错误代码”注册表在“专家审查”政策下运行[RFC5226]。
错误代码的注册必须包括错误代码的描述。建议专家审查新注册是否与现有错误代码存在重复。鼓励使用现有的注册,但不是强制的。
新注册类型应提供以下信息:
名称:错误代码的名称。指定错误代码名称是可选的。
代码:32位错误码值。
描述:错误代码语义的简要描述,如果没有提供详细的规范,则更详细。
说明:定义错误代码的规范的可选引用。
下表中的条目由这个文档注册。
Name | Code | Description | Specification |
---|---|---|---|
NO_ERROR | 0x0 | Graceful shutdown | Section 7 |
PROTOCOL_ERROR | 0x1 | Protocol error detected | Section 7 |
INTERNAL_ERROR | 0x2 | Implementation fault | Section 7 |
FLOW_CONTROL_ERROR | 0x3 | Flow-control limits exceeded | Section 7 |
SETTINGS_TIMEOUT | 0x4 | Settings not acknowledged | Section 7 |
STREAM_CLOSED | 0x5 | Frame received for closed stream | Section 7 |
FRAME_SIZE_ERROR | 0x6 | Frame size incorrect | Section 7 |
REFUSED_STREAM | 0x7 | Stream not processed | Section 7 |
CANCEL | 0x8 | Stream cancelled | Section 7 |
COMPRESSION_ERROR | 0x9 | Compression state not updated | Section 7 |
CONNECT_ERROR | 0xa | TCP connection error for CONNECT method | Section 7 |
ENHANCE_YOUR_CALM | 0xb | Processing capacity exceeded | Section 7 |
INADEQUATE_SECURITY | 0xc | Negotiated TLS parameters not acceptable | Section 7 |
HTTP_1_1_REQUIRED | 0xd | Use HTTP/1.1 for the request | Section 7 |
11.5 HTTP2-Settings头字段注册
此部分在“永久消息头字段名称”注册表[BCP90]中注册HTTP2-Settings头字段。
头字段名称: HTTP2-Settings 适用的协议: http 状态: 标准 作者/改变控制器: IETF 规范文档(s): 第3.2.1节 相关信息: 此头字段仅供HTTP/2客户端用于基于升级的协商。
11.6 PRI 方法注册
本节在“HTTP方法注册表”([RFC7231],章节8.1)中注册PRI方法。
方法名称: PRI 安全: 是 幂等: 是 规范文档(s): 第3.5节 相关信息: 实际客户端从不使用此方法。当HTTP/1.1服务器或中介试图解析HTTP/2连接序言时,将使用此方法。
11.7 421(重导错误请求)HTTP状态码
此文档在“HTTP状态码”注册表([RFC7231],章节8.2)中注册了421(定向请求)HTTP状态码。
状态码: 421 简短描述:重导错误请求 规格:第9.1.2节
11.8 h2c升级令牌
本文档在“HTTP升级令牌”注册表中注册“h2c”升级令牌([RFC7230],第8.6节)。
值: h2c 描述: 超文本传输协议版本2 (HTTP/2) 预期的版本标记: 没有 参考: 第3.2节