--- title: 使用 server-sent 事件 slug: Web/API/Server-sent_events/Using_server-sent_events tags: - Advanced - Communication - DOM - EventSource - Guide - SSE - Server Sent Events - Server-Sent-Event translation_of: Web/API/Server-sent_events/Using_server-sent_events ---
{{DefaultAPISidebar("Server Sent Events")}}
開發一個使用 server-sent 事件的網頁應用程式很簡單。在伺服器端只需要一些的程式碼與網頁串流事件,而客戶端這邊的處理進入事件的部分幾乎跟 websockets 一樣。這是一種單向的連線,所以你無法從客戶端向伺服器傳送事件。
server-sent event API 包含在 {{domxref("EventSource")}} 介面;為了與伺服器端開啟連線並接收事件,需要建立帶有產生事件 script URL 的 {{domxref("EventSource")}} 物件。例如:
const evtSource = new EventSource("ssedemo.php");
如果事件產生的 script 在不同源的伺服器上,在建立 {{domxref("EventSource")}} 物件時需要同時提供 URL 和第二個參數作為選項設定。假設客戶端的 script 伺服於 example.com
:
const evtSource = new EventSource("//api.example.com/ssedemo.php", { withCredentials: true } );
當你完成初始化事件來源後,你就可以透過新增 {{event("message")}} 事件的處理器來開始監聽來自伺服器的訊息:
evtSource.onmessage = function(event) { const newElement = document.createElement("li"); const eventList = document.getElementById("list"); newElement.innerHTML = "message: " + event.data; eventList.appendChild(newElement); }
上述的程式碼會監聽進入的訊息(這裡來自伺服器的通知沒有指明 event
欄位,所以統一用 onmessage
處理即可)並且把訊息的文字附加到 document 的清單。
你也可以利用 addEventListener()
監聽事件:
evtSource.addEventListener("ping", function(event) { const newElement = document.createElement("li"); const time = JSON.parse(event.data).time; newElement.innerHTML = "ping at " + time; eventList.appendChild(newElement); });
上述的程式碼大同小異,不同之處在於若伺服器傳送了 event
欄位值為「ping」的訊息時它就會把 data
欄位的值解析為 JSON 並輸出到畫面上。
當連線不是透過 HTTP/2 時,SSE 會受到最大連線數限制所苦,尤其當開啟多個分頁。每個瀏覽器有自己的限制數而且被限制在很低的數量(6)。這個問題已經被 Chrome 和 Firefox 標註為「Won't fix」(不修復)。限制是基於每個瀏覽器 + 網域,也就是說你可以針對 www.example1.com 網域在所有的分頁中開啟六個 SSE 連線,另一個網域 www.example2.com 也可以開啟六個(根據 Stackoverflow)。當使用 HTTP/2 時最大同時 HTTP streams 連線數是由伺服器和客戶端之間協調(預設 100)。
由伺服器端所發送的事件需要使用 text/event-stream
的 MIME 類型回應。每一個通知皆由一組文字組成並由一對換行結尾。如何處理事件串流的格式,請參考 {{ anch("Event stream format") }} 。
下面是一個 {{Glossary("PHP")}} 範例:
date_default_timezone_set("America/New_York"); header("Cache-Control: no-cache"); header("Content-Type: text/event-stream"); $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); } 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 文章中找到包含本文所使用程式碼的完整範例 —— 參考 Simple SSE demo using PHP.
當錯誤發生時(譬如網路逾時或有關存取控制的問題)會產生錯誤事件。你可以透過對 EventSource
物件實作 onerror
回呼的方式採取程式化的處理:
evtSource.onerror = function(err) { console.error("EventSource failed:", err); };
預設的情況下,如果客戶端和伺服器的連線關閉則連線會被重啟。連線的關閉會伴隨著 .close()
方法的執行。
evtSource.close();
事件串流是個簡易的文字資料串流,內容必須以 UTF-8 格式編碼。在事件串流中,不同的訊息以一對換行符號做區隔。若要撰寫註解,則要在該行的開頭加上冒號(:)。
每一個訊息是由一到多列的欄位所組成的文字。每個欄位依序由欄位的名稱、冒號、該欄位的文字內容所組合而成。
每隔訊息皆可以由下列的欄位組合而成,每個欄位以換行做為區隔:
event
addEventListener()
來監聽已命名的事件。 onmessage
處理器只有在訊息沒有指定事件名稱時才會被呼叫。data
data:
開頭的內容;它會串接這些內容並為每一列插入一個換行字元。最後的換行會被移除。id
retry
除上述的幾個欄位,其他欄位均會被忽略。
在下列的範例中,共發送了三個訊息。第一個是註解,因其以冒號開頭。如之前提到的,對不會持續發送訊息的情境下,這將有助於維持連線的存續。
第二則訊息包含了 data 欄位及「some text」的值。第三則訊息包含了 data 欄位及「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."}
EventSource
{{Compat("api.EventSource")}}