HTTP 协议自诞生以来经历了多次修订和更改, 截止目前, 最新的已成为互联网标准 (基于 QUIC 的 HTTP/3 [Draft-IETF-QUIC-HTTP-34] 仍在草案阶段, 我将在以后的博客中详细讨论) 的 HTTP 协议为 2015 年发布的 HTTP version 2 [RFC 7540], 一般简写为 HTTP/2, HTTP/2 相比于 HTTP/1.0 和 HTTP/1.1 有了革命性的变更, 从它的大版本号由 1 变为 2 也可以看出这是一次巨大的升级, HTTP/2 主要解决 HTTP/1.x 协议交互效率低下的问题, HTTP 使用 TCP 作为传输层协议, 在 HTTP/1.0 [RFC 1945] 中, 每当建立一个 HTTP 请求都要重新建立 TCP 连接, 当请求完成后便立即释放 TCP 链接, 而一次 TCP 链接的建立需要客户端和服务器进行三次握手, 当 TCP 连接释放时则需要四次挥手, 因此 HTTP/1.0 的效率非常低下, 为了解决这个问题, 某些 Web Server 增加了对 Connection: keep-alive 的支持, 当 HTTP Header 中含有这个字段时, 当 HTTP 请求结束后服务端不会立刻释放 TCP 连接, 因此当客户端在一定时间内继续对服务端发起 HTTP 请求便可以继续使用上次未释放的 TCP 连接, 在 HTTP/1.1 [RFC 2616] 中, Connection: keep-alive 被 IETF 正式标准化, 并默认开启 keep-alive, 当不需要 TCP 连接维持时需要显式的在 Header 中设置 Connection: close
在 HTTP/1.0 中, HTTP 请求都是完全阻塞的, 即客户端只有在上一次 HTTP 请求完成以后才可以继续发送下一次 HTTP 请求, HTTP/1.1 对此作了改进, 允许 pipelining 方式的调用, 即客户端可以在没有收到 Response 的情况下连续发送多次 HTTP Request, 但服务端依旧是顺序对请求进行处理, 并按照收到请求的次序予以返回, 也就说在 HTTP/1.1 中 HTTP 请求的处理仍然是线性的, 尽管 TCP 长连接和流水线方式相对于 HTTP/1.0 有了一定的性能提升, 但 HTTP/1.1 仍然存在很多问题, 比如流水线方式的调用可能出现 Head-of-line blocking (通常简写为 HOL Blocking) 问题, 即由于请求的处理是顺序的, 因此即便 Client 连续发送了多个 HTTP Request, 若其中某一次请求非常耗时, 则在其后的 Request 都处于排队阻塞的状态, 所以在 HTTP/1.0 和 HTTP/1.1 中, 要想真正有效地提高交互效率, 降低请求耗时, 则都需要建立多个 TCP 连接并行地与服务端交互, 而 HTTP/2 在不改变现有 HTTP 语义的前提下, 对 HTTP 协议做了革命性的变更, 在一个 TCP 连接的基础上虚拟出多个 Stream, (没有偏序关系的) Stream 之间可以并发的请求和处理, 并且 HTTP/2 以二进制帧 (frame) 的方式进行数据传送, 并引入了头部压缩 (HPACK), 大大提升了交互效率, 此外 HTTP/2 还支持服务端主动推送等特性, HTTP/2 的 RFC 文档有两份, 分别是 RFC 7540 和 RFC 7541, 后者主要讨论 HTTP/2 的头部压缩 (HPACK) 问题, 本文主要讨论和分析 RFC 7540, 关于 HTTP/2 的头部压缩将在后续文章中详细展开讨论
11.1 HTTP 协议协商
由于 HTTP 协议存在多个版本, 如 HTTP/1.0 [RFC 1945], HTTP/1.1 [RFC 2616] 还有本文讨论的 HTTP/2, 不同版本协议之间的交互方式存在差异, 当客户端和服务端通信时, 首先需要确定或称协商出所使用的 HTTP 协议版本, 对于存在多版本的协议的通信双方在握手时几乎都需要有协商 (Negotiation) 环节, 如 MySQL 的客户端和服务端在握手时首先也需要协商, 以便明确对方的协议版本, 所支持的功能特性等, 单纯的 HTTP 协议和 HTTP over TLS (即 https) 协议对于 HTTP/2 的协商方式是不同的, 我们以 h2 表示 HTTP over TLS, 以 h2c (c 是 clear 的首字母, 代表 clear text, 与 https 的加密报文相区分) 表示单纯的 HTTP 协议, 由于 TLS 的拓展字段支持 ALPN (Application-Layer Protocol Negotiation, 应用层协议协商), 即在进行 TLS 握手的同时本身可以通过 ALPN 知晓对方使用的应用层协议是什么, 所以二者的协商方式不同, 我们首先讨论 HTTP 的协商方式
与 WebSocket 协议 [RFC 6455] 类似, HTTP/2 使用 Upgrade 的头部字段来探测对方是否支持 HTTP/2 协议, 在没有任何先验知识的情况下, 客户端若想要和服务端以 HTTP/2 协议进行通信, 那么客户端可以向服务端发送如下形式的 Request:
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
即在 Header 中包含 Upgrade 字段, 并且将这个字段的值设置为 h2c, 同时增加一个 HTTP2-Settings 字段 (将在下面讨论), 若 Server 端不支持 HTTP/2, 则在 Response 的 Header 里将不包含 Upgrade 字段, 否则, Server 应在 Response Header 中增加 Upgrade: h2c, 支持 HTTP/2 的 Server 返回形式大致如下:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
Server 端返回的 HTTP Status Code 为 101, 代表 Server 接受了 Client 的升级请求, 接下来, 客户端和服务端就可以使用 HTTP/2 进行交互了。RFC 7540 要求进行 HTTP/2 协商的客户端在 Header 中必须包含且仅包含一个 HTTP2-Settings 字段, 这个字段的值是 base64 编码的 HTTP/2 SETTINGS frame (将在下面具体讨论), 用于客户端向服务端传递一些配置参数, 若客户端在协商阶段发送的 Request 的 Header 中没有包含这个字段或多于一个该字段, 则服务端不能 (MUST NOT) 升级为 HTTP/2 协议
而对于 HTTP over TLS (https) 来说, 由于双方在 TLS 握手阶段, 通过 ALPN 拓展字段已经协商好了双方使用的应用层协议, 因此当 TLS 握手完成后便可以进行 HTTP/2 的通信交互了
在 HTTP/2 协议中, 客户端和服务端都需要发送 Connection Preface 以便最终确认双方使用 HTTP/2 协议进行交互, 并且在 Connection Preface 中可以对协议参数做一些初始化的工作, 对于客户端来说, 当收到服务端 101 状态码的 Response (通过 HTTP Upgrade 进行协议协商) 或 TLS 握手成功 (通过 TLS ALPN 进行协议协商) 后, 便立即开始发送 Connection Preface, Connection Preface 的开头是一个固定的字节序列(可以认为这是一个魔数, 一般在设计网络协议时都会设置一个魔数以过滤掉不支持的数据), 这个值用字符串表示为 "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 在此序列后跟随发送一个可选的 SETTINGS frame, 其中可以设置一些协议参数(将在下面讨论), 服务端的 Connection Preface 不需要魔数, 但同样需要包含一个可选的 SETTINGS frame 用于设置服务端的协议参数, 无论是客户端还是服务端, 当收到不合法的 Connection Preface 都需要报告连接错误
11.2 HTTP/2 frame
在 HTTP/2 中, frame 是客户端和服务端数据传输的最小单元, 当 HTTP/2 Connection Preface 都发送校验完毕之后, 双方就可以正式开始以 frame 的形式进行数据交换, frame 由 Header 和 Payload 两部分构成, 其中 Header (注意区分 frame 的 Header 和 HTTP 协议本身的 Header) 的长度固定为 9 字节, Payload 的长度是可变的, frame 的结构如下所示:
-
Length 字段长度为 3 字节, 以字节为单位指示 frame 的 Payload 的长度(即该字段指示的长度不包含 9 字节的 frame header)
-
Type 字段长度为 1 字节, 指示 frame 的类型(将在下面讨论)
-
Flags 字段长度为 1 字节, Flags 字段与 frame 的类型有关, 以 bit 位来表征特定类型 frame 的特定设置(将在下面讨论)
-
R 字段长度为 1 比特, 它是 Reserve 的首字母, 即该字段是保留字段, 目前必须设置为 0
-
Stream Identifier 是 31 位的无符号整数, 它的值代表流编号, 当该字段非 0 时, 表示当前帧属于某个特定的 Stream , 当其为 0 时, 代表该帧是属于整个 TCP 连接的
因为 Length 字段的长度为 3 字节, 所以在 HTTP/2 中, 一个 frame 的最大长度为 字节的 Payload + 9 字节的 header, 在实际交互中, 客户端和服务端任何一方都可以通过 SETTINGS frame 来设置自己所接受的 frame payload 的最大长度, 这个长度的范围可以取 到 (以字节为单位) 的区间内任意一个值, 当设置了该最大值时, 若在以后的通信中接收到的 frame 的 payload 超过之前的设定, 则接收方应发送 FRAME_SIZE_ERROR 错误, 尽管在 HTTP/2 中, frame payload 最大可以设置为 个字节的大小, 但对于时延敏感的 frame (如 RST_STREAM, 类似于 TCP 的 rst, 用于复位连接) 当 frame 数据过大时传输效率低下, 将会影响整体的性能
11.3 HTTP/2 Stream
Stream 是 HTTP/2 协议的核心, 因为在 HTTP/1.x 中, 所有的请求都是在单个 TCP 连接上顺序发送的, HTTP/2 引入了 Stream 的概念, Stream 实际上是一个逻辑概念, 是虚拟的, 并非真实存在的对象, 在 frame 的结构中我们看到, frame header 中有 Stream Identifier 字段, 用于指示该 frame 所属的 Stream 序号, 当一个 Stream Identifier 为 N 的 frame 在 TCP 链路上传输时, 我们就可以认为它是在 Stream N 上传输. Stream 需要由一方主动创建, RFC 7540 要求由客户端初始化的 Stream, 其编号 (即 Identifier) 必须是奇数, 而由服务端初始化的 Stream, 其编号必须是偶数, 特别地, 编号为 0 的 Stream 是用来传输整个 (TCP) 连接的控制消息的, 一个 TCP 连接上可以同时存在多个 Stream, 这些 Stream 可以并发地传输数据, 因此实际上, HTTP/2 Stream 是对 TCP 连接的多路复用 (Multiplexing)
在 HTTP/2 中, 每一个新创建的 Stream 的编号必须比已有的所有的 Stream 的编号都大, 当使用新编号的 Stream 时, 所有低于该编号的并且处于空闲 (Idle) 状态的 Stream 都会被隐式的关闭, 在一个 TCP 链接中, 流编号不能重复使用, 即新创建的 Stream 编号不能是之前用过的编号(即便是之前用过的编号并且已关闭也不允许再使用), 在 frame 中, 由于流编号只有 31 位, 因此对于一个 TCP 长连接来说, 存在流编号被用光的情形, 当流编号用尽时, 如果需要再创建一个新的 Stream, 对于客户端来说, 可以创建一个新的 TCP 连接, 对于服务端来说, 可以向客户端发送一个 GOAWAY frame, 强制客户端打开新的一个 TCP 连接
客户端和服务端都可以通过发送 SETTINGS frame 来设置自己所接受的最大 Stream 数量的上限, 该变量为 SETTINGS_MAX_CONCURRENT_STREAMS, 该项设置是由发送方发送的, 希望接收方遵守的上限, 也就是说该项设置是单向的, 只对接收方有效, 例如服务端向客户端发送一个包含 SETTINGS_MAX_CONCURRENT_STREAMS 设置的 SETTINGS frame, 并将该变量的值设置为 50, 这代表在该 TCP 连接上, 至多包含有 50 个处于未关闭状态的, 由客户端创建的 Stream, 而服务端自身不受该规则的限制, 除非客户端也向服务端发送 包含 SETTINGS_MAX_CONCURRENT_STREAMS 设置的 SETTINGS frame, SETTINGS_MAX_CONCURRENT_STREAMS 设置只计算处于 open 或 half-closed 状态的 Stream, 对于处于 reserved 状态的 Stream 则不计算在内, 在交互过程中, 任何一方都不应创建超过对方设置的最大上限数量的 Stream, 当超过数量上限后, 对方可返回 PROTOCOL_ERROR 或者 REFUSED_STREAM
11.4 HTTP/2 流控制 (Flow Control)
HTTP/2 在单个 TCP 连接上虚拟出多个 Stream, 多个 Stream 实现对一个 TCP 连接的多路复用, 为了合理地利用传输链路, 实现在有限资源内达到传输性能的最优化, 必须对 Stream 做一定的控制, HTTP/2 本身只在逻辑层面规定了流控制的语义, 具体的实现算法由协议的实现者自行决定, 类似于定义了一组抽象接口, 具体的实现交由程序员去完成, HTTP/2 的流控制有如下几条原则:
-
流控制的 hop-to-hop 的, 而不是 end-to-end, 具体来说, 在一个 HTTP 请求链路上, 中间可能存在代理, 例如 客户端 → 代理 → 服务端 的请求链路, 在 客户端 → 代理 与 代理 → 服务端 都各自独立地有流控制 (这里的 hop-to-hop 是在应用层的, 不要与 IP 协议的 hop-to-hop 混淆)
-
HTTP/2 的流控制与 TCP 的流量控制有些类似, 但不完全相同, 双方发送 WINDOW_UPDATE frame 以字节为单位来指示自身所接受的窗口大小, 双方都必须遵守对方设置的窗口大小, RFC 7540 规定的初始化窗口大小为 65535 个字节
-
只有 DATA frame 受流控制的约束, 对于其它类型的 frame 不受该规则限制, 从而确保控制类的 frame 不会因流控约束而无法(及时)发送, 并且 HTTP/2 的流控制双方都必须严格遵守, 流控制在 HTTP/2 中不能被关闭 (disable), 当发送方不需要进行流控制时可以发送 WINDOW_UPDATE frame 将窗口的值设置为最大值 (), 但它仍然需要遵守对方设置的窗口限制
由于在一个 TCP 上存在多个 Stream, 而底层的传输层连接只有一个, 为了更好地利用有限的资源, HTTP/2 对流引入了优先级的概念, 引入优先级一方面向对方表达自身希望对方为该流分配资源的权重, 另一方面, 对自身来说, 当资源有限时, 流的优先级可以用于决策优先发送哪个流上的 frame, 可以通过标记一个流依赖于另一个流的完成来表征它的优先级, 并且为依赖关系分配一个相对的权重, 举例来说, 若流 A 依赖于流 B, 则称流 A 是流 B 的从属流 (dependent stream), 流 B 是流 A 的父级流 (parent stream), 一个流可以被任意个其它流所依赖, 例如流 B, C 可以同时依赖于流 A, 它们都是流 A 的从属流, 可以用如下所示的图示来表示:
A A
/ \ ==> /|\
B C B D C
可以在创建流的时候通过 HEADERS frame (将在下面讨论) 指示该流所依赖的流, 当流创建完成以后也可以通过 PRIORITY frame 来改变流的优先级, 在设置流的依赖关系时, 可以在 frame header 中设置 exclusive flag 来指示该流的排他性, 在上面的例子中, 我们看到流 B D C 同时依赖于 A, 若设置 exclusive flag 我们可以继续创建流 E 使其与流 B D C 一样都在同一级依赖于流 A, 而若设置了 exclusive flag, 那么流的层级依赖关系将如下所示:
A
A |
/ \ ==> D
B C / \
B C
在这里例子中, 原先 B C 都依赖于流 A, 而创建流 D 时, 在 frame 中设置了 exclusive flag, 这样以来只有流 D 直接依赖于流 A, 而原先的流 B C 的父级流都将更改为流 D
依赖的权重 (Weight) 用于决定流所能分配的资源(这个资源可能是多维度的, 如为该流分配的内存等), 在 HTTP/2 中, 流的权重是一个 1~256 的整数, 权重越大, 分配到的资源便越多, 举例来说, 假设流 B 和流 C 同时依赖于流 A, 流 B 的依赖权重为 4, 流 C 的依赖权重为 12, 当流 A 的操作都完成以后或流 A 处于阻塞状态暂时无法继续进行更多的操作, 在理想情况下, 流 B 分配到的资源应是流 C 分配到的资源总量的
当然, 流的权重和优先级在 HTTP/2 中只是建议, 通信双方应该 (SHOULD) 尽可能遵守这些规则, 但并不强制, 通信的任何一方都不能强制要求对方必须按照流的优先级对流进行处理或严格按照权重比例进行资源分配, 任何流都有依赖的流, 没有显示指明依赖流的流都依赖于编号为 0x0 的流, 在所有情形下, 流的默认权重都为 16, 更多关于流管理的细节可以参看 RFC 7540 Section 5.3
11.5 HTTP/2 错误处理
在前面的讨论中, 我们可以看到, HTTP/2 将一个 TCP 连接虚拟出多个 Stream, 这些 Steam 对 TCP 连接实现了多路复用, 因此在讨论 HTTP/2 时, 讨论的对象实际有两个维度, 一个是 TCP 连接, 我们称之为 Connection, 一个是在 TCP 连接上的单个流,我们称之为 Stream, HTTP/2 的错误处理也因此分为两个维度, 一个是 Connection Error, 这代表连接上的错误, 当发生了 Connection Error 后, 意味通信双方不能继续使用当前的 TCP 连接, 这是一种严重程度比较高的错误, 而与此相对的 Stream Error 则是发生在单个 Stream 内的错误, 由于 Stream 之间是相互独立的, 因此对于 Stream Error 不需要终止整个 TCP 连接, 一个 Stream 内的错误不会影响其它 Stream
当发生 Connection Error 后, 通信方应发送一个 GOAWAY frame 给另一方并立即终止 TCP 连接, 在 GOAWAY 内包含对应的错误码, 错误码可以指示错误类型, 以便于另一方了解为什么需要终止 TCP 连接, 由于发送 GOAWAY frame 之后便关闭 TCP 连接, 这可能会造成接收方没有接收到 GOAWAY frame (尽管 TCP 是可靠传输, 但发送之后, 发送方便主动关闭 TCP 连接, 不再等待接收方对该 TCP 报文段的确认信息, 当然也不再对该报文段做超时重传), RFC 7540 要求发送方尽最大努力 (best-effort) 发送 GOAWAY frame
Stream Error 是特定于具体的 Stream 的, 当发生 Stream Error 后, 通信方应向对方发送包含流编号的 RST_STREAM frame, 同时, RST_STREAM frame 的 Payload 中也包含 Error Code, 以便于对方了解发生了什么错误, Stream Error frame 是在该 Stream 上所能发送的最后一个 frame, 发送完毕以后, 该 Stream 的状态便转换为 closed 状态, 当然由于 Stream 是全双工的, 因此可能发生的情形是发送方已经发送了 RST_STREAM frame, 但实际上在发送方发送该 frame 之前, 通信的另一方已经发出或正在排队等待发送其它 frame, 通信方在发送出 RST_STREAM frame 后如果收到了对方在该 Stream 上发来的 frame 时, 通常应直接忽略, 但如果是控制类的 frame, 如用于 HTTP/2 流控制的 frame, 则应正常地接收、处理, 通常情况下, 错误的发现方应只发送一次 RST_STREAM frame, 但如果该方在发送玩 RST_STREAM frame 之后, 经过一个 RTT 后又收到了对方发来的 frame, 则该方应继续发送一个额外的 RST_STREAM frame, 以便应对没有正确实现 HTTP/2 协议的另一方, 为了避免陷入循环, RFC 7540 规定对 RST_STREAM frame 不设置确认机制, 即接收方收到 RST_STREAM frame 后不需要向对方发送确认
11.6 HTTP/2 DATA frame
在 11.2 中, 我们讨论了 HTTP/2 frame 的 Header 结构, 其中 type 字段长度为 8 位, 由它来指示 frame 的类型, 在 HTTP/2 中, frame 的种类有很多, 当该字段为 0x0 时, 代表这是一个 DATA frame, DATA frame 用来传输 HTTP 请求和响应的 Payload, DATA frame 的结构如下所示:
DATA frame 可以选择性的传输 padding, padding 用于隐藏实际的 payload 长度, 当需要使用 padding 时, 需要在 frame header 中设置标志, padding 的标志值为 0x8, 在设置标识时可以将所有标识位按位或, 写到标识位对应的 offset 上, 它的标志值为 0x8 代表需要将标识字段的第三位二进制位设置成 1, 当设置了 padding 标识后, Pad length 字段指示 padding 的长度, 而 Padding 字段便是相应长度的数据, 这里的数据是没有任何语义的, 需要都设置为 0, 接收方若收到设置了 padding 标识的 DATA frame, 并且它的 padding 字段非 0 可以返回 Connection Error, 按 RFC 7540 的表述, 这里的错误处理是 MAY, 也就是说 HTTP/2 标准本身并不强制, 由协议的实现者自行决定是否要作为错误来处理
除了 padding 标志外, DATA frame 本身还有 END STREAM 标志, 它的标志值为 0x1, END STREAM 标志代表这是发送方在该 Stream(frame header 的流编号字段指示的 stream) 上发送的最后一个 frame, 当发送设置了 END STREAM 标志的 DATA frame 后, 该 Stream 便进入了 half-closed 或者 closed 状态
DATA frame 必须关联一个具体的 Stream (即 DATA frame header 的 Stream Identifier 字段必须是非 0 的), 因为 DATA frame 总是在 Stream 上进行传输的, 接收方若收到 DATA frame 并且其 Stream Identifier 字段为 0 时, 应立即报告 Connection Error, 另外, DATA frame 是唯一受流控制 (flow control) 的 frame 类型, 它所发送的 Payload 的长度(当然包括 Padding 字段)都计算在流控窗口内
DATA frame 只能在状态为 open 或 half-closed (remote) 状态的 Stream 上发送, 当接收方收到不属于这两种状态的 Stream 的 DATA frame 时, 应立即报告 STREAM_CLOSED 的 Stream Error
另外, Pad length 字段指示 Padding 的长度, 若 Pad length 指示的长度与实际的 Padding 长度不匹配, 则接收方应立即报告 Connection Error
11.7 HTTP/2 HEADERS frame
HEADERS frame 用来初始化一个新的 Stream 或传输 HTTP/2 Header Block (将在下面讨论), Header frame 的 frame type 为 0x1, 它的 Payload 结构如下所示:
与 DATA frame 相同, HEADER frame 也包含 Pad length 和 Padding 字段用于隐藏真实的 Payload 长度, 其它字段的语义如下:
-
E, 长度为 1 比特, 作为一个放在 Payload 中的标志位, 用来指示是否开启 exclusive flag (已在上面讨论过), 当且仅当在 frame 的 header 中设置了 PRIORITY flag 时, 该字段有效
-
Stream Dependency, 长度为 31 比特, 用来指示该流所依赖的流 (已在上面讨论过), 当且仅当在 frame 的 header 中设置了 PRIORITY flag 时, 该字段有效
-
Weight, 长度为 1 字节, 用于设置依赖的权重 (已在上面讨论过), 值的有效范围为 1 ~ 256, 当且仅当在 frame 的 header 中设置了 PRIORITY flag 时, 该字段有效
-
Header Block Fragment, 长度是可变的, 用于传输 header block fragment (将在下面讨论)
HEADERS frame 总共定义了 4 个标志位, 分别是
-
END_STREAM, 在 DATA frame 中已讨论过了, 但有所不同的是, 尽管 END_STREAM 通常是在关联 Stream 上发送方所发送的最后一个 frame, 但对于 HEADERS frame, 当它用来传输 header block fragment 时, 即便已经设置了 END_STREAM 标志, 但若没有设置 END_HEADERS (下面即将讨论) 时, 仍需要继续发送 CONTINUATION frame, 这发生在 header block 长度过大时, 一个 HEADERS frame 的 Payload 不足以承载整个 header block, 此时将发送一个 HEADERS frame 并跟随着若干个 CONTINUATION frame 来传输剩下的 header block 数据
-
END_HEADERS, 用于指示该 frame 包含了整个 header block 的数据, 此时, 其后不会跟随 CONTINUATION frame
-
PADDED, 与 DATA frame 相同, 不再赘述
-
PRIORITY, 用来做流优先级控制, 当设置了该标志位之后, E、Stream Dependency 和 Weight 字段才有效
HEADERS frame 和 DATA frame 传输了基本的 HTTP 请求和响应的头部和 Payload Body 信息, 关于其它类型的 frame 请读者阅读 RFC 7540 Section 6, 这里不再一一展开讨论
11.8 HTTP/2 Header 传输方式
这里对 HTTP/2 的 Header 传输方式做一个简短的叙述, 更详细的头部压缩细节将在以后的博客中根据 RFC 7541 详细讨论.
为了提高传输效率, HTTP/2 将 HTTP Header 序列化为二进制字节流, 并做相应的压缩 [RFC 7541], 然后通过 HEADERS frame, CONTINUATION frame 或 PUSH_PROMISE frame 进行传输, 一个 header block 可以是单个的 HEADERS frame (此时因为没有后续的 frame, 所有 Header 数据都在这一个 frame 中完整传输, 此时需要设置 END_HEADERS 标志), 或是一个 HEADERS frame 其后跟随若干个 CONTINUATION frame, 并且最后一个 CONTINUATION frame 必须设置 END_HEADERS 标志用于指示头部数据结束, 在进行头部传输的时候, Stream 之间不能并发传输数据, 所有的头部数据必须作为一个序列, 整体地传输完成, 而接收方在收到所有与携带头部信息的 frame 后, 将它们按序连接在一起, 并解压缩从而获得完整的 Header 信息