欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Server-sent?events實(shí)時(shí)獲取服務(wù)端數(shù)據(jù)技術(shù)詳解

 更新時(shí)間:2023年02月07日 11:15:12   作者:qinghuanI  
這篇文章主要為大家介紹了Server-sent?events實(shí)時(shí)獲取服務(wù)端數(shù)據(jù)技術(shù)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

實(shí)時(shí)獲取服務(wù)端的數(shù)據(jù),大家第一時(shí)間想到的是輪詢(xún)和 WebSocket 兩種方案,其實(shí)還有一種新方案 Server-sent events 下文簡(jiǎn)稱(chēng)(SSE)。SSE 中的數(shù)據(jù)只能由服務(wù)端推向客戶(hù)端

SSE 是基于 http 協(xié)議的服務(wù)器推送技術(shù),數(shù)據(jù)只能從服務(wù)端到客戶(hù)端。服務(wù)端把序列化后的數(shù)據(jù)發(fā)送給客戶(hù)端, 整個(gè)過(guò)程持續(xù)不斷直至連接關(guān)閉

WebSocket vs 輪詢(xún) vs SSE

下面是 WebSocket、輪詢(xún)和 SSE 的功能對(duì)比

  • SSE 和輪詢(xún)使用 HTTP 協(xié)議,現(xiàn)有的服務(wù)器軟件都支持。WebSocket 是一個(gè)獨(dú)立協(xié)議
  • SSE 屬于輕量級(jí)的 WebSocket,使用簡(jiǎn)單;WebSocket 使用相對(duì)復(fù)雜,輪詢(xún)使用簡(jiǎn)單
  • SSE 默認(rèn)支持?jǐn)嗑€重連,WebSocket 需要自己實(shí)現(xiàn)斷線重連
  • SSE 一般只用來(lái)傳送文本,二進(jìn)制數(shù)據(jù)需要編碼后傳送,WebSocket 默認(rèn)支持傳送二進(jìn)制數(shù)據(jù)
  • SSE 支持自定義發(fā)送的消息類(lèi)型
  • WebSocket 支持雙向推送消息,SSE 是單向的
  • 輪詢(xún)性能開(kāi)銷(xiāo)大、輪詢(xún)時(shí)間久導(dǎo)致客戶(hù)端及時(shí)更新數(shù)據(jù)

使用場(chǎng)景

基于服務(wù)端單向的向客戶(hù)端推送信息的特性,SSE 使用場(chǎng)景主要有

  • Sass 平臺(tái)的消息通知
  • 信息流網(wǎng)站實(shí)時(shí)更新數(shù)據(jù)

使用方式

下面講解如何在客戶(hù)端使用 SSE

  • 創(chuàng)建一個(gè) EventSource 實(shí)例,向服務(wù)器發(fā)起連接
const evtSource = new EventSource();
  • 自定義事件

對(duì)于自定義事件,服務(wù)端和客戶(hù)端一定要保持事件名一致。服務(wù)端通過(guò)自定義事件發(fā)送數(shù)據(jù), 就會(huì)觸發(fā)自定義事件。SSE 默認(rèn)支持 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);
  }
});

自定義事件的回調(diào)函數(shù)接收 event 對(duì)象,event.data 存著服務(wù)端發(fā)給客戶(hù)端的數(shù)據(jù)但是需要反序列化

可以通過(guò) Chrome Devtool 工具查看 eventsource 通信情況,如圖所示

  • 1 - 自定義事件名,服務(wù)端和客戶(hù)端需要保持一致
  • 2 - EventStream Tab,數(shù)據(jù)都在這里
  • 3 - 服務(wù)端推送給客戶(hù)端的數(shù)據(jù)
  • 錯(cuò)誤處理

如果連接發(fā)生錯(cuò)誤,就會(huì)觸發(fā) error 事件

evtSource.addEventListener("error", (err) => {
  console.error("EventSource failed:", err);
});
  • 關(guān)閉連接

SSE 提供 close 方法,用來(lái)關(guān)閉 SSE 連接

evtSource.close();

瀏覽器兼容性

通過(guò) caniuse 查看 SSE 瀏覽器兼容性,如圖所示

除了 IE 瀏覽器不支持,其它現(xiàn)代瀏覽器都支持,所以放心大膽在項(xiàng)目中使用 SSE

簡(jiǎn)單封裝

在平常的工作中,每次寫(xiě) SSE 的事件監(jiān)聽(tīng)和錯(cuò)誤處理會(huì)很麻煩。多個(gè)業(yè)務(wù)場(chǎng)景需要使用 SSE 時(shí),就需要對(duì) SSE 進(jìn)行封裝。接下來(lái)我們嘗試封裝一個(gè)簡(jiǎn)單的 SSE SDK,方便在項(xiàng)目中使用

當(dāng)我們決定寫(xiě) SSE 的 SDK 時(shí),首先想到使用面向?qū)ο螅∣OP)進(jìn)行封裝。根據(jù) SSE 的特性,那么庫(kù)需要實(shí)現(xiàn) subscribeunsubscribe 兩個(gè)方法。通過(guò)確定 SSE 庫(kù)使用方式,根據(jù)使用方式確定 SDK 的實(shí)現(xiàn)。我們可以在代碼中這樣使用,如下所示

// SSESdk 實(shí)例化
const SSE = new SSESdk(url, options);
// 訂閱來(lái)自服務(wù)端的消息
SSE.subscribe("message", (data) => {
  console.log("receive message from server", data);
});
// 取消訂閱
SSE.unsuscribe();

我們要封裝的庫(kù)對(duì)外僅僅提供 subscribeunsubscribe 兩個(gè) Api,非常方便開(kāi)發(fā)人員使用。 subscribe 用來(lái)訂閱來(lái)自服務(wù)端的消息, unsubscribe 用來(lái)取消訂閱,關(guān)閉 SSE 連接,通過(guò)使用形式可以看出,使用 ES6 中的類(lèi)語(yǔ)法。接下來(lái)我們先確定 SSE SDK 的大體結(jié)構(gòu)

class SSEClient {
  constructor() {}
  subscribe(type, handler) {}
  unsunscribe() {}
}

SSEClient 類(lèi)中有三個(gè)方法需要實(shí)現(xiàn),通過(guò) constructor 接受可配置的參數(shù),比如 SSE 建立連接失敗后的重試次數(shù)和重試時(shí)間。 subscribe 接收一個(gè)與后端保持一致的事件名和一個(gè)回調(diào)函數(shù)。unsunscribe 不需要傳遞任何參數(shù),調(diào)用 unsunscribe 方法關(guān)閉 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();
    }
  }
}

就這樣實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 SSE SDK。首先根據(jù) url 參數(shù)創(chuàng)建一個(gè) SSEClient 實(shí)例,當(dāng)調(diào)用 subscribe 方法時(shí),才會(huì)根據(jù)傳入的 url 建立 SSE 連接,然后監(jiān)聽(tīng)對(duì)應(yīng)的事件,一旦 連接建立成功,后端向客戶(hù)端發(fā)送數(shù)據(jù),就可以從 handler 方法中拿到數(shù)據(jù)

這個(gè)庫(kù)僅僅實(shí)現(xiàn)了非?;镜墓δ埽a封裝上存在很多問(wèn)題。比如 es 的事件全部雜糅在 subscribe 方法中、缺少 SSE 連接建立失敗的重試等等功能。接下來(lái)我們對(duì)剛剛實(shí)現(xiàn)的 SSEClient SDK 進(jìn)行優(yōu)化

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 中的三個(gè)事件方法分別提取為三個(gè)私有方法,_onOpen 方法在 event 觸發(fā) open 時(shí)調(diào)用,向控制臺(tái)輸出鏈接已經(jīng)創(chuàng)建。 _onMessage 方法在后端向前端發(fā)送數(shù)據(jù)時(shí)觸發(fā),負(fù)責(zé)解析數(shù)據(jù),并調(diào)用 handler 方法。_onError 方法在 SSE 發(fā)生錯(cuò)誤時(shí)觸發(fā), 會(huì)在控制臺(tái)輸出錯(cuò)誤的提示,根據(jù)開(kāi)發(fā)者傳入的重試次數(shù),先關(guān)閉上一次的 SSE 鏈接,取消所有的事件監(jiān)聽(tīng),關(guān)閉定時(shí)器, 再開(kāi)啟遞歸調(diào)用 subscribe 方法進(jìn)行重連, 一旦重連成功,重試次數(shù)恢復(fù)為設(shè)定的重試次數(shù),如果超過(guò)重試次數(shù)依舊沒(méi)有連接成功,那么 SSE 會(huì)徹底終止。需要開(kāi)發(fā)人員排查具體原因

一個(gè)可以用在項(xiàng)目上的簡(jiǎn)單 SSE SDK 封裝完

第三方庫(kù)

SSE 雖然很好,但是也有它先天不足,主要問(wèn)題是不能通過(guò) headers 傳遞 Authorization token。雖然可以把 token 放在 url 上 解決不能傳 token 的問(wèn)題,但是又會(huì)引發(fā) token 安全隱患。所以社區(qū)里有使用 xhrfetch 模擬原生 Server-sent events 的功能,解決不能 通過(guò) headers 傳遞 Authorization token 的問(wèn)題。主要有兩個(gè)第三方庫(kù),分別是 eventsourceevent-source-polyfill, 下面筆者詳細(xì)講述這兩個(gè)庫(kù)的使用

eventsource

此庫(kù)是 EventSource 客戶(hù)端的純 JavaScript 實(shí)現(xiàn)。使用方式很簡(jiǎn)單。在項(xiàng)目中安裝依賴(lài)

yarn add eventsource
# Or npm install eventsource

然后從 eventsource 中導(dǎo)出 EventSource 類(lèi),然后實(shí)例化得到 es 實(shí)例

import EventSource from "eventsource";
const eventSourceInitDict = { headers: { authorization: "Bearer token" } };
const es = new EventSource(url, eventSourceInitDict);
es.addEventListener("message", (event) =&gt; {
  console.log("receiving data from server:", JSON.parse(event.data));
});

eventsource 的實(shí)現(xiàn)用到了一些 node 標(biāo)準(zhǔn)庫(kù)。分別是 httpshttp。 筆者將 eventsource 的部分源碼列在下面。

// eventsource.js 源碼如下
const https = require("https");
const http = require("http");

然而,瀏覽器環(huán)境并不支持 httpshttp 標(biāo)準(zhǔn)庫(kù)。所以當(dāng)我們?cè)跒g覽器環(huán)境中使用 eventsource 時(shí),需要做一些額外的工作。下面以 webpack5 為例子講解解決辦法

  • 需要在 webpack 配置文件中添加 node-polyfill-webpack-plugin 插件
yarn add node-polyfill-webpack-plugin -D

然后在 webpack 配置文件使用該插件

// 項(xiàng)目中的 webpack 配置文件,比如 webpack.config.js
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
module.exports = {
  // Other rules...
  plugins: [new NodePolyfillPlugin()],
};
  • 或者在 webpackcallback 中對(duì)使用的庫(kù)進(jìn)行單獨(dú)的配置
module.exports = {
  // other configuration ...
  resolve: {
    fallback: {
      https: false,
      http: false,
    },
  },
};

做完上面的步驟后,eventsource 可以在瀏覽器中正常運(yùn)行

如果不想改動(dòng) webpack 的配置,那么可以試試 event-source-polyfill 這個(gè)庫(kù)

event-source-polyfill

event-source-polyfill 的使用非常簡(jiǎn)單,使用 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));
});

不足之處

eventsourceevent-source-polyfill 只是在一定的程度上解決了 Authorization token 的問(wèn)題,但它們也存在問(wèn)題。 這兩個(gè)庫(kù)提供的 close 方法只能關(guān)閉處于 pending 狀態(tài)的 SSE 連接,因?yàn)?fetch 一旦從 pending 變?yōu)?resolvedreject, 其結(jié)果無(wú)法改變。當(dāng)頻繁的斷開(kāi) SSE 連接和建立新 SSE 連接時(shí),舊的 SSE 連接實(shí)際上并沒(méi)有關(guān)閉,系統(tǒng)里會(huì)存在多個(gè) SSE 連接,這樣會(huì)帶來(lái)很大的性能開(kāi)銷(xiāo)

FAQ

  • SSE 不能向服務(wù)端發(fā)送數(shù)據(jù)?

可以將數(shù)據(jù)放入 url 中,斷開(kāi)當(dāng)前的 SSE 連接,根據(jù)新 url 重新建立 SSE 連接

總結(jié)

本篇文章講述一種服務(wù)端向客戶(hù)端推送信息的技術(shù)、它比 WebSocket 更簡(jiǎn)單更輕量化,比輪詢(xún)性能好。 簡(jiǎn)單介紹 Server-sent events 的技術(shù)原理和使用場(chǎng)景,并進(jìn)行簡(jiǎn)單的封裝,方便日常在項(xiàng)目中使用。推薦使用 eventsourceevent-source-polyfill 第三方庫(kù)解決不能通過(guò) headers 傳遞 Authorization token 的問(wèn)題。

參考鏈接 Server-sent events

以上就是Server-sent events實(shí)時(shí)獲取服務(wù)端數(shù)據(jù)技術(shù)詳解的詳細(xì)內(nèi)容,更多關(guān)于Server-sent events的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JavaScript?ES6語(yǔ)法中l(wèi)et,const?,var?的區(qū)別

    JavaScript?ES6語(yǔ)法中l(wèi)et,const?,var?的區(qū)別

    這篇文章主要為大家介紹了JavaScript中l(wèi)et,const?,var?的區(qū)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-01-01
  • JavaScript原型Prototype詳情

    JavaScript原型Prototype詳情

    這篇文章主要介紹了JavaScript原型Prototype,在JavaScript中,函數(shù)是一個(gè)包含屬性和方法的Function類(lèi)型的對(duì)象。而原型(Prototype?)就是Function類(lèi)型對(duì)象的一個(gè)屬性。具體內(nèi)容需要的朋友可以參考下面文章的介紹
    2021-12-12
  • 微信小程序左滑刪除效果的實(shí)現(xiàn)代碼

    微信小程序左滑刪除效果的實(shí)現(xiàn)代碼

    這篇文章主要介紹了微信小程序左滑刪除效果的實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-02-02
  • 一文詳解js基本類(lèi)型與引用類(lèi)型的區(qū)別

    一文詳解js基本類(lèi)型與引用類(lèi)型的區(qū)別

    這篇文章主要為大家介紹了js基本類(lèi)型與引用類(lèi)型的區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • rollup打包引發(fā)對(duì)JS模塊循環(huán)引用思考

    rollup打包引發(fā)對(duì)JS模塊循環(huán)引用思考

    這篇文章主要為大家介紹了rollup打包引發(fā)的對(duì)JS模塊循環(huán)引用的思考,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 類(lèi)和原型的設(shè)計(jì)模式之復(fù)制與委托差異

    類(lèi)和原型的設(shè)計(jì)模式之復(fù)制與委托差異

    這篇文章主要為大家介紹了類(lèi)和原型的設(shè)計(jì)模式之復(fù)制與委托差異詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • JavaScript loader原理簡(jiǎn)單總結(jié)示例解析

    JavaScript loader原理簡(jiǎn)單總結(jié)示例解析

    這篇文章主要為大家介紹了JavaScript loader原理簡(jiǎn)單總結(jié)示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • ECMAScript 6數(shù)值擴(kuò)展實(shí)例詳解

    ECMAScript 6數(shù)值擴(kuò)展實(shí)例詳解

    這篇文章主要為大家介紹了ECMAScript6數(shù)值擴(kuò)展實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 微信小程序 (十八)picker組件詳細(xì)介紹

    微信小程序 (十八)picker組件詳細(xì)介紹

    這篇文章主要介紹了微信小程序 picker組件詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下
    2016-09-09
  • 微信小程序 底部導(dǎo)航欄目開(kāi)發(fā)資料

    微信小程序 底部導(dǎo)航欄目開(kāi)發(fā)資料

    這篇文章主要介紹了微信小程序 底部導(dǎo)航欄目開(kāi)發(fā)資料的相關(guān)資料,微信小程序底部想要有一個(gè)漂亮的導(dǎo)航欄目,不知道怎么制作,于是百度找到了本篇文章,分享給大家,需要的朋友可以參考下
    2016-12-12

最新評(píng)論