---
HTTP/2.0
---

#### 目录

1. 思维导图
2. 前言
3. 设计和技术目标
   * 二进制分帧层
   * 流、消息与帧
   * 多项请求与响应
   * 请求优先级
   * 服务器推送
   * 首部压缩
4. 二进制分帧层简介

#### 前言

HTTP/2.0 的目的就是通过支持请求与响应的多路复用来减少延迟，通过压缩 HTTP 首部字段将协议开销降至最低，同时增加对请求优先级和服务器推送的支持。为达到这些目标，HTTP/2.0 还会给我们带来大量其他协议层面的辅助实现，比如新的流量控制、错误处理和更新机制。

HTTP/2.0 不会改动 HTTP 的语义，HTTP 方法、状态码、URI 及首部字段，等等这些核心概念一如往常。但是，HTTP/2.0 修改了格式化数据（分帧）的方式，以及客户端与服务器间传输这些数据的方式。这两点统帅全局，通过新的组帧机制向我们的应用隐藏了所有复杂性。换句话说，所有原来的应用都可以不必修改而在新协议运行。

#### 设计和技术目标

HTTP/1.x 的设计初衷主要是实现要简单：HTTP/0.9 只用一行协议就启动了万维网；HTTP/1.0 则是对流行的 0.9 扩展的一个正式说明；HTTP/1.1 则是 IETF 的一份官方标准。因此，HTTP 0.9~1.x 只描述了现实是怎么一回事：HTTP 是应用最广泛、采用最多的一个互联网应用协议。

然而，实现简单是以牺牲应用性能为代价的，而这正是 HTTP/2.0 要致力于解决的：HTTP/2.0 通过支持首部字段压缩和在同一个连接上发送多个并发消息，让应用更有效地利用网络资源，减少感知的延迟时间。而且，它还支持服务器到客户端的主动推送机制。

##### 二进制分帧层

HTTP/2.0 性能增强的核心，全在于新增的二进制分帧层，它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。

![](https://i.loli.net/2019/06/05/5cf730c6bc34987526.png)

这里所谓的层，指的是位于套接字接口与应用可见的高层 HTTP API 之间的一个新机制：HTTP 的语义，包括各种动词、方法、首部，都不受影响，不同的是传输期间对它们的编码方式变了。HTTP/1.x 以换行符作为纯文本的分隔符，而 HTTP/2.0 将所有传输的信息分割为更小的消息和帧，并对它们采用二进制格式的编码。

##### 流、消息和帧

新的二进制分帧机制改变了客户端与服务器之间交互数据的格式，为了说明这个过程，我们需要了解 HTTP/2.0 两个新的概念：

* 流

  已建立的连接上的双向字节流。

* 消息

  与逻辑消息对应的完整的一系列数据帧。

* 帧

  HTTP/2.0 通信的最小单位，每个帧包含帧首部，至少也会标识出当前帧所属的流。

![](https://i.loli.net/2019/06/05/5cf7343f8610140755.png)

所有 HTTP/2.0 通信都在一个连接上完成，这个连接可以承载任意数量的双向数据流。相应地，每个数据流以消息的形式发送，而消息由一个或多个帧组成，这些帧可以乱序发送，然后再根据每个帧首部的流标识符重新组装。

这简简单单的几句话里浓缩了大量的信息，要理解 HTTP/2.0，就必须理解流、消息和帧这几个基础概念：

1. 所有通信都在一个 TCP 连接上完成
2. 流是连接中的一个虚拟信道，可以承载双向的消息；每个流都有一个唯一的整数标识符（1、2 ~N）
3. 消息是指逻辑上的 HTTP 消息，比如请求、响应等，由一或多个帧组成
4. 帧是最小的通信单位，承载着特定类型的数据，如 HTTP 首部、负荷，等等

简言之，HTTP/2.0 把 HTTP 协议通信的基本单位缩小为一个一个的帧，这些帧对应着逻辑流中的消息。相应的，很多流可以并行的在同一个 TCP 连接上交换消息。

##### 多向请求与响应

在 HTTP/1.x 中，如果客户端想发送多个并行的请求以及改进性能，那么必须使用多个 TCP 连接。这是 HTTP/1.x 交互模型的直接结果，该模型会保证每个连接每次只交互一个响应（多个响应必须排队）。更糟糕的是，这种模型也会导致队首阻塞，从而造成底层 TCP 连接的效率低下。

HTTP/2.0 中新的二进制分帧层突破了这些限制，实现了多向请求和响应：客户端和服务器可以把 HTTP 消息分解为互不依赖的帧，然后乱序发送，最后再在另一端把它们重新组合起来。

![](https://i.loli.net/2019/06/05/5cf742f19abea24700.png)

上图中包含了同一个连接上多个传输中的数据流：客户端正在向服务器传输一个 DATA 帧（stream 5），与此同时，服务器正向客户端乱序发送 stream 1 和 stream 3 的一系列帧。此时，一个连接上有 3 个请求/响应并行交换！

把 HTTP 消息分解为独立的帧，交错发送，然后在另一端重新组装是 HTTP/2.0 最重要的一项增强。事实上，这个机制会在整个 Web 技术栈中引发一系列连锁反应，从而带来巨大的性能提升，因为：

* 可以并行交错的发送请求，请求之间互不影响
* 可以并行交错的发送响应，响应之间互不干扰
* 只使用一个连接即可并行发送多个请求和响应
* 消除不必要的延迟，从而减少页面加载的时间
* 不必再为绕过 HTTP/1.x 限制而多做很多工作

总之，HTTP/2.0 的二进制分帧机制解决了 HTTP/1.x 中存在的队首阻塞问题，也消除了并行处理和发送请求及响应时对多个连接的依赖。结果，就是应用速度更快，开发更简单，部署成本更低。

> 支持多向请求与响应，可以省掉针对 HTTP/1.x 限制所费的那些脑筋和工作，比如拼接文件、图片精灵、域名分区。类似的，通过减少 TCP 连接的数量，HTTP/2.0 也会减少客户端和服务器的 CPU 及内存占用。

##### 请求优先级

把 HTTP 消息分解为很多独立的帧之后，就可以通过优化这些帧的交错和传输顺序，进一步提升性能。为了做到这一点，每个流都可以带一个 31 比特的优先值：

* 0 表示最高优先级
* 2^31 - 1 表示最低优先级

有了这个优先值，客户端和服务器就可以在处理不同的流时采取不同的策略，以最优的方式发送流、消息和帧。具体来讲，服务器可以根据流的优先级，控制资源分配（CPU、内存、带宽），而在响应数据准备好之后，优先将最高优先级的帧发送给客户端。

##### 服务器推送

HTTP/2.0 新增的一个强大的新功能，就是服务器可以对一个客户端请求发送多个响应。换句话说，除了对最初请求的响应外，服务器还可以额外向客户端推送资源，而无需客户端明确的请求。

![](https://i.loli.net/2019/06/05/5cf7598f14b9933893.png)

建立 HTTP/2.0 连接后，客户端与服务器交换 SETTINGS 帧，借此可以限定双向并发的流的最大数量。因此，客户端可以限定推送流的数量，或者通过把这个值设置为 0 而完全禁用服务器推送。

为什么需要这样一个机制呢？通常的 Web 应用都由几十个资源组成，客户端需要分析服务器提供的文档才能逐个找到它们。那为什么不让服务器提前就把这些资源推送给客户端，从而减少额外的时间延迟呢？服务器已经知道客户端下一步要请求什么资源了，这时候服务器推送即可派上用场。上篇文章所说的 “嵌入资源” 也可以算作服务器推送。把资源直接插入到文档中，就是把资源直接推送给客户端，而无需客户端请求。在 HTTP/2.0 中，唯一的不同就是把这个过程从应用中拿出来，放到 HTTP 协议本身来实现，而且还带来了如下好处：

* 客户端可以缓存推送过来的资源
* 客户端可以拒绝推送过来的资源
* 推送资源可以由不同的页面共享
* 服务器可以按照优先级推送资源

最后还有一点，就是推送的资源将直接进入客户端缓存，就像客户端请求了似的。不存在客户端 API 或 JavaScript 回调方法等通知机制，可以用于确定资源何时到达。整个过程对运行在浏览器中的 Web 应用来说好像根本不存在。

##### 首部压缩

HTTP 的每一次通信都会携带一组首部，用于描述传输的资源及其属性。在 HTTP/1.x 中，这些元数据都是以纯文本形式发送的，通常会给每个请求增加 500~800 字节的负荷。如果算上 HTTP Cookie，增加的负荷通常会达到上千字节。为减少这些开销并提升性能，HTTP/2.0 会压缩首部元数据：

* HTTP/2.0 在客户端和服务器端使用 “首部表” 来跟踪和存储之前发送的键值对，对于相同的数据，不在通过每次请求和响应发送。
* 首部表在 HTTP/2.0 的连接存续期内始终存在，由客户端和服务器共同渐进的更新。
* 每个新的首部键值对要么被追加到当前表的末尾，要么替换表中之前的值

于是，HTTP/2.0 连接的两端都知道已经发送了哪些首部，这些首部的值是什么，从而可以针对之前的数据只编码发送差异数据。

![](https://i.loli.net/2019/06/05/5cf78307e362058386.png)

请求与响应首部的定义在 HTTP/2.0 中基本没有改变，只是所有首部键必须全部小写，而且请求行要独立为 :method、:scheme、:host 和 :path 等这些键值对。

在前面的例子中，第二次请求只需要发送变化了的路径首部（:path），其他首部没有变化，不用再发送了。这样就可以避免传输冗余的首部，从而显著减少每个请求的开销。

#### 二进制分帧层简介

HTTP/2.0 的根本改进还是新增的长度前置的二进制分帧层。与 HTTP/1.x 使用换行符分隔纯文本不同，二进制分帧层更加简洁，通过代码处理起来更简单也更有效。

建立了 HTTP/2.0 连接后，客户端与服务器会通过交换帧来通信，帧是基于这个新协议通信的最小单位。所有帧都共享一个 8 字节的首部，如下图所示，其中包含帧的长度、类型、标志，还有一个保留位和一个 31 位的流标识符。

![](https://i.loli.net/2019/06/05/5cf79db949a0866805.png)

* 16 位的长度前缀意味着一帧大约可以携带 64 KB 数据，不包括 8 字节首部
* 8 位的类型字段决定如何解释帧其余部分的内容
* 8 位的标志字段允许不同的帧类型定义特定于帧的消息标志
* 1 位的保留字段始终置为 0
* 31 位的流标识符唯一标识 HTTP/2.0 的流

知道了帧类型，解析器就知道该如何解析帧的其余内容了，HTTP/2.0 规定了如下帧类型：

* DATA：用于传输 HTTP 消息体
* HEADERS：用于传输关于流的额外的首部字段
* PRIORITY：用于指定或重新指定引用资源的优先级
* RST_STREAM：用于通知流的非正常终止
* SETTINGS：用于通知两端通信方式的配置数据
* PUSH_PROMISE：用于发出创建流和服务器引用资源的要约
* PING：用于计算往返时间，执行 “活性” 检查
* GOAWAY：用于通知对端停止在当前连接中创建流
* WINDOW_UPDATE：用于针对个别流或个别连接实现流量控制
* CONTINUATION：用于继续一系列首部块片段

服务器可以利用 GOAWAY 类型的帧告诉客户端要处理的最后一个流的 ID，从而消除一些请求竞争，而且浏览器也可以据此智能的重试或取消 “悬着的” 请求。这也是保证复用连接安全的一个重要和必要的功能。

