Javascript Server Sent Events

2023-02-17 10:57 更新

Server-Sent Events 規(guī)范描述了一個內建的類 ?EventSource?,它能保持與服務器的連接,并允許從中接收事件。

與 WebSocket 類似,其連接是持久的。

但是兩者之間有幾個重要的區(qū)別:

WebSocket EventSource
雙向:客戶端和服務端都能交換消息 單向:僅服務端能發(fā)送消息
二進制和文本數(shù)據(jù) 僅文本數(shù)據(jù)
WebSocket 協(xié)議 常規(guī) HTTP 協(xié)議

與 WebSocket 相比,EventSource 是與服務器通信的一種不那么強大的方式。

我們?yōu)槭裁匆褂盟?

主要原因:簡單。在很多應用中,WebSocket 有點大材小用。

我們需要從服務器接收一個數(shù)據(jù)流:可能是聊天消息或者市場價格等。這正是 EventSource 所擅長的。它還支持自動重新連接,而在 WebSocket 中這個功能需要我們手動實現(xiàn)。此外,它是一個普通的舊的 HTTP,不是一個新協(xié)議。

獲取消息

要開始接收消息,我們只需要創(chuàng)建 new EventSource(url) 即可。

瀏覽器將會連接到 url 并保持連接打開,等待事件。

服務器響應狀態(tài)碼應該為 200,header 為 Content-Type: text/event-stream,然后保持此連接并以一種特殊的格式寫入消息,就像這樣:

data: Message 1

data: Message 2

data: Message 3
data: of two lines
  • ?data:? 后為消息文本,冒號后面的空格是可選的。
  • 消息以雙換行符 ?\n\n? 分隔。
  • 要發(fā)送一個換行 ?\n?,我們可以在要換行的位置立即再發(fā)送一個 ?data:?(上面的第三條消息)。

在實際開發(fā)中,復雜的消息通常是用 JSON 編碼后發(fā)送。換行符在其中編碼為 \n,因此不需要多行 data: 消息。

例如:

data: {"user":"John","message":"First line\n Second line"}

……因此,我們可以假設一個 data: 只保存了一條消息。

對于每個這樣的消息,都會生成 message 事件:

let eventSource = new EventSource("/events/subscribe");

eventSource.onmessage = function(event) {
  console.log("New message", event.data);
  // 對于上面的數(shù)據(jù)流將打印三次
};

// 或 eventSource.addEventListener('message', ...)

跨源請求

EventSource 支持跨源請求,就像 fetch 和任何其他網(wǎng)絡方法。我們可以使用任何 URL:

let source = new EventSource("https://another-site.com/events");

遠程服務器將會獲取到 Origin header,并且必須以 Access-Control-Allow-Origin 響應來處理。

要傳遞憑證(credentials),我們應該設置附加選項 withCredentials,就像這樣:

let source = new EventSource("https://another-site.com/events", {
  withCredentials: true
});

更多關于跨源 header 的詳細內容,請參見 Fetch:跨源請求。

重新連接

創(chuàng)建之后,new EventSource 連接到服務器,如果連接斷開 —— 則重新連接。

這非常方便,我們不用去關心重新連接的事情。

每次重新連接之間有一點小的延遲,默認為幾秒鐘。

服務器可以使用 retry: 來設置需要的延遲響應時間(以毫秒為單位)。

retry: 15000
data: Hello, I set the reconnection delay to 15 seconds

retry: 既可以與某些數(shù)據(jù)一起出現(xiàn),也可以作為獨立的消息出現(xiàn)。

在重新連接之前,瀏覽器需要等待那么多毫秒。甚至更長,例如,如果瀏覽器知道(從操作系統(tǒng))此時沒有網(wǎng)絡連接,它會等到連接出現(xiàn),然后重試。

  • 如果服務器想要瀏覽器停止重新連接,那么它應該使用 HTTP 狀態(tài)碼 204 進行響應。
  • 如果瀏覽器想要關閉連接,則應該調用 ?eventSource.close()?:
let eventSource = new EventSource(...);

eventSource.close();

并且,如果響應具有不正確的 Content-Type 或者其 HTTP 狀態(tài)碼不是 301,307,200 和 204,則不會進行重新連接。在這種情況下,將會發(fā)出 "error" 事件,并且瀏覽器不會重新連接。

請注意:

當連接最終被關閉時,就無法“重新打開”它。如果我們想要再次連接,只需要創(chuàng)建一個新的 EventSource。

消息 id

當一個連接由于網(wǎng)絡問題而中斷時,客戶端和服務器都無法確定哪些消息已經(jīng)收到哪些沒有收到。

為了正確地恢復連接,每條消息都應該有一個 ?id? 字段,就像這樣:

data: Message 1
id: 1

data: Message 2
id: 2

data: Message 3
data: of two lines
id: 3

當收到具有 id 的消息時,瀏覽器會:

  • 將屬性 ?eventSource.lastEventId? 設置為其值。
  • 重新連接后,發(fā)送帶有 ?id? 的 header ?Last-Event-ID?,以便服務器可以重新發(fā)送后面的消息。

把 ?id:? 放在 ?data:? 后

請注意:id 被服務器附加到 data 消息后,以確保在收到消息后 lastEventId 會被更新。

連接狀態(tài):readyState

EventSource 對象有 readyState 屬性,該屬性具有下列值之一:

EventSource.CONNECTING = 0; // 連接中或者重連中
EventSource.OPEN = 1;       // 已連接
EventSource.CLOSED = 2;     // 連接已關閉

對象創(chuàng)建完成或者連接斷開后,它始終是 EventSource.CONNECTING(等于 0)。

我們可以查詢該屬性以了解 EventSource 的狀態(tài)。

Event 類型

默認情況下 ?EventSource? 對象生成三個事件:

  • ?message? —— 收到消息,可以用 ?event.data? 訪問。
  • ?open? —— 連接已打開。
  • ?error? —— 無法建立連接,例如,服務器返回 HTTP 500 狀態(tài)碼。

服務器可以在事件開始時使用 event: ... 指定另一種類型事件。

例如:

event: join
data: Bob

data: Hello

event: leave
data: Bob

要處理自定義事件,我們必須使用 addEventListener 而非 onmessage

eventSource.addEventListener('join', event => {
  alert(`Joined ${event.data}`);
});

eventSource.addEventListener('message', event => {
  alert(`Said: ${event.data}`);
});

eventSource.addEventListener('leave', event => {
  alert(`Left ${event.data}`);
});

完整示例

服務器依次發(fā)送 1,2,3,最后發(fā)送 bye 并斷開連接。

然后瀏覽器會自動重新連接。

下載鏈接

總結

?EventSource? 對象自動建立一個持久的連接,并允許服務器通過這個連接發(fā)送消息。

它提供了:

  • 在可調的 ?retry? 超時內自動重新連接。
  • 用于恢復事件的消息 id,重新連接后,最后接收到的標識符被在 ?Last-Event-ID? header 中發(fā)送出去。
  • 當前狀態(tài)位于 ?readyState? 屬性中。

這使得 EventSource 成為 WebSocket 的一個可行的替代方案,因為 WebSocket 更底層(low-level),且缺乏這樣的內建功能(盡管它們可以被實現(xiàn))。

在很多實際應用中,EventSource 的功能就已經(jīng)夠用了。

EventSource 在所有現(xiàn)代瀏覽器(除了 IE)中都得到了支持。

語法:

let source = new EventSource(url, [credentials]);

第二個參數(shù)只有一個可選項:{ withCredentials: true },它允許發(fā)送跨源憑證。

總體跨源安全性與 fetch 以及其他網(wǎng)絡方法相同。

EventSource 對象的屬性

?readyState ?

當前連接狀態(tài):為 ?EventSource.CONNECTING (=0)?,?EventSource.OPEN (=1)?,?EventSource.CLOSED (=2)? 三者之一。

?lastEventId ?

最后接收到的 ?id?。重新連接后,瀏覽器在 header ?Last-Event-ID? 中發(fā)送此 id。

EventSource 對象的方法

?close() ?

關閉連接。

EventSource 對象的事件

?message ?

接收到的消息,消息數(shù)據(jù)在 ?event.data? 中。

?open ?

連接已建立。

?error ?

如果發(fā)生錯誤,包括連接丟失(將會自動重連)以及其他致命錯誤。我們可以檢查 ?readyState? 以查看是否正在嘗試重新連接。

服務器可以在 event: 中設置自定義事件名稱。應該使用 addEventListener 來處理此類事件,而不是使用 on<event>

服務器響應格式

服務器發(fā)送由 \n\n 分隔的消息。

一條消息可能有以下字段:

  • ?data:? —— 消息體(body),一系列多個 ?data? 被解釋為單個消息,各個部分之間由 ?\n? 分隔。
  • ?id:? —— 更新 ?lastEventId?,重連時以 ?Last-Event-ID? 發(fā)送此 id。
  • ?retry:? —— 建議重連的延遲,以 ms 為單位。無法通過 JavaScript 進行設置。
  • ?event:? —— 事件名,必須在 ?data:? 之前。

一條消息可以按任何順序包含一個或多個字段,但是 id: 通常排在最后。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號