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
TAG:一分菜地 |