使用服务器发送事件
开发使用 服务器发送事件 的 Web 应用程序很简单。您需要在服务器上编写一些代码来将事件流传输到前端,但客户端代码在处理传入事件的部分与 WebSockets 几乎完全相同。这是一个单向连接,因此您无法从客户端向服务器发送事件。
从服务器接收事件
服务器发送事件 API 包含在 EventSource
接口中。
创建 EventSource
实例
要打开与服务器的连接以开始接收来自服务器的事件,请使用生成事件的脚本的 URL 创建一个新的 EventSource
对象。例如
const evtSource = new EventSource("ssedemo.php");
如果事件生成器脚本托管在不同的来源,则应该使用 URL 和选项字典创建一个新的 EventSource
对象。例如,假设客户端脚本位于 example.com
上
const evtSource = new EventSource("//api.example.com/ssedemo.php", {
withCredentials: true,
});
监听 message
事件
从服务器发送但没有 event
字段的消息将作为 message
事件接收。要接收消息事件,请为 message
事件附加一个处理程序
evtSource.onmessage = (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
newElement.textContent = `message: ${event.data}`;
eventList.appendChild(newElement);
};
此代码监听传入的消息事件,并将消息文本追加到文档 HTML 中的列表中。
监听自定义事件
来自服务器的消息,如果确实定义了 event
字段,则将作为具有 event
中给定的名称的事件接收。例如
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)。该问题在 Chrome 和 Firefox 中被标记为“不会修复”。此限制是针对每个浏览器 + 域的,这意味着您可以对 www.example1.com
的所有选项卡打开 6 个 SSE 连接,并对 www.example2.com
打开另外 6 个 SSE 连接(根据 Stackoverflow)。使用 HTTP/2 时,同时HTTP 流的最大数量由服务器和客户端协商(默认值为 100)。
从服务器发送事件
发送事件的服务器端脚本需要使用 MIME 类型 text/event-stream
响应。每个通知都作为以一对换行符结尾的文本块发送。有关事件流格式的详细信息,请参见 事件流格式。
我们这里使用的示例的 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
回调来以编程方式对此采取行动
evtSource.onerror = (err) => {
console.error("EventSource failed:", err);
};
关闭事件流
默认情况下,如果客户端和服务器之间的连接关闭,则连接将重新启动。连接使用 .close()
方法终止。
evtSource.close();
事件流格式
事件流是简单的文本数据流,必须使用 UTF-8 编码。事件流中的消息由一对换行符分隔。冒号作为一行的第一个字符本质上是注释,将被忽略。
注意:注释行可用于防止连接超时;服务器可以定期发送注释以保持连接活动。
每条消息都包含一行或多行文本,列出了该消息的字段。每个字段都由字段名称、冒号以及该字段值的文本数据表示。
字段
每条接收的消息都包含以下字段的某种组合,每行一个
event
-
标识所描述事件类型的字符串。如果指定了此字段,则会在浏览器中向指定事件名称的侦听器分派一个事件;网站源代码应该使用
addEventListener()
监听命名事件。如果消息没有指定事件名称,则会调用onmessage
处理程序。 data
-
消息的数据字段。当
EventSource
接收以data:
开头的多个连续行时,它会将它们连接起来,在每个之间插入一个换行符。尾随换行符将被删除。 id
-
要设置
EventSource
对象的最后一个事件 ID 值的事件 ID。 retry
-
重新连接时间。如果与服务器的连接断开,浏览器将等待指定时间,然后再尝试重新连接。这必须是一个整数,以毫秒为单位指定重新连接时间。如果指定了非整数值,则会忽略该字段。
所有其他字段名称将被忽略。
注意:如果一行不包含冒号,则整行将被视为具有空值字符串的字段名称。
示例
仅数据消息
在以下示例中,发送了三条消息。第一条只是一条注释,因为它以冒号字符开头。如前所述,如果消息可能不会定期发送,这可以作为一种保持活动机制。
第二条消息包含一个数据字段,其值为“some text”。第三条消息包含一个数据字段,其值为“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."}
浏览器兼容性
BCD 表仅在浏览器中加载