首页 >web前端 >js教程 >如何在node.js中使用服务器范围的事件

如何在node.js中使用服务器范围的事件

Joseph Gordon-Levitt
Joseph Gordon-Levitt原创
2025-02-08 09:31:09237浏览

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