Nginx+Koa 开启http/2 server push

一看这标题就是不准备好好写的。对的,最近特别忙,只能简单记录一下折腾的东西。

/ Nginx开启server push /

升级nginx到1.13.9或以上版本(注意1.13.6修改http/2的实现,与一些旧版本客户端不兼容,比如旧版Android okhttp)

nginx配置中加上http2_push_preload on,表示使用preload header来作为server push标识

/ Node开启server push /

Node处理文件内容时加上preload header即可,例如:

link: ; as=style; rel=preload, ; as=style; rel=preload

此处比较科学的做法应该是使用一个中间件,在返回内容之前,根据要返回的HTML内容来处理preload header。

因为我主要处理静态html文件,又用的koa,所以将主要逻辑放在了koa-static的setHeaders函数中。setHeaders主要用于在返回静态文件前设置自定义的header,刚好和server push的场景相符。

主要逻辑:

读取html文件,使用正则表达式匹配出css和js文件的路径(如果有图片也可以一起)

将这些资源拼接成preload header的值

这样就可以实现http/2 server push了。

/ 有缓存不再推送 /

上面两步都超简单,网上的教程满天飞。这一个标题“有缓存不再推送”内容才是促成本文的原因。

回到http/2 server push的原理,浏览器访问index.html时,server除了返回html,还将css / js / image也一并推送回来,这样浏览器接受完之后,就不用再单独请求一次,从而加快页面的加载。

但是这里有一个矛盾,如果我们的静态资源是有长缓存的,下一次请求的时候该推送还是不该推送呢?如果推送,则相当于是忽略了缓存,白白浪费带宽。

到目前为止,这些仍然是网上文章中的主要结论。于是我就验证了一下,有缓存时是否真的会浪费带宽。于是我打开了chrome://net-internals/#http2,然后找到了活跃的http/2连接。在Source Type为HTTP2 SESSION那一栏中,可以看到详细的HTTP/2通信过程。我截了一些图:

首先是没有缓存的情况下,server push开启:

1. 浏览器请求完html之后,发现了PUSH_PROMISE,按字面意思理解,也就是server承诺推荐这些资源

2. 接下来浏览器接受了这些推送的stream,把资源弄下来了

到这里一切正常。但是当资源有缓存时,再次请求,server push仍然开启的情况下:

2. 接下来搜索这个stream_id=12,找到一串看不懂的东西,看起来跟TCP窗口调整的逻辑很类似?

3. 再接着,罢工了……清楚地写着Abandoned.已放弃,应该是浏览器拒绝了这次推送,注意stream_id仍然是12

也就是说,在有缓存的情况下,浏览器并不会傻乎乎地再接受推送。试验到这里后有点不可思议,于是又从两个方面做了验证。

1. 网络带宽

这是chrome://net-internals/#timeline 的时间线,比较明显的有15个峰,分别是我发起的15次请求,前5次缓存有效,中间5次没有缓存,后5次缓存有效。可以明显看到,有缓存时带宽是比没缓存时低的,证实有缓存时push并不会真的发生。

2. 服务端网络IO

在有缓存的情况下,服务端网络IO大约每个请求0.2-0.4M,在无缓存的情况下,服务端网络IO大约每个请求2.2M-2.4M。同样证实有缓存的情况下,push并不会发生。

/ 有缓存不再推送 /

其实有上面的结论后,不需要再做什么了。但是仍然怀疑这是不是哪一端实现的bug,要不然为什么大家都把这当作一个不可解决的缺陷呢?

那么就假装我不知道上面这一段吧,还是需要自己根据是否有缓存控制是否开启server push。

最容易想到的办法就是cookies了,将已推送过的资源url放入cookies中,下次再请求时进行对比,已有的资源就不再推送。

但是这样的话,cookies会非常庞大。于是有人提出了使用BloomFilter来存放推送信息。

BloomFilter是一个空间和时间复杂度都比较小的算法,主要用于快速进行“有损”存在性判定。所谓存在性判定就是给定一个key,确定它是否存在。而“有损”的意思则是指它并不100%精准。

它的原理可以简单这么理解:首先放一个数组,接下来每一个需要检查的key都做一个hash,映射到这个数组中的某几个位置,如果这几个位置全部为1,则认为这个key存在,否则认为这个key不存在。

考虑到server push的场景,即使不精准也不影响页面打开使用,因此它是适用的。HTTP server软件H2O就是使用了类似的算法。

本来我也打算这么实现一版,但是后来转念一想,我就一个页面,3个资源,好像没必要这么麻烦。不如直接全部hash一把,hash匹配就认为有缓存,hash不匹配就认为没缓存,全部重新推送一遍。

于是就有了类似这样的代码:

简单粗暴有效。

参考:

- https://imququ.com/post/cache-aware-server-push-in-h2o.html

- https://www.keakon.net/2018/03/07/NGINX%E6%94%AF%E6%8C%81HTTP/2serverpush%E4%BA%86

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180515G0C6YB00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券