使用服务器发送事件
Baseline 广泛可用 *
开发使用 服务器发送事件 的 Web 应用程序很简单。您需要在服务器端编写一些代码来将事件流式传输到前端,但在处理传入事件的某些方面,客户端代码与 WebSocket 几乎相同。这是一个单向连接,因此您无法从客户端向服务器发送事件。
从服务器接收事件
服务器发送事件 API 包含在 EventSource 接口中。
创建 EventSource 实例
要打开与服务器的连接以开始从中接收事件,请使用生成事件的脚本的 URL 创建一个新的 EventSource 对象。例如:
const evtSource = new EventSource("sse-demo.php");
如果事件生成器脚本托管在不同的域上,则应使用 URL 和选项字典创建一个新的 EventSource 对象。例如,假设客户端脚本位于 example.com
const evtSource = new EventSource("//api.example.com/sse-demo.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);
});
每当服务器发送 event 字段设置为 ping 的消息时,都会调用此代码;然后它会解析 data 字段中的 JSON 并输出该信息。
警告: 当不通过 HTTP/2 使用时,SSE 会受到最大打开连接数的限制,这在打开多个选项卡时可能会特别痛苦,因为限制是每个浏览器并且设置为一个非常小的数字(6)。这个问题已在 Chrome 和 Firefox 中被标记为“不会修复”。此限制是每个浏览器 + 域,这意味着您可以打开 6 个到 www.example1.com 的 SSE 连接,以及另外 6 个到 www.example2.com 的 SSE 连接(根据 Stack Overflow)。使用 HTTP/2 时,同时HTTP 流的最大数量在服务器和客户端之间协商(默认为 100)。
从服务器发送事件
发送事件的服务器端脚本需要使用 text/event-stream MIME 类型进行响应。每个通知都作为一个文本块发送,并以一对换行符终止。有关事件流格式的详细信息,请参阅 事件流格式。
我们在此示例中使用的 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 演示。
错误处理
如果服务器响应了 error 键(例如,JSON.parse(event.data.error) 或发生其他问题(例如网络超时或与 访问控制相关的问题)),则会生成一个错误事件。您可以通过实现 EventSource 对象上的 onerror 回调来以编程方式处理此问题。
evtSource.onerror = (err) => {
console.error("EventSource failed:", err);
};
关闭事件流
默认情况下,如果客户端和服务器之间的连接关闭,连接将重新启动。通过 .close() 方法终止连接。
evtSource.close();
事件流格式
事件流是一个简单的文本数据流,必须使用 UTF-8 进行编码。事件流中的消息由一对换行符分隔。行首的冒号本质上是注释,会被忽略。
注意:注释行可用于防止连接超时;服务器可以定期发送注释以保持连接活跃。
每条消息由一行或多行文本组成,列出该消息的字段。每个字段由字段名、冒号和字段值的文本数据组成。
字段
每条收到的消息都包含以下字段的某种组合,每行一个:
event-
一个字符串,标识事件的类型。如果指定了此字段,则会在浏览器上为指定事件名的监听器分派一个事件;网站源代码应使用
addEventListener()来监听命名事件。如果没有为消息指定事件名,则会调用onmessage处理程序。 data-
消息的数据字段。当
EventSource收到多个以data:开头的连续行时,它会将它们连接起来,并在它们之间插入一个换行符。尾随的换行符会被删除。 id-
事件 ID,用于设置
EventSource对象的最后事件 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."}
浏览器兼容性
加载中…