HTTP/2 新特性浅析

HTTP/2 源自 SPDY/2

SPDY 系列协议由谷歌开发,于 2009 年公开。它的设计目标是下降 50% 的页面加载时间。当下不少著名的互联网公司,例如百度、淘宝、UPYUN 都在本身的网站或 APP 中采用了 SPDY 系列协议(当前最新版本是 SPDY/3.1),由于它对性能的提高是显而易见的。主流的浏览器(谷歌、火狐、Opera)也都早已经支持 SPDY,它已经成为了工业标准,HTTP Working-Group 最终决定以 SPDY/2 为基础,开发 HTTP/2。html

可是,HTTP/2 跟 SPDY 仍有不一样的地方,主要是如下两点:git

  1. HTTP/2 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
  2. HTTP/2 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DELEFT

HTTP/2 的优点

相比 HTTP/1.x,HTTP/2 在底层传输作了很大的改动和优化:github

  1. HTTP/2 采用二进制格式传输数据,而非 HTTP/1.x 的文本格式。二进制格式在协议的解析和优化扩展上带来更多的优点和可能。
  2. HTTP/2 对消息头采用 HPACK 进行压缩传输,可以节省消息头占用的网络的流量。而 HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了不少带宽资源。头压缩可以很好的解决该问题。
  3. 多路复用,直白的说就是全部的请求都是经过一个 TCP 链接并发完成。HTTP/1.x 虽然经过 pipeline 也能并发请求,可是多个请求之间的响应会被阻塞的,因此 pipeline 至今也没有被普及应用,而 HTTP/2 作到了真正的并发请求。同时,流还支持优先级和流量控制。
  4. Server Push:服务端可以更快的把资源推送给客户端。例如服务端能够主动把 JS 和 CSS 文件推送给客户端,而不须要客户端解析 HTML 再发送这些请求。当客户端须要的时候,它已经在客户端了。

HTTP/2 主要是 HTTP/1.x 在底层传输机制上的彻底重构,HTTP/2 是基本兼容 HTTP/1.x 的语义的(详细兼容性说明请戳 这里)。Content-Type 仍然是 Content-Type,只不过它再也不是文本传输了。那么 HTTP/2 的这些新特性又是如何实现的呢?算法

HTTP/2 的基石 - Frame

Frame 是 HTTP/2 二进制格式的基础,基本能够把它理解为它 TCP 里面的数据包同样。HTTP/2 之因此可以有如此多的新特性,正是由于底层数据格式的改变。 Frame 的基本格式以下(图中的数字表示所占位数,内容摘自 http2-draft-17):浏览器

+-----------------------------------------------+
|                 Length (24)                   |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------+
|R|                 Stream Identifier (31)          |
+=+=================================================+
|                   Frame Payload (0...)        ...
+---------------------------------------------------+
  • Length: 表示 Frame Payload 部分的长度,另外 Frame Header 的长度是固定的 9 字节(Length + Type + Flags + R + Stream Identifier = 72 bit)。
  • Type: 区分这个 Frame Payload 存储的数据是属于 HTTP Header 仍是 HTTP Body;另外 HTTP/2 新定义了一些其余的 Frame Type,例如,这个字段为 0 时,表示 DATA 类型(即 HTTP/1.x 里的 Body 部分数据)
  • Flags: 共 8 位, 每位都起标记做用。每种不一样的 Frame Type 都有不一样的 Frame Flags。例如发送最后一个 DATA 类型的 Frame 时,就会将 Flags 最后一位设置 1(flags &= 0x01),表示 END_STREAM,说明这个 Frame 是流的最后一个数据包。
  • R: 保留位。
  • Stream Identifier: 流 ID,当客户端和服务端创建 TCP 连接时,就会先发送一个 Stream ID = 0 的流,用来作些初始化工做。以后客户端和服务端从 1 开始发送请求/响应。

Frame 由 Frame Header 和 Frame Payload 两部分组成。不管是原来的 HTTP Header 仍是 HTTP Body,在 HTTP/2 中,都将这些数据存储到 Frame Payload,组成一个个 Frame,再发送响应/请求。经过 Frame Header 中的 Type 区分这个 Frame 的类型。因而可知语义并无太大变化,而是数据的格式变成二进制的 Frame。两者的转换和关系以下图:缓存

HTTP/1.x 数据转换为 HTTP/2
图片引用自这里服务器

为 HTTP/2 头压缩专门设计的 HPACK

若是咱们约定将经常使用的请求好比 GET /index.html 用一个 1 来表示,POST /index.html 用 2 来表示。那么是否是能够节省不少字节?网络

为 HTTP/2 的专门量身打造的 HPACK 即是相似这样的思路延伸。它使用一份索引表来定义经常使用的 HTTP Header。把经常使用的 HTTP Header 存放在表里。请求的时候便只须要发送在表里的索引位置便可。例如 :method=GET 使用索引值 2 表示,:path=/index.html 使用索引值 5 表示(完整的列表参考:HPACK Static Table)。只要给服务端发送一个 Frame,该 Frame 的 Payload 部分存储 0x8285,Frame 的 Type 设置为 Header 类型,即可表示这个 Frame 属于 HTTP Header,请求的内容是:并发

GET /index.html

为何是 0x8285,而不是 0x0205? 这是由于高位设置为 1 表示这个字节是一个彻底索引值(key 和 value 都在索引中)。相似的,经过高位的标志位能够区分出这个字节是属于一个彻底索引值,仍是仅索引了 key,仍是 key 和 value 都没有索引。由于索引表的大小的是有限的,它仅保存了一些经常使用的 HTTP Header,同时每次请求还能够在表的末尾动态追加新的 HTTP Header 缓存。动态部分称之为 Dynamic Table。Static Table 和 Dynamic Table 在一块儿组合成了索引表:性能

<----------  Index Address Space ---------->
<-- Static  Table -->  <-- Dynamic Table -->
+---+-----------+---+  +---+-----------+---+
| 1 |    ...    | s |  |s+1|    ...    |s+k|
+---+-----------+---+  +---+-----------+---+
                       ^                   |
                       |                   V
                 Insertion Point      Dropping Point

HPACK 不只仅经过索引键值对来下降数据量,同时还会将字符串进行霍夫曼编码来压缩字符串大小。

以经常使用的 User-Agent 为例,它在静态表中的索引值是 58,它的值是不存在表中的,由于它的值是多变的。第一次请求的时候它的 key 用 58 表示,表示这是一个 User-Agent ,它的值部分会进行霍夫曼编码(若是编码后的字符串变动长了,则不采用霍夫曼编码)。服务端收到请求后,会将这个 User-Agent 添加到 Dynamic Table 缓存起来,分配一个新的索引值。客户端下一次请求时,假设上次请求User-Agent的在表中的索引位置是 62, 此时只须要发送 0xBE(一样的,高位置 1),即可以表明: User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36。其过程以下图所示:

HPACK 原理图
图片引用自这里

最终,相同的 Header 只须要发送索引值,新的 Header 会从新加入 Dynamic Table。

Multipexing 多路复用

每一个 Frame Header 都有一个 Stream ID 就是被用于实现该特性。每次请求/响应使用不一样的 Stream ID。就像同一个 TCP 连接上的数据包经过 IP:PORT来区分出数据包去往哪里同样。经过 Stream ID 标识,全部的请求和响应均可以欢快的同时跑在一条 TCP 连接上了。 下图是 http 和 spdy(http2 的模型和 spdy 是相似的) 的并发模型对比:

http并发模型

当流并发时,就会涉及到流的优先级和依赖。优先级高的流会被优先发送。图片请求的优先级要低于 CSS 和 SCRIPT,这个设计能够确保重要的东西能够被优先加载完。

Server Push

当服务端须要主动推送某个资源时,便会发送一个 Frame Type 为 PUSH_PROMISE 的 Frame,里面带了 PUSH 须要新建的 Stream ID。意思是告诉客户端:接下来我要用这个 ID 向你发送东西,客户端准备好接着。客户端解析 Frame 时,发现它是一个 PUSH_PROMISE 类型,便会准备接收服务端要推送的流。

结束语

本文简化了不少 HTTP/2 协议中的具体细节,只描述了 HTTP/2 中主要特性实现的基本过程。

若是你想实现一个支持 HTTP/2 的服务器,那么你能够移步 HTTP/2 官网 作更多了解,它还提供了一份已经实现 HTTP/2 的项目列表:https://github.com/http2/http...

另外,关于 HTTP/2 性能如何,能够参考官方小组给出的例子:https://http2.akamai.com/demo

UPYUN 在不久的未来也会加入对 HTTP/2 协议支持,为用户提供更好更快的云加速服务。

追加:目前又拍云已全网支持 HTTP/2 协议及 SPDY3.1协议。

又拍云 CDN 当前已全平台支持 HTTP/2,并已默认开启。又因 HTTP/2 是在 HTTPS 协议的基础上实现的,因此只要使用又拍云 HTTPS 加速服务的域名,均可免费享受 HTTP/2 服务,无需作任何特殊配置。而开启HTTPS,只需完成证书申请与管理,无须繁杂流程,轻松实现网站与 Web 应用的 HTTPS 加密部署。

延伸阅读

相关文章
相关标签/搜索