使用伺服器傳送事件

Baseline 廣泛可用 *

此特性已相當成熟,可在許多裝置和瀏覽器版本上使用。自 ⁨2020 年 1 月⁩ 起,所有主流瀏覽器均已支援。

* 此特性的某些部分可能存在不同級別的支援。

開發使用 伺服器傳送事件 的 Web 應用程式很簡單。您需要在伺服器端編寫一些程式碼來將事件流式傳輸到前端,但在處理傳入事件的某些方面,客戶端程式碼與 WebSocket 幾乎相同。這是一個單向連線,因此您無法從客戶端向伺服器傳送事件。

從伺服器接收事件

伺服器傳送事件 API 包含在 EventSource 介面中。

建立 EventSource 例項

要開啟與伺服器的連線以開始從中接收事件,請使用生成事件的指令碼的 URL 建立一個新的 EventSource 物件。例如:

js
const evtSource = new EventSource("sse-demo.php");

如果事件生成器指令碼託管在不同的域上,則應使用 URL 和選項字典建立一個新的 EventSource 物件。例如,假設客戶端指令碼位於 example.com

js
const evtSource = new EventSource("//api.example.com/sse-demo.php", {
  withCredentials: true,
});

監聽 message 事件

從伺服器傳送的、沒有 event 欄位的訊息將作為 message 事件接收。要接收訊息事件,請為 message 事件附加一個處理程式:

js
evtSource.onmessage = (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");

  newElement.textContent = `message: ${event.data}`;
  eventList.appendChild(newElement);
};

此程式碼監聽傳入的訊息事件,並將訊息文字追加到文件 HTML 中的列表中。

監聽自定義事件

伺服器傳送的、具有 event 欄位的訊息將作為在 event 中命名的事件接收。例如:

js
evtSource.addEventListener("ping", (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");
  const time = JSON.parse(event.data).time;
  newElement.textContent = `ping at ${time}`;
  eventList.appendChild(newElement);
});

每當伺服器傳送 event 欄位設定為 ping 的訊息時,都會呼叫此程式碼;然後它會解析 data 欄位中的 JSON 並輸出該資訊。

警告:不透過 HTTP/2 使用時,SSE 會受到最大開啟連線數的限制,這在開啟多個選項卡時可能會特別痛苦,因為限制是每個瀏覽器並且設定為一個非常小的數字(6)。這個問題已在 ChromeFirefox 中被標記為“不會修復”。此限制是每個瀏覽器 + 域,這意味著您可以開啟 6 個到 www.example1.com 的 SSE 連線,以及另外 6 個到 www.example2.com 的 SSE 連線(根據 Stack Overflow)。使用 HTTP/2 時,同時HTTP 流的最大數量在伺服器和客戶端之間協商(預設為 100)。

從伺服器傳送事件

傳送事件的伺服器端指令碼需要使用 text/event-stream MIME 型別進行響應。每個通知都作為一個文字塊傳送,並以一對換行符終止。有關事件流格式的詳細資訊,請參閱 事件流格式

我們在此示例中使用的 PHP 程式碼如下:

php
date_default_timezone_set("America/New_York");
header("X-Accel-Buffering: no");
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");

$counter = rand(1, 10);
while (true) {
  // Every second, send a "ping" event.

  echo "event: ping\n";
  $curDate = date(DATE_ISO8601);
  echo 'data: {"time": "' . $curDate . '"}';
  echo "\n\n";

  // Send a simple message at random intervals.

  $counter--;

  if (!$counter) {
    echo 'data: This is a message at time ' . $curDate . "\n\n";
    $counter = rand(1, 10);
  }

  if (ob_get_contents()) {
      ob_end_flush();
  }
  flush();

  // Break the loop if the client aborted the connection (closed the page)

  if (connection_aborted()) break;

  sleep(1);
}

上面的程式碼每秒生成一個事件,事件型別為“ping”。每個事件的資料是一個 JSON 物件,其中包含與事件生成時間對應的 ISO 8601 時間戳。在隨機間隔,會發送一個簡單的訊息(沒有事件型別)。迴圈將獨立於連線狀態繼續執行,因此包含一個檢查,如果連線已關閉(例如,客戶端關閉頁面),則中斷迴圈。

注意:您可以在 GitHub 上找到使用本文件所示程式碼的完整示例 — 請參閱 使用 PHP 的簡單 SSE 演示

錯誤處理

如果伺服器響應了 error 鍵(例如,JSON.parse(event.data.error) 或發生其他問題(例如網路超時或與 訪問控制相關的問題)),則會生成一個錯誤事件。您可以透過實現 EventSource 物件上的 onerror 回撥來以程式設計方式處理此問題。

js
evtSource.onerror = (err) => {
  console.error("EventSource failed:", err);
};

關閉事件流

預設情況下,如果客戶端和伺服器之間的連線關閉,連線將重新啟動。透過 .close() 方法終止連線。

js
evtSource.close();

事件流格式

事件流是一個簡單的文字資料流,必須使用 UTF-8 進行編碼。事件流中的訊息由一對換行符分隔。行首的冒號本質上是註釋,會被忽略。

注意:註釋行可用於防止連線超時;伺服器可以定期傳送註釋以保持連線活躍。

每條訊息由一行或多行文字組成,列出該訊息的欄位。每個欄位由欄位名、冒號和欄位值的文字資料組成。

欄位

每條收到的訊息都包含以下欄位的某種組合,每行一個:

event

一個字串,標識事件的型別。如果指定了此欄位,則會在瀏覽器上為指定事件名的監聽器分派一個事件;網站原始碼應使用 addEventListener() 來監聽命名事件。如果沒有為訊息指定事件名,則會呼叫 onmessage 處理程式。

data

訊息的資料欄位。當 EventSource 收到多個以 data: 開頭的連續行時,它會將它們連線起來,並在它們之間插入一個換行符。尾隨的換行符會被刪除。

id

事件 ID,用於設定 EventSource 物件的最後事件 ID 值。

retry

重新連線時間。如果與伺服器的連線丟失,瀏覽器將在嘗試重新連線之前等待指定的時間。這必須是一個整數,指定以毫秒為單位的重新連線時間。如果指定了非整數值,則忽略該欄位。

所有其他欄位名都將被忽略。

注意:如果一行不包含冒號,則整行被視為欄位名,其值為一個空字串。

示例

僅資料訊息

在以下示例中,傳送了三條訊息。第一條只是一個註釋,因為它以冒號開頭。如前所述,這可以作為保持活動機制,如果訊息可能不會定期傳送。

第二條訊息包含一個值為“some text”的資料欄位。第三條訊息包含一個值為“another message\nwith two lines”的資料欄位。請注意值中的換行特殊字元。

bash
: this is a test stream

data: some text

data: another message
data: with two lines

命名事件

此示例傳送命名事件。每個事件都有一個由 event 欄位指定的事件名,以及一個 data 欄位,其值為適當的 JSON 字串,包含客戶端處理事件所需的資料。當然,data 欄位可以是任何字串資料;不一定是 JSON。

bash
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}

event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}

event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}

混合搭配

您不必只使用未命名的訊息或型別化事件;您可以在單個事件流中混合使用它們。

bash
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

data: Here's a system message of some kind that will get used
data: to accomplish some task.

event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}

瀏覽器相容性