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

來源:互聯網
上載者:User

標籤:des   style   blog   http   color   java   使用   os   

轉帖: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>   <head>     <mce:script type=‘text/javascript‘><!--        var source = new EventSource(‘Events‘);        source.onmessage = function (event) {           ev = document.getElementById(‘events‘);           ev.innerHTML += "<br>[in] " + event.data;        };     // --></mce:script>  </head>  <body>    <div id="events"></div>  </body></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.1Host: myServer:8875User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE)         AppleWebKit/532+ (KHTML, like Gecko) Version/4.0.4         Safari/531.21.10Accept-Encoding: gzip, deflateReferer: http://myServer:8875/Accept: text/event-streamLast-Event-Id: 6Accept-Language: de-DECache-Control: no-cacheConnection: 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 OKServer: xLightweb/2.12-HTML5Preview6Content-Type: text/event-streamExpires: Fri, 01 Jan 1990 00:00:00 GMTCache-Control: no-cache, no-store, max-age=0, must-revalidatePragma: no-cacheConnection: close: time streamretry: 5000id: 7data: Thu Mar 11 07:31:30 CET 2010id: 8data: Thu Mar 11 07:31:35 CET 2010[...]

 

根據定義,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 {  private final Timer timer = new Timer(false);  public void onRequest(IHttpExchange exchange) throws IOException {    String requestURI = exchange.getRequest().getRequestURI();    if (requestURI.equals("/ServerSentEventExample")) {      sendServerSendPage(exchange, requestURI);    } else if (requestURI.equals("/Events")) {      sendEventStream(exchange);    } else {      exchange.sendError(404);    }  }  private void sendServerSendPage(IHttpExchange exchange,           String uri) throws IOException {    String page = "<html>/r/n " +        " <head>/r/n" +        "     <mce:script type=‘text/javascript‘><!--/r/n" +        "        var source = new EventSource(‘Events‘);/r/n" +        "        source.onmessage = function (event) {/r/n" +        "          ev = document.getElementById(‘events‘);/r/n" +        "          ev.innerHTML += /"<br>[in] /" + event.data;/r/n"+        "        };/r/n" +        "     // --></mce:script>/r/n" +        " </head>/r/n" +        " <body>/r/n" +        "    <div id=/"events/"></div>/r/n" +        " </body>/r/n" +        "</html>/r/n ";    exchange.send(new HttpResponse(200, "text/html", page));  }  private void sendEventStream(final IHttpExchange exchange)           throws IOException {    // get the last id string    final String idString = exchange.getRequest().getHeader(            "Last-Event-Id", "0");    // sending the response header    final BodyDataSink sink = exchange.send(new             HttpResponseHeader(200, "text/event-stream"));    TimerTask tt = new TimerTask() {       private int id = Integer.parseInt(idString);       public void run() {         try {           Event event = new Event(new Date().toString(), ++id);           sink.write(event.toString());         } catch (IOException ioe) {           cancel();           sink.destroy();         }       };    };    Event event = new Event();    event.setRetryMillis(5 * 1000);    event.setComment("time stream");    sink.write(event.toString());    timer.schedule(tt, 3000, 3000);  }}XHttpServer server = new XHttpServer(8875, new ServerHandler());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的瀏覽器支援(這就是規範的威力啊)。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.