1. 程式人生 > >HTML5中的伺服器‘推送’技術 -Server-Sent Events

HTML5中的伺服器‘推送’技術 -Server-Sent Events

一直以來,HTTP協議都是嚴格遵循Request-Response模型的。客戶端傳送一個Request到伺服器,伺服器對Request作出響應並將Response傳送回客戶端。也就是說,所有的互動都是由客戶端發起的,伺服器不會發起任何互動。
為了建立互動性更強的web應用程式,AJAX出現了,AJAX實現了一個動態的從Server獲取資料的方法。通過使用AJAX,瀏覽器通過XMLHttpRequest API來發送HTTP request。XMLHttpRequest使得我們可以在不阻塞使用者介面的情況下向伺服器傳送非同步的HTTP request來獲取資料。但是AJAX並沒有定義新的HTTP request型別,只是將傳送HTTP request的工作移到了後臺,不影響使用者的操作。因此AJAX也沒有打破Request-Response的模型,還是由瀏覽器從伺服器‘拉’資料。
另外一種技術是Comet,也稱為反向Ajax。和Ajax一樣,Comet也是建立在已經存在的HTTP協議之上的。Comet會維護一個長期存活的HTTP連線,傳送‘假’的請求從而得到response。
這些都是為了打破HTTP協議的限制的解決方法。但是在HTML5

中,這種限制會被打破。HTML5規範中包含很多功能強大的特性,能夠將瀏覽器變成功能齊全的RIA客戶端平臺。Server-Sent Event和WebSockets就是其中的兩個特性,這兩個特效能夠幫助我們實現伺服器將資料‘推送’到客戶端的功能。
在這篇文章中我們先介紹一下Server-Sent Events特性
Server-Sent Events
Server-Sent Events實際上將Comet技術進行了標準化。Server-Sent Events規範“定義了API來開啟一個HTTP連線,通過該連線能夠獲取從伺服器推送的通知”。Server-Sent Events包含新的HTML元素EventSource和新的MIME型別 text/event-stream,這個MIME型別定義了事件框架格式。

EventSource代表的是接收事件的客戶端的終點。客戶端通過建立EventSource物件來開啟一個event stream。建立EventSource物件時,該物件接收一個事件來源的URL作為其建構函式的引數。當每次收到新的事件資料時onmessage事件處理器會被呼叫。
通常情況下,瀏覽器會限制到每個伺服器的連線的數量。在有些情況下,裝載多個包含到同一個域的EventSource物件的頁面會導致對每個EventSource建立一個專屬於該EventSource的連線,這種情況下很快就會超出連線數量限制。為了處理這種情況,我們可以使用共享的WebWorker,該物件共享一個EventSource的例項。另外,通過定義瀏覽器特定的EventSource實現,我們可以做到如果兩個EventSource的URL是相同的,那麼他們就重用相同的連線。這時,共享的連線就由瀏覽器特定的EventSource實現來管理。
當event stream開啟的時候,瀏覽器會發送如下的HTTP request。
REQUEST:

Accept定義了需要的格式 text/event-stream。 雖然Server-Sent Events規範定義了text/event-stream的MIME 型別,該規範同時允許使用其他的事件框架格式。但是Server-Sent Events的實現必須支援test/event-stream格式。
根據text/event-stream的格式,一個事件有一個或多個註釋行和欄位行組成。註釋行是由冒號:開始的行。欄位域行由欄位名和欄位值組成,欄位名和欄位值也是由冒號:分隔。多個事件之間用空行分隔。下面就是一個Response的例子:
RESPONSE:

根據定義,Event stream不應該被快取。為了避免快取,在Response的頭中包含了Cache-Control,禁止了快取該response。
上面的例子中,該response中包含三個事件。第一個事件包含一個註釋行和一個retry欄位;第二個事件和第三個事件都是包含一個id欄位和一個data欄位。data欄位中包含的是事件的資料,在上面的例子中是當前的時間。id欄位是用來在event stream中跟蹤處理程序的。上面的例子中,伺服器端的應用程式會每隔5秒向event stream中寫入一個事件。當EventSource接收到該事件後,onmessage事件處理器就會被呼叫。
不同的是,第一個事件不會觸發onmessage處理器。第一個個事件沒有data欄位,只包含一個註釋行和一個retry欄位,retry欄位是用於重新連線的目的的。retry欄位定義了重新連線的時間,單位是毫秒。如果收到了這樣的欄位,EventSource會更新其相關的重新連線時間的屬性。在發生網路錯誤的情況下,重新連線時間在提高可靠性方面扮演了重要的角色。當EventSource例項發現連線斷開了,在指定的重新連線時間之後會自動的重建連線。
我們可以看到,在HTTP request中,我們可以指定Last-Event-Id。EventSource在重建連線的時候會指定該值。每次EventSource收到包含id欄位的事件時,EventSource的last event id屬性會被更改,在重建連線的時候,EventSource的last event id屬性會被寫入HTTP request的Last-Event-Id中。這樣如果伺服器端實現了lastEventId的處理,就可以保證在重建的連線中不會發送已經收到的事件了。
下面的程式碼是一個基於Java HTTP 庫xLightweb(包含HTML5預覽擴充套件)的HttpServer的例子。

Server-Sent Events規範推薦如果沒有其他的資料要傳送,那麼定期的傳送keep-alive註釋。這樣代理伺服器就可以在某個HTTP連線有一段時間不活躍時關閉該連線,這樣代理伺服器能夠關閉空閒的連線來避免浪費連線在沒有響應的HTTP伺服器上。傳送註釋事件使得這種機制不會發生在有效的連線上。儘管EventSource會自動重建連線,但是傳送註釋事件還是能夠避免不必要的重新連線。
Server-Sent Event是基於HTTP streaming的。如上所述,response會一直開啟,當伺服器端有事件發生的時候,事件會被寫入response中。理論上來說,如果網路的中介如HTTP代理不立即轉發部分的response,HTTP streaming會導致一些問題。現在的HTTP RFC (RFC2616 Hypertext Transfer Protocal – HTTP/1.1)沒有要求部分的response必須被立刻轉發。但是,很多已經存在的流行的、工作良好的web應用程式是基於HTTP streaming的。而且,產品級別的中介通常會避免緩衝大量的資料來降低記憶體的佔用率。
和其他的流行的Coment協議如Bayeux和BOSH不同,Server-Sent Event只支援單向的從伺服器到客戶端的通道。Bayeux協議支援雙向的通訊通道。另外,Bayeux能夠使用HTTP Streaming和輪詢。BOSH協議也支援雙向通訊通道,但是BOSH是基於輪詢機制的。(所謂的輪詢就是客戶端定期傳送request到伺服器端來獲取資料)。
儘管Server-Sent Events比Bayeux和BOSH的功能要少,但是在只需要單向的伺服器向客戶端推送資料的情況下(在很多情況下都是這樣),Server-Sent Events有潛力成為占主導地位的協議。Server-Sent Events協議被Bayeus和BOSH要簡單的多。另外,Server-Sent Events被所有相容HTML5的瀏覽器支援(這就是規範的威力啊)。