<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
實時獲取伺服器端的資料,大家第一時間想到的是輪詢和 WebSocket 兩種方案,其實還有一種新方案 Server-sent events 下文簡稱(SSE)。SSE 中的資料只能由伺服器端推向使用者端
SSE 是基於 http 協定的伺服器推播技術,資料只能從伺服器端到使用者端。伺服器端把序列化後的資料傳送給使用者端, 整個過程持續不斷直至連線關閉
下面是 WebSocket、輪詢和 SSE 的功能對比
基於伺服器端單向的向用戶端推播資訊的特性,SSE 使用場景主要有
下面講解如何在使用者端使用 SSE
EventSource
範例,向伺服器發起連線const evtSource = new EventSource();
對於自定義事件,伺服器端和使用者端一定要保持事件名一致。伺服器端通過自定義事件傳送資料, 就會觸發自定義事件。SSE 預設支援 message
事件,下面以 message
事件為例
evtSource.addEventListener("message", (event) => { let payload; try { payload = JSON.parse(event.data); // <--- event.data 需要反序列化 console.log("receiving data...", payload); } catch (error) { console.error("failed to parse payload from server", error); } });
自定義事件的回撥函數接收 event
物件,event.data
存著伺服器端發給使用者端的資料但是需要反序列化
可以通過 Chrome Devtool 工具檢視 eventsource
通訊情況,如圖所示
1
- 自定義事件名,伺服器端和使用者端需要保持一致2
- EventStream Tab,資料都在這裡3
- 伺服器端推播給使用者端的資料如果連線發生錯誤,就會觸發 error
事件
evtSource.addEventListener("error", (err) => { console.error("EventSource failed:", err); });
SSE 提供 close
方法,用來關閉 SSE 連線
evtSource.close();
通過 caniuse 檢視 SSE 瀏覽器相容性,如圖所示
除了 IE 瀏覽器不支援,其它現代瀏覽器都支援,所以放心大膽在專案中使用 SSE
在平常的工作中,每次寫 SSE 的事件監聽和錯誤處理會很麻煩。多個業務場景需要使用 SSE 時,就需要對 SSE 進行封裝。接下來我們嘗試封裝一個簡單的 SSE SDK,方便在專案中使用
當我們決定寫 SSE 的 SDK 時,首先想到使用物件導向(OOP)進行封裝。根據 SSE 的特性,那麼庫需要實現 subscribe
和 unsubscribe
兩個方法。通過確定 SSE
庫使用方式,根據使用方式確定 SDK 的實現。我們可以在程式碼中這樣使用,如下所示
// SSESdk 範例化 const SSE = new SSESdk(url, options); // 訂閱來自伺服器端的訊息 SSE.subscribe("message", (data) => { console.log("receive message from server", data); }); // 取消訂閱 SSE.unsuscribe();
我們要封裝的庫對外僅僅提供 subscribe
和 unsubscribe
兩個 Api,非常方便開發人員使用。 subscribe
用來訂閱來自伺服器端的訊息, unsubscribe
用來取消訂閱,關閉 SSE 連線,通過使用形式可以看出,使用 ES6 中的類語法。接下來我們先確定 SSE SDK 的大體結構
class SSEClient { constructor() {} subscribe(type, handler) {} unsunscribe() {} }
在 SSEClient
類中有三個方法需要實現,通過 constructor 接受可設定的引數,比如 SSE 建立連線失敗後的重試次數和重試時間。 subscribe
接收一個與後端保持一致的事件名和一個回撥函數。unsunscribe
不需要傳遞任何引數,呼叫 unsunscribe
方法關閉 SSE 連線
// SSE-client.js class SSEClient { constructor(url) { this.url = url; this.es = null; } subscribe(type, handler) { this.es = new EventSource(url); this.es.addEventListener("open", () => { console.log("server sent event connect created"); }); this.es.addEventListener(type, (event) => { let payload; try { payload = JSON.parse(event.data); console.log("receiving data...", payload); } catch (error) { console.error("failed to parse payload from server", error); } if (typeof handler === "function") { handler(payload); } }); this.es.addEventListener("error", () => { console.error("EventSource connection failed for subscribe.Retry"); }); } unsunscribe() { if (this.es) { this.es.close(); } } }
就這樣實現了一個簡單的 SSE
SDK。首先根據 url 引數建立一個 SSEClient
範例,當呼叫 subscribe
方法時,才會根據傳入的 url 建立 SSE
連線,然後監聽對應的事件,一旦 連線建立成功,後端向用戶端傳送資料,就可以從 handler
方法中拿到資料
這個庫僅僅實現了非常基本的功能,程式碼封裝上存在很多問題。比如 es
的事件全部雜糅在 subscribe
方法中、缺少 SSE
連線建立失敗的重試等等功能。接下來我們對剛剛實現的 SSEClient
SDK 進行優化
const defaultOptions = { retry: 5, interval: 3 * 1000, }; class SSEClient { constructor(url, options = defaultOptions) { this.url = url; this.es = null; this.options = options; this.retry = options.retry; this.timer = null; } _onOpen() { console.log("server sent event connect created"); } _onMessage(handler) { return (event) => { this.retry = options.retry; let payload; try { payload = JSON.parse(event.data); console.log("receiving data...", payload); } catch (error) { console.error("failed to parse payload from server", error); } if (typeof handler === "function") { handler(payload); } }; } _onError(type, handler) { return () => { console.error("EventSource connection failed for subscribe.Retry"); if (this.es) { this._removeAllEvent(type, handler); this.unsunscribe(); } if (this.retry > 0) { this.timer = setTimeout(() => { this.subscribe(type, handler); }, this.options.interval); } else { this.retry--; } }; } _removeAllEvent(type, handler) { this.es.removeEventListener("open", this._onOpen); this.es.removeEventListener(type, this._onMessage(handler)); this.es.removeEventListener("error", this._onError(type, handler)); } subscribe(type, handler) { this.es = new EventSource(url); this.es.addEventListener("open", this._onOpen); this.es.addEventListener(type, this._onMessage(handler)); this.es.addEventListener("error", this._onError(type, handler)); } unsunscribe() { if (this.es) { this.es.close(); this.es = null; } if (this.timer) { clearTimeout(this.timer); } } }
我們將 SSEClient
中的三個事件方法分別提取為三個私有方法,_onOpen
方法在 event 觸發 open 時呼叫,向控制檯輸出連結已經建立。 _onMessage
方法在後端向前端傳送資料時觸發,負責解析資料,並呼叫 handler
方法。_onError
方法在 SSE 發生錯誤時觸發, 會在控制檯輸出錯誤的提示,根據開發者傳入的重試次數,先關閉上一次的 SSE 連結,取消所有的事件監聽,關閉定時器, 再開啟遞迴呼叫 subscribe
方法進行重連, 一旦重連成功,重試次數恢復為設定的重試次數,如果超過重試次數依舊沒有連線成功,那麼 SSE
會徹底終止。需要開發人員排查具體原因
一個可以用在專案上的簡單 SSE
SDK 封裝完
SSE
雖然很好,但是也有它先天不足,主要問題是不能通過 headers
傳遞 Authorization token
。雖然可以把 token 放在 url 上 解決不能傳 token
的問題,但是又會引發 token
安全隱患。所以社群裡有使用 xhr
和 fetch
模擬原生 Server-sent events
的功能,解決不能 通過 headers
傳遞 Authorization token
的問題。主要有兩個第三方庫,分別是 eventsource
和 event-source-polyfill
, 下面筆者詳細講述這兩個庫的使用
此庫是 EventSource 使用者端的純 JavaScript 實現。使用方式很簡單。在專案中安裝依賴
yarn add eventsource # Or npm install eventsource
然後從 eventsource
中匯出 EventSource
類,然後範例化得到 es
範例
import EventSource from "eventsource"; const eventSourceInitDict = { headers: { authorization: "Bearer token" } }; const es = new EventSource(url, eventSourceInitDict); es.addEventListener("message", (event) => { console.log("receiving data from server:", JSON.parse(event.data)); });
eventsource
的實現用到了一些 node
標準庫。分別是 https
和 http
。 筆者將 eventsource
的部分原始碼列在下面。
// eventsource.js 原始碼如下 const https = require("https"); const http = require("http");
然而,瀏覽器環境並不支援 https
和 http
標準庫。所以當我們在瀏覽器環境中使用 eventsource
時,需要做一些額外的工作。下面以 webpack5 為例子講解解決辦法
webpack
組態檔中新增 node-polyfill-webpack-plugin
外掛yarn add node-polyfill-webpack-plugin -D
然後在 webpack
組態檔使用該外掛
// 專案中的 webpack 組態檔,比如 webpack.config.js const NodePolyfillPlugin = require("node-polyfill-webpack-plugin"); module.exports = { // Other rules... plugins: [new NodePolyfillPlugin()], };
webpack
的 callback
中對使用的庫進行單獨的設定module.exports = { // other configuration ... resolve: { fallback: { https: false, http: false, }, }, };
做完上面的步驟後,eventsource
可以在瀏覽器中正常執行
如果不想改動 webpack
的設定,那麼可以試試 event-source-polyfill
這個庫
event-source-polyfill 的使用非常簡單,使用 EventSourcePolyfill
替換原生的 EventSource
import { EventSourcePolyfill } from "event-source-polyfill"; var es = new EventSourcePolyfill(url, { headers: { authorization: "Bearer token", }, }); es.addEventListener("message", (event) => { console.log("receiving data from server:", JSON.parse(event.data)); });
eventsource
和 event-source-polyfill
只是在一定的程度上解決了 Authorization token
的問題,但它們也存在問題。 這兩個庫提供的 close
方法只能關閉處於 pending
狀態的 SSE 連線,因為 fetch 一旦從 pending
變為 resolved
或 reject
, 其結果無法改變。當頻繁的斷開 SSE 連線和建立新 SSE 連線時,舊的 SSE 連線實際上並沒有關閉,系統裡會存在多個 SSE 連線,這樣會帶來很大的效能開銷
可以將資料放入 url
中,斷開當前的 SSE 連線,根據新 url 重新建立 SSE 連線
本篇文章講述一種伺服器端向用戶端推播資訊的技術、它比 WebSocket
更簡單更輕量化,比輪詢效能好。 簡單介紹 Server-sent events
的技術原理和使用場景,並進行簡單的封裝,方便日常在專案中使用。推薦使用 eventsource
和 event-source-polyfill
第三方庫解決不能通過 headers
傳遞 Authorization token
的問題。
參考連結 Server-sent events
以上就是Server-sent events實時獲取伺服器端資料技術詳解的詳細內容,更多關於Server-sent events的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45