首頁 >web前端 >js教程 >如何在node.js中使用服務器範圍的事件

如何在node.js中使用服務器範圍的事件

Joseph Gordon-Levitt
Joseph Gordon-Levitt原創
2025-02-08 09:31:09236瀏覽

How to Use Server-sent Events in Node.js

核心要點

  • 服務器發送事件 (SSE) 使服務器能夠隨時向瀏覽器推送數據,從而實現諸如實時新聞快報、天氣預報和股票價格等功能。瀏覽器發出初始請求以建立連接,服務器保持連接打開以發送文本消息。
  • SSE 比 WebSockets 更簡單,使用標準 HTTP,支持單向通信,並提供自動重新連接。服務器可以隨時終止 SSE 響應,但如果連接中斷,瀏覽器會自動嘗試重新連接。
  • 服務器可以提供任意數量的 SSE 通道 URL 或單個端點 URL。來自服務器的消息可以具有關聯的事件,以識別特定類型的信息。服務器還可以數據行後發送 ID,以便在連接斷開時重新發送任何錯過的消息。
  • 瀏覽器可以使用 EventSource 對象的 .close() 方法終止 SSE 通信。服務器可以通過觸發 res.end() 或發送重試延遲來終止連接,然後在同一瀏覽器嘗試重新連接時返回 HTTP 狀態 204。只有瀏覽器可以通過創建新的 EventSource 對象來重新建立連接。

本文將探討如何使用服務器發送事件 (SSE) 使客戶端能夠通過 HTTP 連接接收來自服務器的自動更新。我們還將探討其用途,並展示如何使用 Node.js 使用服務器發送事件的實際演示。

  • 服務器發送事件的優勢
  • 服務器發送事件快速入門
    • 重要提示
  • 高級服務器發送事件
    • 單個與多個 SSE 通道
    • 在單個通道上發送不同的數據
    • 使用數據標識符
    • 指定重試延遲
    • 其他事件處理程序
    • 終止 SSE 通信
  • 結論

服務器發送事件的優勢

Web 基於請求-響應 HTTP 消息。您的瀏覽器發出 URL 請求,服務器返回數據。這可能會導致瀏覽器對圖像、CSS、JavaScript 等發出更多請求,服務器做出響應。服務器無法主動向瀏覽器發送消息,那麼它如何指示數據已更改?幸運的是,您可以使用服務器發送事件 (SSE) 添加諸如實時新聞快報、天氣預報和股票價格等功能。

使用標準 Web 技術實現實時數據更新一直是可能的:

  • 20 世紀 90 年代的 Web 使用全頁或框架/iframe 刷新。
  • 2000 年代的 Web 引入了 Ajax,它可以使用長輪詢來請求數據並使用新信息更新相應的 DOM 元素。

這兩個選項都不是理想的,因為瀏覽器必須觸髮刷新。如果它過於頻繁地發出請求,則不會有任何數據更改,因此瀏覽器和服務器都會執行不必要的工作。如果它請求太慢,它可能會錯過重要的更新,而您正在關注的股票價格已經暴跌!

服務器發送事件 (SSE) 允許服務器隨時向瀏覽器推送數據:

  • 瀏覽器仍然發出初始請求以建立連接。
  • 服務器返回事件流響應並保持連接打開。
  • 服務器可以在任何時候使用此連接發送文本消息。
  • 傳入的數據會在瀏覽器中引發 JavaScript 事件。事件處理程序函數可以解析數據並更新 DOM。

本質上,SSE 是一個無限的數據流。可以將其視為下載一個無限大的文件,該文件以您可以攔截和讀取的小塊的形式下載。

SSE 最初於 2006 年實施,所有主要瀏覽器都支持該標準。它可能不如 WebSockets 廣為人知,但服務器發送事件更簡單,使用標準 HTTP,支持單向通信,並提供自動重新連接。本教程提供了無需第三方模塊的示例 Node.js 代碼,但 SSE 可用於其他服務器端語言,包括 PHP。

服務器發送事件快速入門

以下演示實現了一個 Node.js Web 服務器,該服務器以至少每三秒一次的隨機間隔輸出 1 到 1000 之間的隨機數。

您可以在這裡找到我們的 Node.js SSE 演示。

該代碼使用標準 Node.js http 和 url 模塊來創建 Web 服務器和解析 URL:

<code class="language-javascript">import http from "node:http";
import url from "node:url";</code>

服務器檢查傳入的 URL 請求,並在遇到 /random 路徑時做出反應:

<code class="language-javascript">const port = 8000;

http.createServer(async (req, res) => {

  // 获取 URI 路径
  const uri = url.parse(req.url).pathname;

  // 返回响应
  switch (uri) {
    case "/random":
      sseStart(res);
      sseRandom(res);
      break;
  }

}).listen(port);

console.log(`server running: http://localhost:${port}\n\n`);</code>

它最初會使用 SSE HTTP 事件流標頭進行響應:

<code class="language-javascript">// SSE 头
function sseStart(res) {
  res.writeHead(200, {
    Content-Type: "text/event-stream",
    Cache-Control: "no-cache",
    Connection: "keep-alive"
  });
}</code>

另一個函數隨後發送一個隨機數,並在隨機間隔過去後調用自身:

<code class="language-javascript">// SSE 随机数
function sseRandom(res) {
  res.write("data: " + (Math.floor(Math.random() * 1000) + 1) + "\n\n");
  setTimeout(() => sseRandom(res), Math.random() * 3000);
}</code>

如果您在本地運行代碼,則可以使用終端中的 cURL 測試響應:

<code class="language-bash">$> curl -H Accept:text/event-stream http://localhost:8000/random
data: 481

data: 127

data: 975</code>

Ctrl | CmdC 終止請求。

瀏覽器的客戶端 JavaScript 使用 EventSource 對象構造函數連接到 /random URI:

<code class="language-javascript">// 客户端 JS
const source = new EventSource("/random");</code>

傳入的數據會觸發消息事件處理程序,其中 data: 後面的字符串在事件對象的 .data 屬性中可用:

<code class="language-javascript">source.addEventListener('message', e => {
  console.log('RECEIVED', e.data);
});</code>

重要提示

  • 與 Fetch() 一樣,瀏覽器會發出標準 HTTP 請求,因此您可能需要處理 CSP、CORS,並可以選擇向 EventSource 構造函數傳遞第二個 { withCredentials: true } 參數以發送 Cookie。
  • 服務器必須為每個連接的用戶保留單獨的 res 響應對象才能向其發送數據。這在上面的代碼中通過將值傳遞到閉包以進行下一次調用來實現。
  • 消息數據只能是格式為 data: nn 的字符串(可能是 JSON)。終止回車符至關重要。
  • 服務器可以使用 res.end() 隨時終止 SSE 響應,但是……
  • 連接中斷時,瀏覽器會自動嘗試重新連接;無需編寫您自己的重新連接代碼。

高級服務器發送事件

SSE 不需要比上面顯示的更多代碼,但以下部分將討論其他選項。

單個與多個 SSE 通道

服務器可以提供任意數量的 SSE 通道 URL。例如:

  • /latest/news
  • /latest/weather
  • /latest/stockprice

如果單個頁面顯示一個主題,這可能是實用的,但如果單個頁面顯示新聞、天氣和股票價格,則並非如此。在這種情況下,服務器必須為每個用戶維護三個連接,這可能會隨著流量的增加而導致內存問題。

另一種選擇是提供單個端點 URL,例如 /latest,它在一個通信通道上發送任何數據類型。瀏覽器可以在 URL 查詢字符串中指示感興趣的主題,例如 /latest?type=news,weather,stockprice,以便服務器可以將 SSE 響應限制為特定消息。

在單個通道上發送不同的數據

來自服務器的消息可以具有關聯的 event:,它在 data: 行上方傳遞,以識別特定類型的信息:

<code class="language-javascript">import http from "node:http";
import url from "node:url";</code>

這些不會觸發客戶端的“message”事件處理程序。您必須為每種類型的事件添加處理程序。例如:

<code class="language-javascript">const port = 8000;

http.createServer(async (req, res) => {

  // 获取 URI 路径
  const uri = url.parse(req.url).pathname;

  // 返回响应
  switch (uri) {
    case "/random":
      sseStart(res);
      sseRandom(res);
      break;
  }

}).listen(port);

console.log(`server running: http://localhost:${port}\n\n`);</code>

使用數據標識符

服務器還可以選擇在 data: 行之後發送 id:

<code class="language-javascript">// SSE 头
function sseStart(res) {
  res.writeHead(200, {
    Content-Type: "text/event-stream",
    Cache-Control: "no-cache",
    Connection: "keep-alive"
  });
}</code>

如果連接斷開,瀏覽器會在 Last-Event-ID HTTP 標頭中將最後一個 ID 發送回服務器,以便服務器可以重新發送任何錯過的消息。

最新的 ID 也可在客戶端的事件對象的 .lastEventId 屬性中獲得:

<code class="language-javascript">// SSE 随机数
function sseRandom(res) {
  res.write("data: " + (Math.floor(Math.random() * 1000) + 1) + "\n\n");
  setTimeout(() => sseRandom(res), Math.random() * 3000);
}</code>

指定重試延遲

儘管重新連接是自動的,但您的服務器可能知道在特定時間段內不需要新數據,因此無需保留活動的通信通道。服務器可以在其自身或作為最終消息的一部分發送 retry: 響應,其中包含毫秒值。例如:

<code class="language-bash">$> curl -H Accept:text/event-stream http://localhost:8000/random
data: 481

data: 127

data: 975</code>

收到後,瀏覽器將放棄 SSE 連接,並在延遲時間過去後嘗試重新連接。

其他事件處理程序

除了“message”和命名事件之外,您還可以在客戶端 JavaScript 中創建“open”和“error”處理程序。

當服務器連接建立時,會觸發“open”事件。它可用於運行其他配置代碼或初始化 DOM 元素:

<code class="language-javascript">// 客户端 JS
const source = new EventSource("/random");</code>

當服務器連接失敗或終止時,會觸發“error”事件。您可以檢查事件對象的 .eventPhase 屬性以查看發生了什麼:

<code class="language-javascript">source.addEventListener('message', e => {
  console.log('RECEIVED', e.data);
});</code>

請記住,無需重新連接:它會自動發生

終止 SSE 通信

瀏覽器可以使用 EventSource 對象的 .close() 方法終止 SSE 通信。例如:

<code>event: news
data: SSE is great!

event: weather
data: { "temperature": "20C", "wind": "10Kph", "rain": "25%" }

event: stock
data: { "symbol": "AC", "company": "Acme Corp", "price": 123.45, "increase": -1.1 }</code>

服務器可以通過以下方式終止連接:

  1. 觸發 res.end() 或發送 retry: 延遲,然後
  2. 在同一瀏覽器嘗試重新連接時返回 HTTP 狀態 204。

只有瀏覽器可以通過創建新的 EventSource 對象來重新建立連接。

結論

服務器端事件提供了一種實現實時頁面更新的方法,這種方法可能比基於 Fetch() 的 Ajax 輪詢更容易、更實用且更輕量級。複雜性在於服務器端。您必須:

  1. 將所有用戶的活動連接保留在內存中,以及
  2. 在發生更改時觸發數據傳輸。

但這完全在您的控制之下,並且擴展應該不比任何其他 Web 應用程序更複雜。

唯一的缺點是 SSE 不允許您從瀏覽器向服務器發送消息(除了初始連接請求)。您可以使用 Ajax,但這對於動作遊戲等應用程序來說太慢了。對於適當的雙向通信,您需要 WebSockets。請查看如何在 Node.js 中使用 WebSockets 創建實時應用程序以了解更多信息!

以上是如何在node.js中使用服務器範圍的事件的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn