使用 Bun 的服务器发送事件(SSE)
服务器发送事件允许你通过单个 HTTP 响应向浏览器推送文本事件流。客户端使用 EventSource 消费这些事件。
要在 Bun 中实现 SSE 端点,请返回一个正文为流式源的 Response,并将 Content-Type 标头设置为 text/event-stream。
使用异步生成器
在 Bun 中,new Response 可直接接受异步生成器函数。异步生成器通常是编写 SSE 端点最简单的方式。每次 yield 都会向客户端刷新一个数据块。如果客户端断开连接,生成器的 finally 块会运行,以便你进行清理。
Bun.serve({
port: 3000,
routes: {
"/events": (req, server) => {
// SSE 流事件间通常比较安静。
// 默认情况下,Bun.serve 会在 10 秒无活动后关闭连接。
// 禁用该请求的空闲超时,以保持流无限期打开。
server.timeout(req, 0);
return new Response(
async function* () {
yield `data: connected at ${Date.now()}\n\n`;
// 每 5 秒发送一个 tick,直到客户端断开连接。
// 客户端断开时,生成器被返回(取消),此循环自动停止。
while (true) {
await Bun.sleep(5000);
yield `data: tick ${Date.now()}\n\n`;
}
},
{
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
},
);
},
},
});
使用 ReadableStream
如果你的事件来自回调(消息代理、计时器、外部推送),而不是线性的 await 流程,那么 ReadableStream 通常更合适。当客户端断开连接时,Bun 会自动调用流的 cancel() 方法,因此你可以释放在 start() 中设置的所有资源。
Bun.serve({
port: 3000,
routes: {
"/events": (req, server) => {
server.timeout(req, 0);
let timer: Timer;
const stream = new ReadableStream({
start(controller) {
controller.enqueue(`data: connected at ${Date.now()}\n\n`);
timer = setInterval(() => {
controller.enqueue(`data: tick ${Date.now()}\n\n`);
}, 5000);
},
cancel() {
// 当客户端断开连接时自动调用。
clearInterval(timer);
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
});
},
},
});