使用 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",
},
});
},
},
});