Html5實踐之EventSource

最近嘗試了一下服務器端的推送,以前的作法都是客戶端輪詢,定時向服務器發送請求。但這形成了個人一些困擾:php

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

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

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

由於是第一次接觸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()
%>

//代碼解釋:mvc

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

也許你們應該注意到,php和asp的案例有一點不同,就是php推送的信息一個使用了"\n\n"做爲結束標誌,而asp卻沒有。而本人實踐則是用asp.net+mvc,通過測試,若是不以"\n\n"做爲結束標誌,那麼客戶端將不能接收到推送的值。還有須要特別聲明一下:推送的信息格式必須爲」data:內容\n\n「,不然。。。asp.net

        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每次都會觸發這兩個事件,並且每次獲得的結果都同樣,爲什麼?

相關文章
相關標籤/搜索