协议描述
客户端
使用 EventSource
服务端
添加响应头:Content-Type: text/event-stream
SSE 协议原生只支持 GET 请求,如果想使用非 GET 的请求来实现 SSE,需要安装三方库(下文有示例)。
时序
用顺序图表示整个交互过程,如下图所示:
message 数据格式
参考:www.ruanyifeng.com/blog/2017/0...
每一行都以 id: 或者 event: 或者 data: 或者 retry: 开头,以\n 结尾,最后一行以\n\n 结尾 [id/event/data/retry]: value\n 如果 data 有多行,格式如下: data: xxx\n data: yyy\n\n
如果返回的是复杂对象,需要对整个对象序列化。
如果服务端是 nodejs,数据格式示例如下:
javascript
`data: ${JSON.stringify({
message: "Update from server",
})}\n`;
使用场景
服务端推送数据到前端
示例代码
用 GET 发送请求
前端
创建 EventSource
对象,设置 onmessage
的回调(可以用 addEventListener 注册回调)
javascript
const source = new EventSource(url);
source.onmessage = (e) => {
const data = JSON.parse(e.data);
const message = data.message;
this.list.push(message);
};
服务端
用 express 实现
javascript
app.get("/rest/express/sse", async (req, res) => {
res.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
res.flushHeaders();
setInterval(() => {
const data = {
message: `Current time is ${new Date().toLocaleTimeString()}`,
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
});
用 koa 实现
基于 koa-sse-stream 实现
javascript
const sse = require("koa-sse-stream");
app.use(
sse({
maxClients: 5000,
pingInterval: 30000,
})
);
router.get("/rest/sse", async (ctx, next) => {
ctx.header = {
"Content-Type": "text/event-stream",
};
ctx.sse.send("get notice");
for (let i = 0; i < 5; i++) {
await new Promise((resolve) => {
setTimeout(() => {
ctx.sse.send("interval notice");
resolve();
}, 500);
});
}
ctx.sse.sendEnd();
});
不使用 koa-sse-stream 实现
采用 nodejs 原生的 stream
实现,下面的示例采用了PassThrough
stream.
javascript
const { PassThrough } = require("stream");
router.get("/rest/sse/native", (ctx, next) => {
ctx.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
const stream = new PassThrough();
ctx.status = 200;
for (let i = 0; i < 5; i++) {
setTimeout(() => {
stream.write(`id: ${i}\n`);
stream.write(
`data: ${JSON.stringify({
message: "Update from server",
})}\n`
);
stream.write("retry: 1000\n");
stream.write("\n\n");
}, i * 1000);
}
ctx.body = stream;
});
浏览器运行效果
用非 GET 的 method 发送请求
前端
安装 @microsoft/fetch-event-source
shell
npm i -S @microsoft/fetch-event-source
javascript
import { fetchEventSource } from "@microsoft/fetch-event-source";
fetchEventSource("/rest/sse", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
foo: "bar",
}),
onmessage: (ev) => {
console.log(ev.data);
this.list.push(ev.data);
},
onerror(err) {
console.log("err: ", err);
},
signal: AbortController.signal,
});
服务端
使用 koa
时,需要安装 koa-sse-stream
javascript
router.post("/rest/sse", async (ctx, next) => {
ctx.header = {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
};
ctx.sse.send("post notice");
for (let i = 0; i < 5; i++) {
await new Promise((resolve) => {
setTimeout(() => {
ctx.sse.send("interval post notice");
resolve();
}, 200);
});
}
ctx.sse.sendEnd();
});
遇到的问题
sse 请求会等到流结束后才返回,而不是分批返回
现象
原因
回调函数中,stream
被 await
阻塞,没有立即返回
错误代码
javascript
router.get("/rest/sse/native", async (ctx, next) => {
ctx.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
const stream = new PassThrough();
ctx.status = 200;
for (let i = 0; i < 5; i++) {
await new Promise((resolve) => {
// 这行前面不能加await
setTimeout(() => {
stream.write(
`data: ${JSON.stringify({
message: "Update from server",
})}\n`
);
stream.write("retry: 1000\n");
stream.write("\n\n");
resolve();
}, i * 1000);
});
}
ctx.body = stream;
});
解决
javascript
router.get("/xxx", async (ctx) => {});
回调如果有将 stream.write
封装成 promise
,不要 await
这个 promise
.
使用 nodejs 的 PassThrough stream,返回的 message 中,data 为空
现象
原因
没有使用 koa-sse-stream
来返回 message,但是 koa
应用又引入了koa-sse-stream
错误代码
javascript
const Koa = require("koa");
const sse = require("koa-sse-stream");
const app = new Koa();
// app应用不应该引入koa-sse-stream
app.use(
sse({
maxClients: 5000,
pingInterval: 30000,
})
);
router.get("/rest/sse/native", (ctx, next) => {
ctx.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
const stream = new PassThrough();
ctx.status = 200;
for (let i = 0; i < 5; i++) {
setTimeout(() => {
stream.write(
`data: ${JSON.stringify({
message: "Update from server",
})}\n`
);
stream.write("retry: 1000\n");
stream.write("\n\n");
}, i * 1000);
}
ctx.body = stream;
});
解决
koa
应用取消引入 koa-sse-stream