HTTP 2.0 服務器推送技術的創新
HTTP 2.0 允許服務器為一次客戶端請求發送多個響應(并行的) - 即, 服務器推送。等等,我們為什么想要這樣? 是這樣的,一個一般的網頁需要一堆的額外資源,比如JavaScript,CSS,和圖片, 對這些資源的引用就內置在服務器產生的HTML頁面里。那么,與其等待客戶端去發現對這些資源的引用,為什么不讓服務器立即把這些資源都發送過去呢?服務器推送可以消除這種不必要的網絡等待所帶來的整個往返耗時。
事實上,如果你曾經內聯的引用過一個資源(CSS, JS,或者一個圖片),那么你已經“模擬”過服務器推送:一個內聯資源被作為父文檔的一部分“推送”過來。唯一的不同是HTTP 2.0使得這一模式更有效而且更強大!
著手進行HTTP 2.0服務器推送
一個內聯的資源,從定義上說,是父文檔的一部分。那么,它就不能被獨立的緩存,并且它需要在許多不同的頁面之間被復制 - 這是很低效的。相反的,推送的資源可以被瀏覽器獨立的緩存起來,從而在許多頁面中復用。這里準備了一個例子:
spdy.createServer(options, function(req, res) { // push JavaScript asset (/main.js) to the client res.push('/main.js', {'content-type': 'application/javascript'}, function(err, stream) { stream.end('alert("hello from push stream!")'); });// write main response body and terminate stream res.end('Hello World! <script src="/main.js"></script>'); }).listen(443);</pre>
上例中,我們有一個借助于node-spdy module實現的最簡SPDY服務,它對所有的入站請求的響應是,輸出一個字符串"Hello World!",跟著是一個腳本標記。除此之外,我們還做了些聰明的事情: 我們推送“main.js”文件到客戶端,該文件觸發了一個JavaScript彈出框。
結果是,當瀏覽器發現HTML響應中的腳本標記時,“main.js”文件已經在緩存中了,不會發生額外的網絡往返!HTTP 2.0 服務器推送 廢棄了內聯引用。最重要的是,服務器推送已經被所有的支持SPDY的瀏覽器所支持(Firefox,Opera,和Chrome)。
我們還能推送什么?
用服務器推送替換內嵌資源是一個典型例子。但是,為什么止步于此,我們還能推送什么? 其實, 任何HTTP響應都是平等的游戲。我們能推送一次重定向嗎? 是的, 那很簡單:
spdy.createServer(options, function(req, res) { //push JavaScript asset (/newasset.js) to the client res.push('/newasset.js', {'content-type': 'application/javascript'}, function(err, stream) { stream.end('alert("hello from (redirected) push stream!")'); });// push 301 redirect: /asset.js -> /newasset.js res.push('/asset.js', {':status': 301, 'Location': '/newasset.js'}, function(err, stream) { stream.end('301 Redirect'); });
// write main response body and terminate stream res.end('<script src="/asset.js"></script>'); }).listen(443);</pre>
跟上面的例子一樣,只不過我們已經把“asset.js”資源替換為一次對“newasset.js”文件的301重定向。瀏覽器對重定向和“asset.js”都進行了緩存,并且在無任何額外網絡往返的情況下執行腳本。怎么應用?我把這個問題留給讀者當練習吧。
我們還能做的更多嗎?推送緩存失效信息和再驗證信息到客戶端,怎么樣?我們可以把已經存在于客戶端緩存中的任何資源標記為失效(即推送一個失效時間戳),或者相反,推送一個攜帶未來時間戳的304來更新資源的生存期。簡言之,服務器能夠主動的管理客戶端緩存!
如果服務器過于激進或者不守規則,那么客戶端可以限制推送流的數量,或者只要它愿意它可以取消某一個流。找出合適的策略并在兩端之間作出平衡,是找出服務器推送最佳實踐的關鍵 - 這不是一項輕松的挑戰,但卻能帶來很高回報。
注: 目前的瀏覽器不支持推送緩存再驗證。應該支持嗎?
服務器推送的客戶端通知
HTTP 2.0服務器推送不是諸如Server-Sent Events (SSE)或WebSocket的技術替代品。通過HTTP 2.0服務器推送傳遞過來的資源由瀏覽器來處理,但卻不能上升到程序代碼的層面 - 因為沒有JavaScript API來獲取這些事件的通知。不過,解決這個難題的方法卻很簡單,因為我們可以把一個SSE通道和服務器推送結合起來,使兩者都達到最佳效果:
spdy.createServer(options, function(req, res) { // set content type for SSE stream res.setHeader('Content-Type', 'text/event-stream');messageId = 1; setInterval(function(){ // push a simple JSON message into client's cache var msg = JSON.stringify({'msg': messageId}); var resourcePath = '/resource/'+messageId; res.push(resourcePath, {}, function(err, stream) { stream.end(msg) });
// notify client that resource is available in cache res.write('data:'+resourcePath+'\n\n'); messageId+=1;
}, 2000); }).listen(443);</pre>誠然,下面這個例子有點兒傻,但卻很能說明問題: 服務器每隔兩秒鐘產生一條消息,并把它推送到客戶端的緩存里,然后發送一個SSE通知到客戶端。另一方面,客戶端在應用程序代碼中訂閱這些事件,并執行自己的邏輯來處理這些事件:
<script> var source = new EventSource('/');source.onmessage = function(e) { document.body.innerHTML += "SSE notification: " + e.data + '<br />';
// fetch resource via XHR... from cache! var xhr = new XMLHttpRequest(); xhr.open('GET', e.data); xhr.onload = function() { document.body.innerHTML += "Message: " + this.response + '<br />'; }; xhr.send();
}; </script></pre>
長效的SSE流,被推送的資源,和其它所有HTTP 2.0流都有效的復用了同一個TCP鏈接 - 沒有額外的鏈接開銷和不必要的網絡往返。通過把SSE和服務器推送結合起來,我們可以傳送任意資源(二進制或文本)到客戶端,利用瀏覽器本地緩存帶來的好處,并執行恰當的應用邏輯! 最重要的是,今天這已經成為可能。
服務器推送創新
服務器推送開啟了一個充滿了優化機會的全新世界。我們上面所舉的例子只是展示了冰山一角,仍有許多其他問題需要考慮:
- 什么資源應當被推送? 何時被推送?它們是否已經在緩存中?
- 服務器能夠自動推斷應當推送哪些資源嗎?
- 是否應當支持諸如主動緩存管理之類的高級應用?
- 我們應當怎樣設計我們的應用,才能從服務器推送中獲益最多?
- ... </ul>
通過HTTP 2.0,服務器有機會變得非常非常智能,包括如何優化傳送一些具體的資源,更重要的是,也包括優化對整個應用的傳送。類似的,瀏覽器可能增加額外的API和能力來使這個過程更加高效。事實上,長期來看,服務器推送很可能會成為HTTP 2.0的殺手級特性!
本文地址:http://www.oschina.net/translate/innovating-with-http-2.0-server-push
原文地址:http://www.igvita.com/2013/06/12/innovating-with-http-2.0-server-push/