HTML5 Server-Sent Events 機制簡介

本文將介紹 HTML5 Server-Sent Events 機制如何運作?

致謝

感謝點部落團隊舉辦 HTML 5 & JavaScript 程式開發實戰贈書活動,讓筆者有幸可以獲得這本好書。

摘要

以往在設計網頁應用程式時,若想要從 Client 端定期從 Server 端取得資料以更新給使用者時,可能會用到定期以程式方式重新整理網頁,或進階一點使用 XMLHttpRequest 物件來與 Server 端做資料交換。在 SignalR Framework 問世後要做到上述的效果便顯得更加簡便且有效率了,許多前輩已經寫了不少跟 SignalR 相關的好文,有興趣的朋友可以瀏覽參考資料中的網址。而 HTML5 也提供實作單向 Server-Sent Events 以及雙向的 WebSocket 資料傳輸的物件,本文將針對 Server-Sent Events 來做介紹。

實作 Server 端

所謂 Server-Sent Event 中文解釋為伺服器推送或伺服器推播,主要是用來將伺服器上的資料自動傳輸至 Client 端,使用的是 HTTP 通訊協定。首先需要 Server 端的應用程式,筆者以 ASP.NET 泛型處理常式來實作本文 Server 端的應用程式,您也可以利用 PHP、JSP 等其他 Server 端的程式語言來實作。

Server 端的程式推送給 Client 端的封包是由 event(事件或識別名稱,預設值為 message)、data(要傳送給 Client 端的資料內容)、id(Server 端推送資料到 Client 端時的事件ID) 和 retry(以毫秒為單位,用來定義兩次要求之間的間隔時間)等四個欄位所組成,其格式為【欄位名稱:欄位內容】。其中又以 data 欄位最為重要,若 Server 端沒有傳送該欄位給 Client 端,即便您定義了資料接收的事件,該事件仍不會被觸發。

下列程式碼用來示範將回傳目前 Server 端的系統時間給 Client 端的瀏覽器,其中 ContentType 必須要設定為【text/event-stream】整個 Server-Sent Events 機制才能正常運作。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace ServerSendEvents
{
    /// <summary>
    /// ServerSide 的摘要描述
    /// </summary>
    public class ServerSide : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/event-stream";
            context.Response.Write(string.Format("data:{0}\n", DateTime.Now.ToString()));
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

實作 Client 端

Client 端要能接收 Server 端的推送資料,必須先建立 EventSource 物件,例如下列的程式碼,透過建構式傳入的 URL 來設定要從哪裡接收資料,接著 EventSource 物件便會以非同步方式來向 URL 參數所對應的 Server 端提出要求,以接收回傳的資料。

var es = new EventSource(url);

處理 Server 端推送下來的資料時,有三個事件屬性可以使用,分別是 onopen(連線建立時觸發)、onmessage(接收到 Server 端推送的訊息時觸發) 與 onerror(連線失敗時觸發)。

var es = new EventSource("ServerSide.ashx");
var stat;

es.onerror = function (e) {
	switch (e.target.readyState) {
		case EventSource.CONNECTING:
			stat = "等待重新連線";
			break;
		case EventSource.CLOSED:
			stat = "連線失敗,停止連線";
			break;
	}
	document.getElementById("divMsg").innerHTML += stat + "<br/>";
}

es.onmessage = function (e) {
	document.getElementById("divMsg").innerHTML += "現在時刻:" + e.data.toString() + "<br/>";
}

es.onopen = function (e) {
	switch (e.target.readyState)
	{
		case EventSource.CONNECTING:
			stat = "Connecting";
			break;
		case EventSource.OPEN:
			stat = "Open";
			break;
		case EventSource.CLOSED:
			stat = "Closed";
			break;
		default:
			stat = "n/a";
			break;
	}
	document.getElementById("divMsg").innerHTML += "連線狀態:" + stat + "<br/>"
}

執行結果如下:

SNAGHTMLd4a92e

若您希望與 Server 端停止連線,可以呼叫 EventSource 的 Close 方法,例如下列的程式碼(其中 es 為上面所宣告的 EventSource 物件):

function Stop()
{
	es.close();
}

變更重新連線的間隔時間

由上一節的執行結果可知,預設 Server 端每 5000 毫秒推送一次資料到 Client 端的瀏覽器上,若您要調整這個間隔時間,可以在 Server 端設定 retry 欄位的值,例如下列的程式碼:

context.Response.Write(string.Format("retry:{0}\n", "1000"));

修改過後重新執行 Client 端網頁,您將看到如下圖的結果,已經由原本的每 5000 毫秒推送一次,變成每 1000 毫秒推送一次。

SNAGHTMLe023f8

參考資料

- HTML5 & JavaScript 程式開發實戰 - 導讀

- 小朱® 的技術隨手寫 - SignalR 系列文章

- HTML5 完美風暴

- In 91 - [.NET]SignalR簡介 - 建立 realtime 的網站

- HTML5 Server-Sent Events

- Server-Sent Events

- Server-sent Events