logo头像

叩首问路,码梦为生

前端面试系列(3)——HTTP/2新特性

如果你认为前端不需要了解 HTTP 的话你就大错特错了,根据师兄师姐们的面试经验反馈,前端面试时关于 HTTP 的问题提问的几率也很大,而且对于更高一层的 tcp/ip 协议的认知也是前端开发者需要掌握的,所以我打算把关于 HTTP/2 新特性的文章作为前端面试系列的第三篇文章,如果想对 HTTP/2 协议有更深入的了解,可以点击文末的扩展阅读链接。

HTTP/2 源自 SPDY2

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

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

HTTP/2 的优势

1. HTTP 是一个二进制协议

基于二进制的 HTTP/2 可以使成帧的使用变得更为便捷。在 HTTP1.1 和其他基于文本的协议中,对帧的起始和结束识别起来相当复杂。而另一方面,这项决议同样使得我们可以更加便捷的从帧结构中分离出那部分协议本身的内容。而在 HTTP1 中,各个部分相互交织,犹如一团乱麻。

HTTP/2 会发送有着不同类型的二进制帧,但他们都有如下的公共字段:Type, Length, Flags, Steam Identifier 和 frame payload;规范中一共定义了 10 种不同的帧,其中最基础的两种分别对应于 HTTP 1.1 的 DATA 和 HEADERS。之后我会更详细的介绍它们其中的一部分。

二进制协议的优势显而易见:解析开销更小,描述协议也更高效。

2. 多路复用的流

流是一个逻辑上的联合,一个独立的,双向的帧序列可以通过一个 HTTP/2 的连接在服务端与客户端之间不断的交换数据。

每个单独的 HTTP/2 连接都可以包含多个并发的流,这些流中交错的包含着来自两端的帧。流既可以被客户端/服务器端单方面的建立和使用,也可以被双方共享,或者被任意一边关闭。在流里面,每一帧发送的顺序非常关键。接收方会按照收到帧的顺序来进行处理。

流的多路复用意味着在同一连接中来自各个流的数据包会被混合在一起。就好像两个(或者更多)独立的“数据列车”被拼凑到了一辆列车上,但它们最终会在终点站被分开。

3. 优先级和依赖性

每个流都包含一个优先级(也就是“权重”),它被用来告诉对端哪个流更重要。当资源有限的时候,服务器会根据优先级来选择应该先发送哪些流。

借助于 PRIORITY 帧(关于 HTTP/2 中帧的介绍可以查看文末扩展阅读),客户端同样可以告知服务器当前的流依赖于其他哪个流。该功能让客户端能建立一个优先级“树”,所有“子流”会依赖于“父流”的传输完成情况。

优先级和依赖关系可以在传输过程中被动态的改变。这样当用户滚动一个全是图片的页面的时候,浏览器就能够指定哪个图片拥有更高的优先级。或者是在你切换标签页的时候,浏览器可以提升新切换到的页面所包含流的优先级。

4. 头压缩

HTTP 是一种无状态的协议。简而言之,这意味着每个请求必须要携带服务器需要的所有细节,而不是让服务器保存住之前请求的元数据。因为 HTTP/2 并没有改变这个范式,所以它也需要这样(携带所有细节)。

这也保证了 HTTP 可重复性。当一个客户端从同一服务器请求了大量资源(例如页面的图片)的时候,所有这些请求看起来几乎都是一致的,而这些大量一致的东西则正好值得被压缩。

当每个页面资源的个数上升的时候,cookies 和请求的大小都会增加,而每个请求都会包含的 cookie 几乎是一模一样的。

HTTP 1.1 请求的大小正变得越来越大,有时甚至会大于 TCP 窗口的初始大小,这会严重拖累发送请求的速度。因为它们需要等待带着 ACK 的响应回来以后,才能继续被发送。这也是另一个需要压缩的理由。

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 表示。

5. 重置

HTTP 1.1 有一个缺点是:当一个含有确切值的 Content-Length 的 HTTP 消息被送出之后,你就很难中断它了。当然,通常你可以断开整个 TCP 链接(但也不总是可以这样),但这样导致的代价就是需要通过三次握手来重新建立一个新的 TCP 连接。

一个更好的方案是只终止当前传输的消息并重新发送一个新的。在 HTTP/2 里面,我们可以通过发送 RST_STREAM 帧来实现这种需求,从而避免浪费带宽和中断已有的连接。

6.服务器推送

这个功能通常被称作“缓存推送”。主要的思想是:当一个客户端请求资源 X,而服务器知道它很可能也需要资源 Z 的情况下,服务器可以在客户端发送请求前,主动将资源 Z 推送给客户端。这个功能帮助客户端将 Z 放进缓存以备将来之需。

服务器推送需要客户端显式的允许服务器提供该功能。但即使如此,客户端依然能自主选择是否需要中断该推送的流。如果不需要的话,客户端可以通过发送一个 RST_STREAM 帧来中止。

7. 流量控制

HTTP/2 上面每个流都拥有自己的公示的流量窗口,它可以限制另一端发送数据。对于每个流来说,两端都必须告诉对方自己还有更多的空间来接受新的数据,而在该窗口被扩大前,另一端只被允许发送这么多数据。

而只有数据帧会受到流量控制。

8. HTTP/2 的基石-Frame

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

1
2
3
4
5
6
7
8
9
+-----------------------------------------------+
| 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。二者的转换和关系如下图:

9.HTTP/2 对 web 开发的影响

到目前为止,HTTP/2 还没被大范围部署使用,我们也无法确定到底会发生什么变化。

HTTP/2 减少了网络往返传输的数量,并且用多路复用和快速丢弃不需要的流的办法来完全避免了 head of line blocking(线头阻塞)的困扰。它也支持大量并行流,所以即使网站的数据分发在各处也不是问题。合理利用流的优先级,可以让客户端尽可能优先收到更重要的数据。

所有这些加起来,页面载入时间和站点的响应速度都会更快。简而言之,它们都代表着更好的 web 体验。

然而这里的问题在于:对于网站的开发者而言,在短期内开发和部署同一套前端来支持 HTTP 1.1 和 HTTP/2 的客户端访问并获得最大性能将会是一个挑战。考虑到这些问题,彻底发掘 HTTP/2 的潜力还有很长一段路要走

扩展阅读

支付宝打赏 微信打赏

听说赞过就能年薪百万