使用伺服器傳送事件
Baseline 廣泛可用 *
開發使用 伺服器傳送事件 的 Web 應用程式很簡單。您需要在伺服器端編寫一些程式碼來將事件流式傳輸到前端,但在處理傳入事件的某些方面,客戶端程式碼與 WebSocket 幾乎相同。這是一個單向連線,因此您無法從客戶端向伺服器傳送事件。
從伺服器接收事件
伺服器傳送事件 API 包含在 EventSource 介面中。
建立 EventSource 例項
要開啟與伺服器的連線以開始從中接收事件,請使用生成事件的指令碼的 URL 建立一個新的 EventSource 物件。例如:
const evtSource = new EventSource("sse-demo.php");
如果事件生成器指令碼託管在不同的域上,則應使用 URL 和選項字典建立一個新的 EventSource 物件。例如,假設客戶端指令碼位於 example.com
const evtSource = new EventSource("//api.example.com/sse-demo.php", {
withCredentials: true,
});
監聽 message 事件
從伺服器傳送的、沒有 event 欄位的訊息將作為 message 事件接收。要接收訊息事件,請為 message 事件附加一個處理程式:
evtSource.onmessage = (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
newElement.textContent = `message: ${event.data}`;
eventList.appendChild(newElement);
};
此程式碼監聽傳入的訊息事件,並將訊息文字追加到文件 HTML 中的列表中。
監聽自定義事件
伺服器傳送的、具有 event 欄位的訊息將作為在 event 中命名的事件接收。例如:
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)。這個問題已在 Chrome 和 Firefox 中被標記為“不會修復”。此限制是每個瀏覽器 + 域,這意味著您可以開啟 6 個到 www.example1.com 的 SSE 連線,以及另外 6 個到 www.example2.com 的 SSE 連線(根據 Stack Overflow)。使用 HTTP/2 時,同時HTTP 流的最大數量在伺服器和客戶端之間協商(預設為 100)。
從伺服器傳送事件
傳送事件的伺服器端指令碼需要使用 text/event-stream MIME 型別進行響應。每個通知都作為一個文字塊傳送,並以一對換行符終止。有關事件流格式的詳細資訊,請參閱 事件流格式。
我們在此示例中使用的 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 回撥來以程式設計方式處理此問題。
evtSource.onerror = (err) => {
console.error("EventSource failed:", err);
};
關閉事件流
預設情況下,如果客戶端和伺服器之間的連線關閉,連線將重新啟動。透過 .close() 方法終止連線。
evtSource.close();
事件流格式
事件流是一個簡單的文字資料流,必須使用 UTF-8 進行編碼。事件流中的訊息由一對換行符分隔。行首的冒號本質上是註釋,會被忽略。
注意:註釋行可用於防止連線超時;伺服器可以定期傳送註釋以保持連線活躍。
每條訊息由一行或多行文字組成,列出該訊息的欄位。每個欄位由欄位名、冒號和欄位值的文字資料組成。
欄位
每條收到的訊息都包含以下欄位的某種組合,每行一個:
event-
一個字串,標識事件的型別。如果指定了此欄位,則會在瀏覽器上為指定事件名的監聽器分派一個事件;網站原始碼應使用
addEventListener()來監聽命名事件。如果沒有為訊息指定事件名,則會呼叫onmessage處理程式。 data-
訊息的資料欄位。當
EventSource收到多個以data:開頭的連續行時,它會將它們連線起來,並在它們之間插入一個換行符。尾隨的換行符會被刪除。 id-
事件 ID,用於設定
EventSource物件的最後事件 ID 值。 retry-
重新連線時間。如果與伺服器的連線丟失,瀏覽器將在嘗試重新連線之前等待指定的時間。這必須是一個整數,指定以毫秒為單位的重新連線時間。如果指定了非整數值,則忽略該欄位。
所有其他欄位名都將被忽略。
注意:如果一行不包含冒號,則整行被視為欄位名,其值為一個空字串。
示例
僅資料訊息
在以下示例中,傳送了三條訊息。第一條只是一個註釋,因為它以冒號開頭。如前所述,這可以作為保持活動機制,如果訊息可能不會定期傳送。
第二條訊息包含一個值為“some text”的資料欄位。第三條訊息包含一個值為“another message\nwith two lines”的資料欄位。請注意值中的換行特殊字元。
: this is a test stream
data: some text
data: another message
data: with two lines
命名事件
此示例傳送命名事件。每個事件都有一個由 event 欄位指定的事件名,以及一個 data 欄位,其值為適當的 JSON 字串,包含客戶端處理事件所需的資料。當然,data 欄位可以是任何字串資料;不一定是 JSON。
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."}
混合搭配
您不必只使用未命名的訊息或型別化事件;您可以在單個事件流中混合使用它們。
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."}
瀏覽器相容性
載入中…