伺服器傳送事件 (SSE) 會使用 HTTP 連線,從伺服器將自動更新傳送給用戶端。連線建立後,伺服器即可啟動資料傳輸程序。
您可以使用 SSE,從網頁應用程式傳送推播通知。SSE 會以一個方向傳送資訊,因此您將不會收到來自用戶端的更新。
SSE 的概念您可能熟悉,網頁應用程式會「訂閱」伺服器產生的更新的串流,而且每當發生新事件時,都會傳送通知給用戶端。但為了確實瞭解伺服器傳送事件,我們還需要瞭解其 AJAX 前項的限制。包括:
輪詢:應用程式會重複輪詢伺服器來查詢資料。大部分的 AJAX 應用程式都使用這項技術。使用 HTTP 通訊協定時,擷取資料的方法與要求和回應格式有關。用戶端提出要求,並等待伺服器回應資料。如果沒有可用的資料,會傳回空白回應。額外的輪詢作業會帶來較大的 HTTP 負擔。
長時間輪詢 (Hanging GET / COMET):如果伺服器沒有可用資料,伺服器會保留要求,直到有新資料可供使用為止。因此,這項技術通常稱為「Hanging GET」。有可用資訊時,伺服器會回應並關閉連線,然後重複程序。因此,伺服器會持續以新資料回應。如要進行這項設定,開發人員通常會利用入侵手段,例如將指令碼標記附加至「無限」 iframe。
伺服器傳送事件的設計從零開始設計到效率更高,與 SSE 通訊時,伺服器可以隨時將資料推送至應用程式,無需提出初始要求。換句話說,當更新發生時,可以從伺服器將更新串流至用戶端。SSE 會在伺服器和用戶端之間開啟單一單向通道。
伺服器傳送事件與長時間輪詢之間的主要差異在於,SSE 會直接由瀏覽器處理,而使用者只需聽取訊息即可。
伺服器傳送事件與 WebSocket
為什麼要選擇使用 WebSocket 的伺服器傳送事件?就讓我來回答您的問題!
WebSockets 具有豐富的通訊協定,可與雙向的全雙工通訊。雙向管道較適合用於遊戲、訊息應用程式,以及需要近距離即時更新的任何用途。
不過,有時您只需要來自伺服器的單向通訊即可。
例如,好友更新自己的狀態、股票代號、新聞動態饋給或其他自動化資料推送機制時。換句話說,用戶端網路 SQL 資料庫或 IndexedDB 物件儲存庫的更新。如果您需要將資料傳送至伺服器,XMLHttpRequest
一律會是好友。
SSE 會透過 HTTP 傳送。不需要特殊的通訊協定或伺服器實作就能運作。WebSocket 需要完整雙工連線和新的 WebSocket 伺服器才能處理通訊協定。
此外,伺服器傳送事件具有 WebSocket 設計所缺乏的多種功能,包括自動重新連線、事件 ID,以及傳送任意事件的功能。
使用 JavaScript 建立 EventSource
如要訂閱事件串流,請建立 EventSource
物件,並將串流的網址傳送給該物件:
const source = new EventSource('stream.php');
接下來,請為 message
事件設定處理常式。您可以選擇監聽 open
和 error
:
source.addEventListener('message', (e) => {
console.log(e.data);
});
source.addEventListener('open', (e) => {
// Connection was opened.
});
source.addEventListener('error', (e) => {
if (e.readyState == EventSource.CLOSED) {
// Connection was closed.
}
});
從伺服器推送更新時,onmessage
處理常式會觸發,並在其 e.data
屬性中提供新資料。最神奇的部分是,每次連線關閉時,瀏覽器會在 3 秒後自動重新連線至來源。您的伺服器實作甚至可以控制這個重新連線逾時。
就是這麼簡單!您的客戶現在可以處理來自「stream.php
」的事件。
事件串流格式
從來源傳送事件串流,必須建構採用 SSE 格式的 text/event-stream
Content-Type 的純文字回應。以基本格式來說,回應應包含 data:
行,後面接著訊息,然後加上兩個「\n」字元結束串流:
data: My message\n\n
多行資料
如果訊息較長,則可以使用多行 data:
行拆分。系統會將以 data:
開頭的兩行以上連續資料視為單一資料,這表示只會觸發一個 message
事件。
每一行都應以單一「\n」結尾 (最後一個行結尾應為兩個)。傳送至 message
處理常式的結果是由換行字元串連的單一字串。例如:
data: first line\n
data: second line\n\n</pre>
這會在 e.data
中產生「第一行\n 第二行」。這樣一來,您就可以使用 e.data.split('\n').join('')
來重新建構訊息 San Sant「\n」字元。
傳送 JSON 資料
使用多行程式碼可協助您在不破壞語法的情況下傳送 JSON:
data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n
可能的用戶端程式碼來處理該串流:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.id, data.msg);
});
將 ID 與事件建立關聯
您可以加入開頭為 id:
的一行程式碼,利用串流事件傳送專屬 ID:
id: 12345\n
data: GOOG\n
data: 556\n\n
設定 ID 可讓瀏覽器追蹤上一個事件觸發,一旦伺服器的連線中斷,系統就會透過新的要求設定特殊的 HTTP 標頭 (Last-Event-ID
)。以便瀏覽器判斷最適合觸發的事件。
message
事件包含 e.lastEventId
屬性。
控管重新連線逾時
每次連線關閉後,瀏覽器大約都會嘗試重新連線至來源。如要變更逾時設定,請加入以 retry:
開頭的行,接著加入嘗試重新重新連線前要等待的毫秒數。
下列範例會在 10 秒後嘗試重新連線:
retry: 10000\n
data: hello world\n\n
指定事件名稱
單一事件來源可以加入事件名稱,來產生不同類型的事件。如果存在以 event:
開頭的行,後面接著事件的專屬名稱,該事件會與該名稱相關聯。在用戶端上,您可以設定事件監聽器來監聽該特定事件。
舉例來說,以下伺服器輸出內容會傳送三種事件類型:一般的「message」事件、「userlogon」和「update」事件:
data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n
在用戶端上設定事件監聽器時:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.msg);
});
source.addEventListener('userlogon', (e) => {
const data = JSON.parse(e.data);
console.log(`User login: ${data.username}`);
});
source.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.username} is now ${data.emotion}`);
};
伺服器範例
以下是在 PHP 中實作的基本伺服器實作:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
/**
* Constructs the SSE data format and flushes that data to the client.
*
* @param string $id Timestamp/id of this connection.
* @param string $msg Line of text that should be transmitted.
**/
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>
以下使用 Express 處理常式在 Node JS 上進行類似的實作:
app.get('/events', (req, res) => {
// Send the SSE header.
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Sends an event to the client where the data is the current date,
// then schedules the event to happen again after 5 seconds.
const sendEvent = () => {
const data = (new Date()).toLocaleTimeString();
res.write("data: " + data + '\n\n');
setTimeout(sendEvent, 5000);
};
// Send the initial event immediately.
sendEvent();
});
sse-node.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
const source = new EventSource('/events');
source.onmessage = (e) => {
const content = document.createElement('div');
content.textContent = e.data;
document.body.append(content);
};
</script>
</body>
</html>
取消事件串流
一般而言,瀏覽器會在連線關閉時自動重新連線至事件來源,但用戶端或伺服器可以取消該行為。
如要從用戶端取消串流,請呼叫:
source.close();
如要從伺服器取消串流,請使用非 text/event-stream
Content-Type
回應,或傳回 200 OK
以外的 HTTP 狀態 (例如 404 Not Found
)。
這兩種方法都會阻止瀏覽器重新建立連線。
安全性宣言
EventSource 產生的要求必須遵守與其他網路 API (例如擷取) 相同的來源政策。如需可從不同來源存取伺服器上的 SSE 端點,請參閱如何使用跨源資源共享 (CORS) 來啟用。