HTML5 服務器推送事件(Server-sent Events)實戰開發

原文連接

簡介html

服務器推送事件(Server-sent Events)是 HTML 5 規範中的一個組成部分,能夠用來從服務端實時推送數據到瀏覽器端。相對於與之相似的 COMET 和 WebSocket 技術來講,服務器推送事件的使用更簡單,對服務器端的改動也比較小。對於某些類型的應用來講,服務器推送事件是最佳的選擇。本文對服務器推送技術進行了詳細的介紹,包含瀏覽器端和服務器端的相應實現細節,爲在實踐中使用該技術提供了指南。java

對於通常的 Web 應用開發,大多數開發人員並不陌生。在 Web 應用中,瀏覽器和服務器之間使用的是請求 / 響應的交互模式。瀏覽器發出請求,服務器根據收到的請求來生成相應的響應。瀏覽器再對收到的響應進行處理,展示給用戶。響應的格式多是 HTML、XML 或 JSON 等。隨着 REST 架構風格和 AJAX 的流行,服務器更多地使用 JSON 做爲響應的數據格式。Web 應用使用 XMLHttpRequest 對象來發送請求,並根據服務器端返回的數據,對頁面的內容進行動態更新。一般來講,用戶在頁面上的操做,好比點擊或移動鼠標,會觸發相應的事件。由 XMLHttpRequest 對象來發出請求,獲得服務器響應以後進行頁面的局部更新。這種方式的不足之處在於:服務器端產生的數據變化不能及時地通知瀏覽器,而是須要等到下次請求發出時才能被瀏覽器獲取。對於某些對數據實時性要求很高的應用來講,這種延遲是不能接受的。web

爲了知足這類應用的需求,就須要有某種方式可以從服務器端推送數據給瀏覽器,以保證服務器端的數據變化能夠在第一時間通知給用戶。目前常見的解決辦法有很多,主要能夠分紅兩類。這兩類方法的區別在因而否基於 HTTP 協議來實現。不使用 HTTP 協議的作法是使用 HTML 5 新增的 WebSocket 規範,而使用 HTTP 協議的作法則包括簡易輪詢、COMET 技術和本文中要介紹的 HTML 5 服務器推送事件。下面會對這幾種技術進行介紹。編程

在介紹 HTML 5 服務器推送事件以前,首先介紹一些上面提到的幾種服務器端數據推送技術。第一種是 WebSocket。WebSocket 規範是 HTML 5 中的一個重要組成部分,已經被不少主流瀏覽器所支持,也有很多基於 WebSocket 開發的應用。正如名稱所表示的同樣,WebSocket 使用的是套接字鏈接,基於 TCP 協議。使用 WebSocket 以後,實際上在服務器端和瀏覽器之間創建一個套接字鏈接,能夠進行雙向的數據傳輸。WebSocket 的功能是很強大的,使用起來也靈活,能夠適用於不一樣的場景。不過 WebSocket 技術也比較複雜,包括服務器端和瀏覽器端的實現都不一樣於通常的 Web 應用。json

除了 WebSocket 以外,其餘的實現方式是基於 HTTP 協議來達到實時推送的效果。第一種作法是簡易輪詢,即瀏覽器端定時向服務器端發出請求,來查詢是否有數據更新。這種作法比較簡單,能夠在必定程度上解決問題。不過對於輪詢的時間間隔須要進行仔細考慮。輪詢的間隔過長,會致使用戶不能及時接收到更新的數據;輪詢的間隔太短,會致使查詢請求過多,增長服務器端的負擔。跨域

COMET 技術改進了簡易輪詢的缺點,使用的是長輪詢。長輪詢的方式在每次請求時,服務器端會保持該鏈接在一段時間內處於打開狀態,而不是在響應完成以後就當即關閉。這樣作的好處是在鏈接處於打開狀態的時間段內,服務器端產生的數據更新能夠被及時地返回給瀏覽器。當上一個長鏈接關閉以後,瀏覽器會當即打開一個新的長鏈接來繼續請求。不過 COMET 技術的實如今服務器端和瀏覽器端都須要第三方庫的支持。瀏覽器

綜合比較上面提到的 4 種不一樣的技術,簡易輪詢因爲其自己的缺陷,並不推薦使用。COMET 技術並非 HTML 5 標準的一部分,從兼容標準的角度出發,也不推薦使用。WebSocket 規範和服務器推送技術都是 HTML 5 標準的組成部分,在主流瀏覽器上都提供了原生的支持,是推薦使用的。不過 WebSocket 規範更加複雜一些,適用於須要進行復雜雙向數據通信的場景。對於簡單的服務器數據推送的場景,使用服務器推送事件就足夠了。安全

在瀏覽器支持方面,服務器推送事件已經在除 IE 外的大部分桌面和移動瀏覽器上獲得了支持。支持服務器推送事件的瀏覽器及其版本包括:Firefox 6.0+、Chrome 6.0+、Safari 5.0+、Opera 11.0+、iOS Safari 4.0+、Opera Mobile 11.1+、Chrome for Android 25.0+、Firefox for Android 19.0+ 以及 Blackberry Browser 7.0+ 等。關於 IE 的支持,在下面的章節中有詳細的介紹。服務器

下面對服務器推送事件的規範進行具體的說明。cookie

回頁首

規範

Server-sent Events 規範是 HTML 5 規範的一個組成部分,具體的規範文檔見參考資源。該規範比較簡單,主要由兩個部分組成:第一個部分是服務器端與瀏覽器端之間的通信協議,第二部分則是在瀏覽器端可供 JavaScript 使用的 EventSource 對象。通信協議是基於純文本的簡單協議。服務器端的響應的內容類型是「text/event-stream」。響應文本的內容能夠當作是一個事件流,由不一樣的事件所組成。每一個事件由類型和數據兩部分組成,同時每一個事件能夠有一個可選的標識符。不一樣事件的內容之間經過僅包含回車符和換行符的空行(「\r\n」)來分隔。每一個事件的數據可能由多行組成。代碼示例 1 給出了服務器端響應的示例。

示例1. 服務器端響應的示例

data: first event

data: second event
id: 100

event: myevent
data: third event
id: 101

: this is a comment
data: fourth event
data: fourth event continue
複製代碼

如代碼示例 1 所示,每一個事件之間經過空行來分隔。對於每一行來講,冒號(「:」)前面表示的是該行的類型,冒號後面則是對應的值。可能的類型包括:

類型爲空白,表示該行是註釋,會在處理時被忽略。

類型爲 data,表示該行包含的是數據。以 data 開頭的行能夠出現屢次。全部這些行都是該事件的數據。

類型爲 event,表示該行用來聲明事件的類型。瀏覽器在收到數據時,會產生對應類型的事件。

類型爲 id,表示該行用來聲明事件的標識符。

類型爲 retry,表示該行用來聲明瀏覽器在鏈接斷開以後進行再次鏈接以前的等待時間。

在代碼示例 1 中,第一個事件只包含數據「first event」,會產生默認的事件;第二個事件的標識符是 100,數據爲「second event」;第三個事件會產生類型爲「myevent」的事件;最後一個事件的數據爲「fourth event\nfourth event continue」。當有多行數據時,實際的數據由每行數據以換行符鏈接而成。

若是服務器端返回的數據中包含了事件的標識符,瀏覽器會記錄最近一次接收到的事件的標識符。若是與服務器端的鏈接中斷,當瀏覽器端再次進行鏈接時,會經過 HTTP 頭「Last-Event-ID」來聲明最後一次接收到的事件的標識符。服務器端能夠經過瀏覽器端發送的事件標識符來肯定從哪一個事件開始來繼續鏈接。

對於服務器端返回的響應,瀏覽器端須要在 JavaScript 中使用 EventSource 對象來進行處理。EventSource 使用的是標準的事件監聽器方式,只須要在對象上添加相應的事件處理方法便可。EventSource 提供了三個標準事件,如表 1 所示。

表 1. EventSource 對象提供的標準事件 名稱說明事件處理方法 open 當成功與服務器創建鏈接時產生 onopen message 當收到服務器發送的事件時產生 onmessage error 當出現錯誤時產生 onerror

如以前所述,服務器端能夠返回自定義類型的事件。對於這些事件,可使用 addEventListener 方法來添加相應的事件處理方法。代碼示例 2 給出了 EventSource 對象的使用示例。

示例 2. EventSource 對象的使用示例
var es = new EventSource('events');
es.onmessage = function(e) {
    console.log(e.data);
};

es.addEventListener('myevent', function(e) {
    console.log(e.data);
});
複製代碼

如代碼示例 2 所示,在指定 URL 建立出 EventSource 對象以後,能夠經過 onmessage 和 addEventListener 方法來添加事件處理方法。當服務器端有新的事件產生,相應的事件處理方法會被調用。EventSource 對象的 onmessage 屬性的做用相似於 addEventListener( ‘ message ’ ),不過 onmessage 屬性只支持一個事件處理方法。

服務器端和瀏覽器端實現

1.服務端(用的是java其餘編程語言也能夠,只要返回數據格式正確便可)

這是一個消息推送接口,記得將

@RequestMapping``(``"pushMessage.json"``)

``public void pushMessage(HttpServletResponse response,HttpServletRequest request){

OutputStream bos = null;

        try {

            String data1 =" www.imiansha.com愛面紗網站上線了\n\n";

            String data2 = " www.imiansha.com愛面紗網站正在招兵買馬\n\n";

            //聲明瀏覽器在鏈接斷開以後進行再次鏈接以前的等待時間 10秒

            String retry = "retry:"+10000+"\n\n";

            //事件的標識符

            String id="id:100\n\n";

            //最後一次接收到的事件的標識符

            String last = request.getHeader("Last-Event-ID");

            logger.info(last);

            bos = new BufferedOutputStream(response.getOutputStream());

            response.setContentType("text/event-stream");//記得要設置哦

            bos.write(data1.getBytes());

            bos.write(data2.getBytes());

            bos.write(retry.getBytes());

            bos.write(id.getBytes());

            bos.flush();

        } catch (IOException e) {

            e.printStackTrace();

        }finally{

            try {

                bos.close();

            } catch (IOException e) {

                e.printStackTrace();

            }

        }
複製代碼

``}

2.客戶端實現

<!DOCTYPE html>

<html>

``<head>

``<meta charset=``"UTF-8"``>

``<title>HTML ``5 服務器推送消息事件</title>

``</head>

``<body>

``<h1>得到服務器更新的消息</h1>

``<div id=``"result"``></div>

``<script>

``if``(typeof(EventSource)!==``"undefined"``)

``{

``var eventSource=``new EventSource(``"/test/pushMessage.json"``);

``eventSource.onopen = function(){

console.log("鏈接打開。。。。。。");

``}

eventSource.onerror = function(e) { console.log('error'); };

``eventSource.onmessage=function(event) {

``document.getElementById(``"result"``).innerHTML+=event.data + ``"<br />"``;

``};

``}``else``{

``document.getElementById(``"result"``).innerHTML=``"Sorry, your browser does not support server-sent events..."``;

``}

``</script>

``</body>

</html>

對IE 支持

使用瀏覽器原生的 EventSource 對象的一個比較大的問題是 IE 並不提供支持。爲了在 IE 上提供一樣的支持,通常有兩種辦法。第一種辦法是在其餘瀏覽器上使用原生 EventSource 對象,而在 IE 上則使用簡易輪詢或 COMET 技術來實現;另一種作法是使用 polyfill 技術,即便用第三方提供的 JavaScript 庫來屏蔽瀏覽器的不一樣。本文使用的是 polyfill 技術,只須要在頁面中加載第三方 JavaScript 庫便可。應用自己的瀏覽器端代碼並不須要進行改動。通常推薦使用第二種作法,由於這樣的話,在服務器端只須要使用一種實現技術便可。

在 IE 上提供相似原生 EventSource 對象的實現並不簡單。理論上來講,只須要經過 XMLHttpRequest 對象來獲取服務器端的響應內容,並經過文本解析,就能夠提取出相應的事件,並觸發對應的事件處理方法。不過問題在於 IE 上的 XMLHttpRequest 對象並不支持獲取部分的響應內容。只有在響應完成以後,才能獲取其內容。因爲服務器端推送事件使用的是一個長鏈接。當鏈接一直處於打開狀態時,經過 XMLHttpRequest 對象並不能獲取響應的內容,也就沒法觸發對應的事件。更具體的來講,當 XMLHttpRequest 對象的 readyState 爲 3(READYSTATE_INTERACTIVE)時,其 responseText 屬性是沒法獲取的。

爲了解決 IE 上 XMLHttpRequest 對象的問題,就須要使用 IE 8 中引入的 XDomainRequest 對象。XDomainRequest 對象的做用是發出跨域的 AJAX 請求。XDomainRequest 對象提供了 onprogress 事件。當 onprogress 事件發生時,能夠經過 responseText 屬性來獲取到響應的部份內容。這是 XDomainRequest 對象和 XMLHttpRequest 對象的最大不一樣,也是使用 XDomainRequest 對象來實現相似原生 EventSource 對象的基礎。在使用 XDomainRequest 對象打開與服務器端的鏈接以後,當服務器端有新的數據產生時,能夠經過 XDomainRequest 對象的 onprogress 事件的處理方法來進行處理,對接收到的數據進行解析,根據數據的內容觸發相應的事件。

不過因爲 XDomainRequest 對象原本的目的是發出跨域 AJAX 請求,考慮到跨域訪問的安全性問題,XDomainRequest 對象在使用時的限制也比較嚴格。這些限制會影響到其做爲 EventSource 對象的實現方式。具體的限制和解決辦法以下所示:

服務器端的響應須要包含 Access-Control-Allow-Origin 頭,用來聲明容許從哪些域訪問該 URL。「*」表示容許來自任何域的訪問,不推薦使用該值。通常使用與當前應用相同的域,限制只容許來自當前域的訪問。

XDomainRequest 對象發出的請求不能包含自定義的 HTTP 頭,這就限制了不能使用 Last-Event-ID 頭來聲明瀏覽器端最近一次接收到的事件的標識符。只能經過 HTTP 請求的其餘方式來傳遞該標識符,如 GET 請求的參數或 POST 請求的內容體。

XDomainRequest 對象的請求的內容類型(Content-Type)只能是「text/plain」。這就意味着,當使用 POST 請求時,服務器端使用的框架,如 servlet,不會對 POST 請求的內容進行自動解析,沒法使用 HttpServletRequest 類的 getParameter 方法來獲取 POST 請求的內容。只能在服務器端對原始的請求內容進行解析,獲取到其中的參數的值。

XDomainRequest 對象發出的請求中不包含任何與用戶認證相關的信息,包括 cookie 等。這就意味着,若是服務器端須要認證,則須要經過 HTTP 請求的其餘方式來傳遞用戶的認證信息,好比 session 的 ID 等。

因爲 XDomainRequest 對象的這些限制,服務器端的實現也須要做出相應的改動。這些改動包括返回 Access-Control-Allow-Origin 頭;對於瀏覽器端發送的「text/plain」類型的參數進行解析;處理請求中包含的用戶認證相關的信息。

本文的示例使用的 polyfill 庫是 GitHub 上的 Yaffle 開發的 EventSource 項目,具體的地址見參考資源。在使用該 polyfill 庫,並對服務器端的實現進行修改以後,就能夠在 IE 8 及以上的瀏覽器中使用服務器推送事件。若是須要支持 IE 7,則只能使用簡易輪詢或 COMET 技術。本文的示例代碼見參考資源。

小結

若是須要從服務器端推送數據給瀏覽器,可使用的基於 HTML 5 規範標準的技術包括 WebSocket 和服務器推送事件。開發人員能夠根據應用的具體需求來選擇合適的技術。若是隻是須要從服務器端推送數據,服務器推送事件的規範更加簡單,實現起來更容易。本文對服務器推送事件的規範內容、服務器端和瀏覽器端的實現都進行了詳細的介紹,對如何支持 IE 瀏覽器也進行了具體的分析。

相關文章
相關標籤/搜索