WdBly Blog

懂事、有趣、保持理智

WdBly Blog

懂事、有趣、保持理智

周维 | Jim

603927378@qq.com

前端进阶篇-HTTP/2

HTTP/2多路复用,服务端推送,二级制分帧,首部压缩

了解HTTP/2

简单来说,HTTP/2,是HTTP协议的第二个主要版本。HTTP/2是HTTP协议自1999年HTTP1.1发布后的首个更新,主要基于google的SPDY协议。
HTTP/2的特点是:在不改动HTTP语义、方法、状态码、URI及首部字段的情况下,大幅度提高了web性能。

SPDY是谷歌研发的应用层协议,用户提升web性能。

HTTP/2和HTTP1.X相比的新特性

  1. HTTP/2多路复用
  2. HTTP/2服务端推送
  3. HTTP/2二级制分帧
  4. HTTP/2首部压缩

HTTP/2多路复用

线头阻塞 (Head-of-Line Blocking)

  • HTTP1.0的线头阻塞发生在客户端,只有客户端收到请求结果后才会发送下一个请求。(会重新建立TCP连接)
  • HTTP1.1的线头阻塞发生在服务端,根据浏览器不同,客户端能向同一域名的服务器同时建立多个TCP连接,但是服务器必须按照请求的顺序返回。(不必重新建立连接)

HTTP/2通过多路复用解决线头阻塞问题:

  • HTTP1.x的请求都是基于文本传输,服务端一次只能处理一个请求或响应
  • HTTP1.1中支持同时多个请求发送给服务端,但服务端必须按顺序返回响应。

HTTP/2的请求不再基于文本传输,具体方式为:在同一个域名下开启一个TCP connection,每个http请求以流(stream)的方式在此连接上传输,每个stream是由若干帧(frame) 组成,frame是HTTP/2资源传输的最小单元,frame中的字段identifier标识此帧属于哪一个流,identifier相同的frame属于同一流,服务端将identifier相同的帧解析成可用数据。在这个TCP connection中,同时传输了多个流的帧数据,这就是HTTP/2的多路复用。

image.png

每一个帧(frame) 都包含length、type、flags、stream identifier、frame playload等字段, 这里说下帧的type字段,在HTTP/2的标准中定义了10种不同的帧type。

  • HEADERS(包含HTTP头和可选的优先级参数)
  • DATA(流的核心内容)
  • PRIORITY(流的优先级)
  • RST_STREAM(终止流)
  • SETTINGS(设置此连接的参数)
  • PUSH_PROMISE(服务器推送)
  • PING(测量RTT)
  • GOAWAY(终止连接)
  • WINDOW_UPDATE(协商一端要接收多少字节,流量控制)
  • CONTINUATION(继续传输头部数据)

frame的PRIORITY字段用于标识流的权重(1-256)和依赖关系,以正确的顺序请求资源对于用户体验至关重要。

  • 客户端通过对象的依赖关系告诉服务器哪些资源应该优先传输
  • 权重让服务器知道拥有共同依赖关系资源的优先级

服务器根据依赖关系和权重进行带宽分配,高优先级的资源获得更多的带宽。

权重的大体规则不外乎 根文档 - 样式文件 - 字体文件 - 脚本文件 - 背景图 - 图标文件 - 图片。 不同的浏览器规则会有所差异。

ios中浏览器的资源权重参考:

  1. HTML - 255
  2. CSS - 24
  3. JS - 24
  4. FONTS - 16
  5. IMAGES - 8

ps: 服务端对于高优先级的资源的策略是分配更多的带宽,但是不会保证此资源一定会优先返回,这是为了避免线头阻塞

多路复用让http请求变得廉价,创建一个新流的成本很低,理论上可以同时发起无限个请求,不过我们可以在stream的SETTINGS字段上设置SETTINGS_MAX_CONCURRENT_STREAMS用于限制最大并发数。

HTTP/2服务端推送

HTTP1.x中,我们通过将重要的js代码或者css样式代码嵌入html页面中,让页面可以快速响应,但是内嵌代码最大的弊端是无法缓存。哪怕只是修改一个小样式也必须下载整个文件。

这也是其它资源整合的共同弊端,如图片以base64的方式嵌入代码、CSS精灵图(sprite)等,每当修改了精灵图中的一个小图,我们不得不重新下载整张精灵图。

HTTP/2允许服务端主动推送资源,当TCP connection建立后,服务器可以主动推送资源而非客户端发起请求,服务端推送是基于frame的PUSH_PROMISE字段来实现的。而且服务端push的资源是可以被缓存的。

关于服务端推送一个常见的问题是如果客户端已经有一份缓存了怎么办?

浏览器可以通过PUSH_PROMISE字段判断资源是否已经缓存,然后通过发送RST_STREAM帧用于终止推送。在这个过程中,服务端只是推送了资源的地址,并没有直接推送资源的实体,这样的目的也是为了减少不必要的资源传输。

HTTP/2规定,由客户端发起的流的ID(stream identifier)都是奇数,由服务端发起的流ID是偶数。

二进制分帧

HTTP/2实际上是对HTTP1的一个数据封装,本质上没有改变HTTP1的语义,状态码、URI 以及header字段等。

image.png

要实现对HTTP1的封装,HTTP/2在传输层(TCP, UDP 或者SSL/TSL安全协议层)和应用层之间新增一个二进制分帧层,在分帧层中,HTTP/2将所有信息分割成帧(frame),其中原http header信息被封装到 HEADER 类型的帧中,内容被封装到DATA类型的帧中。

二进制分帧是多路复用,头部信息压缩,服务端推送, 流量控制等HTTP/2功能的基础。

首部压缩

为什么要压缩?

在HTTP1.x中,HTTP协议都是由状态行,消息头,主体构成,在传输过程中,主体内容一般都是经过gzip压缩,或者传说的本身就是二进制流(视频,图片)等,而消息头和状态行都是纯文本传输的。

而且很多消息头中的字段冗余如 COOKIE,UserAgent等,浪费了大量带宽,增加了网络延时。很多时候请求消息头的大小甚至超过了主体内容的大小。

压缩首部体积

HTTP/2通过维护静态字典和动态字典的方式来压缩首部

  • 静态字典中包含了常见的头部名称或者头部名称和值的组合,如method:GET
  • 动态字典中包含了每个请求特有的键值对,如自定义的头信息,针对每个TCP connection,都需要维护一份动态字典。

1:对于静态字典中匹配的头部名称或头部名称和值的组合,可以使用一个字符表示,如建立连接时:

method:GET 可以使用 1表示 (完全匹配)
cookeie: xxx 可以使用 2:xxx表示 (头部匹配)

2:同时将cookeie: xxx加入动态字典中,这样后续的整个cookie键值对都可以使用一个字符表示:

cookeie: xxx 可以使用 3表示 (加入到动态字典)

3:对于静态字典和动态字典中都不存在的内容,使用了哈夫曼(霍夫曼)编码来压缩体积。

不是所有的头部信息都可以被添加到动态字典中,这时就是使用哈夫曼编码压缩的场景。

HTTP1中的状态行信息如Status等在2.0中都以键值对的方式放入头部中,做为消息头的一部分。

动态字典会在每个TCP连接上维护一个,且动态字典的内容是根据请求量逐渐累积的。
所以在HTTP/2的网站中,我们需要做的不再是整合资源和散列域名了。而是分散资源和使用一个连接,以下两种情况下浏览器会使用一个连接:

  1. 同一域名下的资源请求。
  2. 不同域名但是同一IP地址,且证书(TSL证书)相同。

总结

  1. HTTP/2只是对HTTP1的封装,并没有改变HTTP1的语义等
  2. HTTP/2新增的二级制分帧多路复用,服务端推送等功能基础
  3. HTTP/2通过维护字典的方式压缩首部体积
  4. HTTP/2的站点中所需的优化手段和HTTP1的区别,不在需求资源整合,而是倡导资源分散(有利于缓存和动态字典积累),不在需求域名散列,而是倡导使用一个连接(减少建立TCP连接的开销,减轻服务端压力,不用维护多个动态字典)