使用服务器发送事件

开发使用 服务器发送事件 的 Web 应用程序很简单。您需要在服务器上编写一些代码来将事件流传输到前端,但客户端代码在处理传入事件的部分与 WebSockets 几乎完全相同。这是一个单向连接,因此您无法从客户端向服务器发送事件。

从服务器接收事件

服务器发送事件 API 包含在 EventSource 接口中。

创建 EventSource 实例

要打开与服务器的连接以开始接收来自服务器的事件,请使用生成事件的脚本的 URL 创建一个新的 EventSource 对象。例如

js
const evtSource = new EventSource("ssedemo.php");

如果事件生成器脚本托管在不同的来源,则应该使用 URL 和选项字典创建一个新的 EventSource 对象。例如,假设客户端脚本位于 example.com

js
const evtSource = new EventSource("//api.example.com/ssedemo.php", {
  withCredentials: true,
});

监听 message 事件

从服务器发送但没有 event 字段的消息将作为 message 事件接收。要接收消息事件,请为 message 事件附加一个处理程序

js
evtSource.onmessage = (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");

  newElement.textContent = `message: ${event.data}`;
  eventList.appendChild(newElement);
};

此代码监听传入的消息事件,并将消息文本追加到文档 HTML 中的列表中。

监听自定义事件

来自服务器的消息,如果确实定义了 event 字段,则将作为具有 event 中给定的名称的事件接收。例如

js
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);
});

每当服务器发送事件字段设置为 ping 的消息时,将调用此代码;它然后解析 data 字段中的 JSON 并输出该信息。

警告:没有通过 HTTP/2 使用时,SSE 受到打开连接数量最大值的限制,这在打开多个选项卡时尤其令人头疼,因为限制是针对每个浏览器的,并且设置为非常低的数字 (6)。该问题在 ChromeFirefox 中被标记为“不会修复”。此限制是针对每个浏览器 + 域的,这意味着您可以对 www.example1.com 的所有选项卡打开 6 个 SSE 连接,并对 www.example2.com 打开另外 6 个 SSE 连接(根据 Stackoverflow)。使用 HTTP/2 时,同时HTTP 流的最大数量由服务器和客户端协商(默认值为 100)。

从服务器发送事件

发送事件的服务器端脚本需要使用 MIME 类型 text/event-stream 响应。每个通知都作为以一对换行符结尾的文本块发送。有关事件流格式的详细信息,请参见 事件流格式

我们这里使用的示例的 PHP 代码如下

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 演示

错误处理

当出现问题(例如网络超时或与 访问控制 相关的問題)时,会生成一个错误事件。您可以通过在 EventSource 对象上实现 onerror 回调来以编程方式对此采取行动

js
evtSource.onerror = (err) => {
  console.error("EventSource failed:", err);
};

关闭事件流

默认情况下,如果客户端和服务器之间的连接关闭,则连接将重新启动。连接使用 .close() 方法终止。

js
evtSource.close();

事件流格式

事件流是简单的文本数据流,必须使用 UTF-8 编码。事件流中的消息由一对换行符分隔。冒号作为一行的第一个字符本质上是注释,将被忽略。

注意:注释行可用于防止连接超时;服务器可以定期发送注释以保持连接活动。

每条消息都包含一行或多行文本,列出了该消息的字段。每个字段都由字段名称、冒号以及该字段值的文本数据表示。

字段

每条接收的消息都包含以下字段的某种组合,每行一个

event

标识所描述事件类型的字符串。如果指定了此字段,则会在浏览器中向指定事件名称的侦听器分派一个事件;网站源代码应该使用 addEventListener() 监听命名事件。如果消息没有指定事件名称,则会调用 onmessage 处理程序。

data

消息的数据字段。当 EventSource 接收以 data: 开头的多个连续行时,它会将它们连接起来,在每个之间插入一个换行符。尾随换行符将被删除。

id

要设置 EventSource 对象的最后一个事件 ID 值的事件 ID。

retry

重新连接时间。如果与服务器的连接断开,浏览器将等待指定时间,然后再尝试重新连接。这必须是一个整数,以毫秒为单位指定重新连接时间。如果指定了非整数值,则会忽略该字段。

所有其他字段名称将被忽略。

注意:如果一行不包含冒号,则整行将被视为具有空值字符串的字段名称。

示例

仅数据消息

在以下示例中,发送了三条消息。第一条只是一条注释,因为它以冒号字符开头。如前所述,如果消息可能不会定期发送,这可以作为一种保持活动机制。

第二条消息包含一个数据字段,其值为“some text”。第三条消息包含一个数据字段,其值为“another message\nwith two lines”。请注意值中的换行符特殊字符。

bash
: this is a test stream

data: some text

data: another message
data: with two lines

命名事件

此示例发送命名事件。每个事件都具有由 event 字段指定的事件名称,以及一个 data 字段,其值为一个适当的 JSON 字符串,其中包含客户端对事件采取行动所需的数据。当然,data 字段可以包含任何字符串数据;它不必是 JSON。

bash
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."}

混合和匹配

您不必只使用未命名的消息或类型事件;您可以将它们混合在一起放到一个事件流中。

bash
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."}

浏览器兼容性

BCD 表仅在浏览器中加载