--- 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,从而消除一些请求竞争,而且浏览器也可以据此智能的重试或取消 “悬着的” 请求。这也是保证复用连接安全的一个重要和必要的功能。