HTTP 2.0 服務器推送技術的創新

jopen 11年前發布 | 8K 次閱讀 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的殺手級特性!

     本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!