Html5實踐之EventSource


最近嘗試了一下服務器端的推送,之前的做法都是客戶端輪詢,定時向服務器發送請求。但這造成了我的一些困擾:

1:輪詢是由客戶端發起的,那么在服務端就不能判別我要推送的內容是否已經過期,因為我很難判斷某個信息是否已經推送給全部的客戶端,那么服務端就需要緩存大量的數據。如果數據保存在數據庫,那么還要每次請求都需要查詢數據庫,這對數據庫和系統設計都是一個很大的挑戰。

2:請求的頻率太高,每次的請求包中含有同樣的數據,這對pc來說也許算不得什么,但是對於移動客戶端來講,這應該不是最佳的方案。尤其是遇到還要做權限判斷的時候,那么服務端的邏輯和效率也會造成用戶體驗的降低。

好在Html5為我們提供了一種方式:Server-Sent Events包含新的HTML元素EventSource和新的MIME類型 text/event-stream來完成我的需要。

因為是第一次接觸Html5,w3school中也有對EventSource的說明和使用。於是馬上開始着手實踐。

頁面腳本就不用說了,按照w3school的方式即可。

var source=new EventSource("demo_sse.php");
source.onmessage=function(event)
  {
  document.getElementById("result").innerHTML+=event.data + "<br />";
  };

服務端的代碼也是如初一折,w3school提供了php和asp的代碼:

//php方式
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$time = date('r');
echo "data: The server time is: {$time}\n\n";
flush();
?>

//asp方式
<%
Response.ContentType="text/event-stream"
Response.Expires=-1
Response.Write("data: " & now())
Response.Flush()
%>

//代碼解釋:

  • 把報頭 "Content-Type" 設置為 "text/event-stream"
  • 規定不對頁面進行緩存
  • 輸出發送日期(始終以 "data: " 開頭)
  • 向網頁刷新輸出數據

也許大家應該注意到,php和asp的案例有一點不一樣,就是php推送的信息一個使用了"\n\n"作為結束標志,而asp卻沒有。而本人實踐則是用asp.net+mvc,經過測試,如果不以"\n\n"作為結束標志,那么客戶端將不能接收到推送的值。還有需要特別聲明一下:推送的信息格式必須為”data:內容\n\n“,否則。。。

        public void Subscribe()
        {
            HttpContext.Response.ContentType = "text/event-stream";
            HttpContext.Response.CacheControl = "no-cache";
            HttpContext.Response.Write("data:" + DateTime.Now.ToString()+ "\n\n");
            HttpContext.Response.Flush();
        }

至此,客戶端應該可以收到服務端推送的值。而如此簡單的結構真的可以完成我們需要的功能設計嗎?
此例我們只是推送了一個當前時間,而我們實際要推送的值是不斷變化的,不然也就沒有推送的必要了。

於是我想到了將訂閱的請求保存起來,當需要推送的時候,在對每個請求進行循環推送,於是有了下面的代碼:

    public class PublishService
    {
        private static IDictionary<string, HttpResponseBase> contexts = new Dictionary<string, HttpResponseBase>();
        public static void AddHttpContext(HttpContextBase context)
        {
            var token = context.GetToken(”CookieName“);
            if (!contexts.Keys.Contains(token))
                contexts.Add(token, context.Response);
        }

        private static void Publish()
        {
            foreach (var context in contexts.Values)
            {
                context.ContentType = "text/event-stream";
                context.CacheControl = "no-cache";
                msg = GetData(context.GetToken("CookieName"));
                context.Write("data:" + msg + "\n\n");
                context.Flush();
            }
        }
        public void Subscribe()
        {
            PublishService.AddHttpContext(HttpContext);
            PublishService.Publish();
        } }

可是在進行測試的時候Chrome告訴我:EventSource's response has a MIME type ("text/plain") that is not "text/event-stream". Aborting the connection.

而FF告訴我:Firefox 無法建立到 http://localhost:8000/Location/Notification/Subscribe 服務器的連接。

經過調試發現,在每次flush的時候發生異常:Server cannot flush a completed response.這究竟是為啥呢?不論是google,還是baidu,我都沒能找到合適的答案,所以此案至今未結,如哪位知道請細說一二。

於是乎,我放棄了這種方式,轉而就推送一個時間看看是什么效果。結果發現Chrome每隔3秒向客戶端推送一次,而FF是每5秒推送一次。有了這樣一個發現,那么服務端的設計就應該是另一個樣子:

        public void Subscribe()
        {
            var data = GetData();

            HttpContext.Response.ContentType = "text/event-stream";
            HttpContext.Response.CacheControl = "no-cache";
            HttpContext.Response.Write("data:" + data + "\n\n");
            HttpContext.Response.Flush();
        }

服務端只需要提供一個服務GetData(),這個服務用來獲取我們需要推送的信息,而根據Server-Sent Events規范推薦如果沒有其他的數據要發送,那么定期的發送keep-alive注釋。其他的事情就不用我們操心了。

這只是一個簡單的使用,因為本人在使用EventSource的時候走了一些彎路,所以寫出來,希望能對大家有些幫助。

總結:走了一些彎路的原因是起初對Server-Sent Events機制的不清楚。個人理解:該機制依然並非由服務器端發起,而是還由客戶端向服務端定時發送請求,EventSource只是提供了一個很好的封裝,不用自己去維護而已。

后記:

求教:EventSource.onopen和EventSource.onerror每次都會觸發這兩個事件,而且每次得到的結果都一樣,為何?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM