HTTP/2 简介

目前互联网几乎所有的内容都采用HTTP/1.1作为通信协议,然而这一伟大的协议正在老去。随着前端承载着越来越多的职责,对HTTP的要求也更高,是时候迎来HTTP/2的时代了。

什么是HTTP/2?

HTTP 协议规范的标准化组织(IETF)在2015年2月19日通过了 HTTP/2 协议。HTTP/2 是在必须支持 HTTP/1.1 的核心特征的要求下定义的下一代HTTP传输协议,它在 SPDY 协议的基础上更新而来。实际上,HTTP/2 包含了两个部分:

  • Hypertext Transfer Protocol version 2 – RFC7540
  • HPACK – Header Compression for HTTP/2 – RFC7541

那么,HTTP/2带来了哪些改变?

二进制分帧层

HTTP/2 是一个二进制协议。它把请求和响应拆分成了不可读的二进制格式的帧,在TCP连接上传输。HTTP/2 一共定义了10种不同的帧,每个帧都有不同的类型和用途。例如 HTTP/1.1 的首部信息会被封装到 Headers 帧,而 Request body 则封装到 Data 帧中。

HTTP2.0

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

多路复用

每个独立的 HTTP/2 链接可包含多个并发的流。流是一个独立的、双向的帧序列。通过同一连接中多个流的混合,即可实现高效的多路复用。

这项机制对前端性能提升的效果是显而易见的:

  • 可以并行交错地发送请求和响应,互不影响;
  • 只使用一个连接即可并行发送多个请求和响应;
  • 消除不必要的延迟,从而减少页面加载的时间。

流量控制

我们已经知道 HTTP/2 可以在一个连接中并发多个流,实际上这些流和 TCP 一样是相互竞争的,因此,我们也需要对流中的帧进行流量控制,HTTP/2 为我们提供了这样一种机制:

  • 流量控制是逐跳的,而非端到端;
  • 流量控制是基于窗口更新帧的;
  • 帧类型决定了是否适用流量控制规则,目前只有DATA帧受流量控制;
  • 接收端对流量控制全权掌控,发送端必须遵守接收端的流量控制限制;

优先级和依赖性

尽管 HTTP/2 提供了相当高效的机制,连接的资源仍是有限宝贵的,因此优先级确保了资源能优先被重要的请求使用,并且 HTTP/2 提供了一个依赖参数指定流的依赖关系。

新建流时可以在Header帧中包含优先级信息来对流标记优先级。对于已存在的流,优先级帧可以用来改变流优先级。因此,优先级是能动态的被改变。

然而,严格按照优先级来处理很容易造成队首堵塞的情况,因此合理分配资源和带宽也是十分重要的,应该交错的处理不同优先级的帧。

服务器推送

服务器推送是 HTTP/2 新增的一个强大的功能。它允许服务器可以对一个客户端主动推送多个响应。这个功能又被称为“缓存推送”,在HTTP/1.1 时代,我们常用的优化手段——资源内联就与之相似,然而它更强大高效,因为客户端还可以缓存起来,甚至可以由不同的页面共享。

这项服务有几点需要注意:

  • 服务器推送需要客户端显式的允许服务器提供该功能,并且客户端能自主选择是否需要中断该推送的流;
  • 推送的资源在遵循同源策略的情况下可在不同页面间共享;
  • 服务器可以按照优先级来推送资源。

Header压缩

HTTP 协议是一种无状态协议,这意味着每个请求必须要携带服务器需要的所有细节,因此每个 HTTP 请求都会带来大量开销。HTTP/2 为减少这不必要的性能损耗采用了 HPACK 进行Header压缩。

HPACK

顾名思义,HPACK 是专为 HTTP/2 头部设计的压缩格式。HPACK是如此重要,以至于IETF单独为它指定了相应的标准。下面,介绍一下 HPACK 的一些技术细节。

我们知道,HTTP 首部有很多字段是重复的,如果我们约定将一些常用的字段进行使用一套统一的特殊编码来表示,比如 1 表示 GET 请求,2 表示 POST 请求,这样就能节省很多字节消耗。 为 HTTP/2 的量身打造的 HPACK 便是类似这样的思路延伸。它使用一份静态字典(Static Table)详见来定义常用的 HTTP Header。把常用的 HTTP Header 存放在表里。请求的时候便只需要发送在表里的索引位置即可。同时每次请求还可以在表的末尾动态追加新的 HTTP Header 缓存,这样后续整个键值对就可以使用一个字符表示了。类似的,服务端也可以更新对方的动态字典( Dynamic Table)。

使用字典可以极大地提升压缩效果,其中静态字典在首次请求中就可以使用。为了压缩静态、动态字典中不存在的内容,还可以使用霍夫曼编码来减小体积。HTTP/2 使用了一份静态霍夫曼码表,也需要内置在客户端和服务端之中。

例如:UserAgent在静态字典中的索引值是58,它的值是不存在表中的,因为它的值是可变的。第一次请求的时候它的 key 用 58 表示,表示这是一个 UserAgent ,它的值部分会进行霍夫曼编码。服务端收到请求后,会将这个 UserAgent 添加到动态字典缓存起来,分配一个新的索引值。客户端下一次请求时,假设上次请求UserAgent的在表中的索引位置是 62, 此时只需要发送 0xBE,便可以代表一个完整的UserAgent: User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36。

它的具体过程可用下图非常直观的表示:

http2.2

HTTP/2 应用

兼容性

目前浏览器支持情况仍然不是很高,但是最新版的各大浏览器都已经得到部分支持。对于不支持 HTTP/2 的浏览器,我们仍可以使用 HTTP/1.1 进行优雅降级。

HTTP/1.1 升级至 HTTP/2

ClearText

HTTP/1.1 本身指定了一个升级策略,请求时给服务器发送一个带升级 Header 的报文,如果服务器支持 HTTP/2,它将返回 101 状态码,并开始在该连接上使用 HTTP/2。然而出于安全性、网络节点的复杂性等多方面考虑,浏览器厂商并不认可该方案。

ALPN

Next Protocol Negotiation (NPN) 是一个用来在 TLS 服务器上协商SPDY的协议。IETF 将这个非正式标准进行规范,从而变成了ALPN(Application Layer Protocol Negotiation)。它的解决方案就是在握手过程中客户端告诉服务器自己所支持的所有协议,服务器根据自己所支持的协议,选择最适合的响应客户端。

如此,HTTP/2 既能减去不必要的一次往返通信,也能保证隐私的安全性。当然,TLS 也有自己的问题,比如会消耗更多的CPU和其他资源等。

以上就是对 HTTP/2 的一个简单介绍,可以预见,未来各浏览器都将更好的支持 HTTP/2,也会有越来越多的 Web 应用使用 HTTP/2。从前端角度来讲, HTTP/2 将前端带入一个更专注于应用本身的阶段。然而短期内,我们仍需要同时支持 HTTP/1.1 和 HTTP/2,相信这还有很长一段路要走。

参考资料