Nginx + Koa 开启http/2 server push

Posted 一分菜地

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx + Koa 开启http/2 server push相关的知识,希望对你有一定的参考价值。

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


/ nginx开启server push /


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

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



/ Node开启server push /


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


link: </main.37d69167.css>; as=style; rel=preload, </main.f06ad8b3.css>; as=style; rel=preload


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


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


主要逻辑:


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

  2. 将这些资源拼接成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承诺推荐这些资源


Nginx + Koa 开启http/2 server push


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

Nginx + Koa 开启http/2 server push


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


1. 浏览器收到PUSH_PROMISE,注意最后一行,main.6e607578.js的stream_id为12


Nginx + Koa 开启http/2 server push

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


Nginx + Koa 开启http/2 server push


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


Nginx + Koa 开启http/2 server push


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


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


以上是关于Nginx + Koa 开启http/2 server push的主要内容,如果未能解决你的问题,请参考以下文章

Vue+koa2开发一款全栈小程序(5.服务端环境搭建和项目初始化)

利用Nginx或koa

nginx开启http2和TLS1.3

使用Koa2进行Web开发静态文件与路由

使用Koa2进行Web开发静态文件与路由

koa开启cors允许跨域,携带cookies