Skip to main content

SSE 和 EventSource 使用

· 3 min read

SSE

规范参考 SSE(Server Send Event):

  1. : 开头表示此行是注释, 不是数据
  2. 每条消息之间用\n\n 分割, 比如以下是两条消息, 第一条消息内容是 hello, 第二条消息的内容是 world
data: hello

data: world

但是以下是一条消息, 消息内容是 hello\nworld:

data: hello
data: world
  1. event: 是可以省略的,缺省状态下事件类型是 message, 事件类型可以自定义, 或者只有事件发送
event: message
data: hello

event: close

示例

服务器端输出如下:

node.js
const rs = new ReadableStream({
async start(ctrl) {
const encoder = new TextEncoder();
ctrl.enqueue(encoder.encode(": this line is comment\n\n"));
ctrl.enqueue(encoder.encode("event: message\ndata: some text\n\n"));
ctrl.enqueue(encoder.encode("data: another message\n"));
ctrl.enqueue(encoder.encode("data: with two lines"));
}
});
return new Response(rs, {
headers: {
Connection: "keep-alive",
"Content-Encoding": "none",
"Cache-Control": "no-cache, no-transform",
"Content-Type": "text/event-stream; charset=utf-8"
}
});
浏览器接收到的流
: this line is comment

event: message
data: some text

data: another message
data: with two lines

对于以上流, 使用 EventSource 实际会接收到 3 条消息:

  • 第一条以冒号开头, 表示仅仅是一个注释, 有时候用于保持客户端和服务器端持续连接(类似于心跳)
  • 第二条的消息内容是 some text
  • 第三条的消息内容是 another message\nwith two lines

EventSource

浏览器端可使用 new EventSource(url, { withCredentials: true }).onmessage 接收服务器端的消息, 但是这种只支持 GET 请求.

可以使用 fetch 接收 SSE 消息:

const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "text/event-stream"
},
body: JSON.stringify({
/** 请求体内容 */
})
});

const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
console.log("value: ", value); // 这里需要自己根据规范处理 data event 等
}

或者直接使用第三方类库 @microsoft/fetch-event-source

const ctrl = new AbortController();
fetchEventSource(url, {
method: "POST",
body: JSON.stringify({}),
signal: ctrl.signal,
credentials: "include",
openWhenHidden: true, // fetchEventSource 默认会监听 visibleChange 事件, 传 `true` 禁用该行为
onmessage(msg) {},
onclose() {},
onerror(err) {
// 这里 abort 是防止自动重试, 根据自己需求设置
ctrl.abort();
}
});