轉帖:http://www.developersky.net/thread-63-1-1.html
一直以來,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類型定義了事件架構格式。
<html><br /> <head><br /> <mce:script type='text/javascript'><!--<br /> var source = new EventSource('Events');<br /> source.onmessage = function (event) {<br /> ev = document.getElementById('events');<br /> ev.innerHTML += "<br>[in] " + event.data;<br /> };</p><p>// --></mce:script><br /> </head><br /> <body><br /> <div id="events"></div><br /> </body><br /></html>
EventSource代表的是接收事件的用戶端的終點。用戶端通過建立EventSource對象來開啟一個event stream。建立EventSource對象時,該對象接收一個事件來源的URL作為其建構函式的參數。當每次收到新的事件數目據時onmessage事件處理器會被調用。
通常情況下,瀏覽器會限制到每個伺服器的串連的數量。在有些情況下,裝載多個包含到同一個域的EventSource對象的頁面會導致對每個EventSource建立一個專屬於該EventSource的串連,這種情況下很快就會超出串連數量限制。為了處理這種情況,我們可以使用共用的WebWorker,該對象共用一個EventSource的執行個體。另外,通過定義瀏覽器特定的EventSource實現,我們可以做到如果兩個EventSource的URL是相同的,那麼他們就重用相同的串連。這時,共用的串連就由瀏覽器特定的EventSource實現來管理。
當event stream開啟的時候,瀏覽器會發送如下的HTTP request。
REQUEST:
GET /Events HTTP/1.1<br />Host: myServer:8875<br />User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE)<br /> AppleWebKit/532+ (KHTML, like Gecko) Version/4.0.4<br /> Safari/531.21.10<br />Accept-Encoding: gzip, deflate<br />Referer: http://myServer:8875/<br />Accept: text/event-stream<br />Last-Event-Id: 6<br />Accept-Language: de-DE<br />Cache-Control: no-cache<br />Connection: keep-alive
Accept定義了需要的格式 text/event-stream。 雖然Server-Sent Events規範定義了text/event-stream的MIME 類型,該規範同時允許使用其他的事件架構格式。但是Server-Sent Events的實現必須支援test/event-stream格式。
根據text/event-stream的格式,一個事件有一個或多個注釋行和欄位行組成。注釋行是由冒號:開始的行。欄位域行由欄位名和欄位值組成,欄位名和欄位值也是由冒號:分隔。多個事件之間用空行分隔。下面就是一個Response的例子:
RESPONSE:
HTTP/1.1 200 OK<br />Server: xLightweb/2.12-HTML5Preview6<br />Content-Type: text/event-stream<br />Expires: Fri, 01 Jan 1990 00:00:00 GMT<br />Cache-Control: no-cache, no-store, max-age=0, must-revalidate<br />Pragma: no-cache<br />Connection: close</p><p>: time stream<br />retry: 5000</p><p>id: 7<br />data: Thu Mar 11 07:31:30 CET 2010</p><p>id: 8<br />data: Thu Mar 11 07:31:35 CET 2010</p><p>[...]
根據定義,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的例子。
class ServerHandler implements IHttpRequestHandler {<br /> private final Timer timer = new Timer(false);<br /> public void onRequest(IHttpExchange exchange) throws IOException {<br /> String requestURI = exchange.getRequest().getRequestURI();<br /> if (requestURI.equals("/ServerSentEventExample")) {<br /> sendServerSendPage(exchange, requestURI);<br /> } else if (requestURI.equals("/Events")) {<br /> sendEventStream(exchange);<br /> } else {<br /> exchange.sendError(404);<br /> }<br /> }<br /> private void sendServerSendPage(IHttpExchange exchange,<br /> String uri) throws IOException {<br /> String page = "<html>/r/n " +<br /> " <head>/r/n" +<br /> " <mce:script type='text/javascript'><!--<br />/r/n" +<br /> " var source = new EventSource('Events');/r/n" +<br /> " source.onmessage = function (event) {/r/n" +<br /> " ev = document.getElementById('events');/r/n" +<br /> " ev.innerHTML += /"<br>[in] /" + event.data;/r/n"+<br /> " };/r/n" +<br /> "<br />// --></mce:script>/r/n" +<br /> " </head>/r/n" +<br /> " <body>/r/n" +<br /> " <div id=/"events/"></div>/r/n" +<br /> " </body>/r/n" +<br /> "</html>/r/n ";<br /> exchange.send(new HttpResponse(200, "text/html", page));<br /> }<br /> private void sendEventStream(final IHttpExchange exchange)<br /> throws IOException {<br /> // get the last id string<br /> final String idString = exchange.getRequest().getHeader(<br /> "Last-Event-Id", "0");<br /> // sending the response header<br /> final BodyDataSink sink = exchange.send(new<br /> HttpResponseHeader(200, "text/event-stream"));<br /> TimerTask tt = new TimerTask() {<br /> private int id = Integer.parseInt(idString);<br /> public void run() {<br /> try {<br /> Event event = new Event(new Date().toString(), ++id);<br /> sink.write(event.toString());<br /> } catch (IOException ioe) {<br /> cancel();<br /> sink.destroy();<br /> }<br /> };<br /> };<br /> Event event = new Event();<br /> event.setRetryMillis(5 * 1000);<br /> event.setComment("time stream");<br /> sink.write(event.toString());<br /> timer.schedule(tt, 3000, 3000);<br /> }<br />}<br />XHttpServer server = new XHttpServer(8875, new ServerHandler());<br />server.start();
Server-Sent Events規範推薦如果沒有其他的資料要發送,那麼週期性發送keep-alive注釋。這樣Proxy 伺服器就可以在某個HTTP串連有一段時間不活躍時關閉該串連,這樣Proxy 伺服器能夠關閉閒置串連來避免浪費串連在沒有響應的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的瀏覽器支援(這就是規範的威力啊)。
轉帖:http://www.developersky.net/thread-63-1-1.html