server-side-events(SSE)開發指南(Node)

SSE是介於websocket、長短輪訓以外的一種服務端推送的方式,用數據流的形式發送文本數據,可想象成網絡視頻的文字版。他的好處有javascript

  • 使用簡單,無需藉助第三方庫(如 socket.io
  • 基於HTTP協議(WebSocket 是一個獨立協議),無需對其作額外處理。還能享受HTTP2帶來的優點
  • 默認支持斷線重連
  • 支持自定義發送的消息類型

詳細對比,這裏我選擇嘗試將一個本來基於輪詢的web app轉到sse上來。雖然這套技術看上去使用很簡單,但可能因爲普及程度不高和資料較少的緣由,在開發過程當中會遇到不少的坑和要面臨的新東西。這裏幫你們總結一下,後端使用了koa.js(express應該會更簡單)。html

後端

對於一個SSE相應咱們須要返回以下一些HTTP頭java

Content-Type: text/event-stream
Cache-Control: no-cache, no-transform
Connection: keep-alive
X-Accel-Buffering: no
複製代碼

在其餘的教程中提供的http頭可能沒有這裏的全,區別主要在於:node

  • Cache-Control中須要包含no-transform,沒有這個的話在開發中,若是你用了create-react-app等工具來轉發你的請求,那麼你的數據流極可能被壓縮,形成你怎麼也收不到響應。這裏當時排查了蠻久的(issue
  • no-transform是開發環境中的遇到的問題,可是在生產環境仍然還存在問題,好比個人網站使用nginx作反向代理的,默認會對應用的響應作緩衝(buffering),以致於我應用返回的消息沒有立馬發出去。因此咱們須要給http頭加上一條X-Accel-Buffering: no(issue

當設置好header後,咱們就能夠寫入數據了。通常來講咱們只須要監聽數據的更新而後使用res.write便可寫入數據:react

const onEvent = function(data) {
    res.write(`event: message\n`);
    res.write(`data: ${JSON.stringify(data)}\n\n`);
};

emitter.on('message', onEvent);
複製代碼

咱們用\n來分隔每一行數據,用\n\n來分隔每個事件。每個事件中包含事件的type和事件的data,分別用兩行來描述。好比上面是返回來一個message事件(若不指定事件類型,則默認message)。下圖中咱們還返回來一個withdraw事件,對應的數據行應該是event: withdrawnginx

events

koa.js返回

對於koa狀況比較複雜,官方不推薦咱們直接操做res對象,而是給context(ctx)對象的body賦值。官方例子git

其實咱們只須要給ctx.body賦一個可寫流,關於node流的概念能夠看taobao的這篇文章。如官方示例的:github

/** * Create a transform stream that converts a stream * to valid `data: <value>\n\n' events for SSE. */

var Transform = require('stream').Transform;
var inherits = require('util').inherits;

module.exports = SSE;

inherits(SSE, Transform);

function SSE(options) {
  if (!(this instanceof SSE)) return new SSE(options);

  options = options || {};
  Transform.call(this, options);
}

SSE.prototype._transform = function(data, enc, cb) {
  this.push(data.toString('utf8'));
  cb();
};
複製代碼

注意官方實例中有個坑就是默認給每行數據前面加上了data:前綴,這裏刪除了。在使用const body = ctx.body = SSE()後就能夠對body對象使用body.write了。詳見官方實例,實例db.js文件中的可讀流是可選項。web

客戶端

客戶端(瀏覽器)的使用就很是簡單了。大部分的瀏覽器支持SSE,並且咱們有針對老瀏覽器的兼容方案,如Yaffle express

使用上真的是特別的簡單,並且幾乎沒有什麼坑

const evtSource = new EventSource('/events');

 evtSource.addEventListener('event', function(evt) {
      const data = JSON.parse(evt.data);
      // Use data here
 }, false);
複製代碼

上面的event能夠替換爲你的其餘自定義事件。注意這裏的鏈接中斷後會自動重連,也許你須要監聽onerror事件來作一些額外的處理(API)。致使中斷的緣由可能有時間間隔到期、網絡錯誤等。你能夠經過定時向客戶端返回內容來避免間隔到期:

// Heartbeat
const nln = function() {
    res.write('\n');
};
const hbt = setInterval(nln, 15000);

// Clear heartbeat and listener
req.on('close', function() {
    clearInterval(hbt);
    emitter.removeListener('event', onEvent);
});
複製代碼

將輪詢替換爲sse後仍是很清爽的。注意和websocket不一樣sse是單向數據流,咱們在發送消息的時候須要使用其它的接口,能夠經過node的events來監聽觸發推送。

參考資料

相關文章
相關標籤/搜索