基于标准 HTTP 的单向实时通信协议,适用于服务端向客户端推送数据的场景。
核心概念 Link to heading
SSE 是 HTML5 标准的一部分,允许服务端通过一个持久的 HTTP 连接向客户端持续推送数据流。与 WebSocket 不同,SSE 是单向的(服务端 → 客户端),但也因此更加轻量。
sequenceDiagram
participant Browser as 浏览器 EventSource
participant Server as 服务端
Browser->>Server: GET /stream (Accept: text/event-stream)
Server-->>Browser: 200 OK (Content-Type: text/event-stream)
Note over Server: 连接保持打开
Server--)Browser: data: {"msg": "hello"}\n\n
Server--)Browser: data: {"msg": "update"}\n\n
Server--)Browser: data: {"msg": "done"}\n\n
SSE 消息格式:
event: message
id: 1
data: {"content": "Hello SSE"}
retry: 3000
关键协议要素:
| 要素 | 说明 |
|---|---|
Content-Type | 必须为 text/event-stream |
data: | 消息体,可多行,以 \n\n 结尾 |
event: | 事件类型,客户端通过对应名称监听 |
id: | 消息 ID,断线重连时通过 Last-Event-ID 回传 |
retry: | 重连间隔(毫秒),默认 3000 |
SSE vs WebSocket vs 轮询 Link to heading
| 维度 | SSE | WebSocket | 轮询 |
|---|---|---|---|
| 通信方向 | 单向(S → C) | 双向 | 单向(C → S 主动拉取) |
| 协议 | 标准 HTTP | 独立协议 (ws/wss) | 标准 HTTP |
| 自动重连 | 内置支持 | 需自行实现 | 每次请求独立 |
| 二进制数据 | 不支持 | 支持 | 支持 |
| 实现复杂度 | 低 | 中 | 低 |
| HTTP/2 兼容 | 是 | 需额外处理 | 是 |
选型原则:只需服务端推送,选 SSE;需要客户端主动发送消息,选 WebSocket。
安装配置 Link to heading
SSE 无需额外安装依赖,浏览器原生支持 EventSource API。
客户端兼容性:所有现代浏览器(Chrome 6+、Firefox 6+、Safari 5+、Edge 79+)均支持,但 IE 全系列不支持。
实际使用 Link to heading
场景一:AI 流式响应 Link to heading
前端使用 EventSource 接收流式数据:
// 客户端
const source = new EventSource("/api/chat/stream");
source.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
appendToOutput(data.content);
});
source.addEventListener("error", (e) => {
console.error("SSE connection error", e);
});
Node.js 服务端实现:
import express, { Request, Response } from "express";
const app = express();
app.get("/api/chat/stream", (req: Request, res: Response) => {
// 必须设置 SSE 响应头
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// 模拟流式输出
const messages = ["正在思考", "正在生成", "回答完成"];
let index = 0;
const interval = setInterval(() => {
if (index < messages.length) {
const data = JSON.stringify({ content: messages[index] });
res.write(`data: ${data}\n\n`);
index++;
} else {
res.write(`event: done\ndata: {"status": "complete"}\n\n`);
clearInterval(interval);
res.end();
}
}, 1000);
// 客户端断开时清理
req.on("close", () => {
clearInterval(interval);
});
});
app.listen(3000);
场景二:带事件类型和断线恢复的通知系统 Link to heading
// 客户端 - 监听不同事件类型
const es = new EventSource("/api/notifications");
es.addEventListener("order", (e) => {
showOrderNotification(JSON.parse(e.data));
});
es.addEventListener("system", (e) => {
showSystemAlert(JSON.parse(e.data));
});
// 服务端 - 发送带事件类型和 ID 的消息
function sendEvent(res: Response, event: string, id: string, data: object) {
res.write(`event: ${event}\n`);
res.write(`id: ${id}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
}
// 处理重连时的 Last-Event-ID
app.get("/api/notifications", (req: Request, res: Response) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
const lastId = req.headers["last-event-id"];
if (lastId) {
// 从断点恢复,补发缺失的消息
resendMissedEvents(res, parseInt(lastId));
}
// 推送新消息...
});
关键注意事项 Link to heading
- Nginx 反向代理:需关闭
proxy_buffering,否则 SSE 消息会被缓冲区阻塞:
location /api/stream {
proxy_pass http://backend:3000;
proxy_buffering off;
proxy_cache off;
}
- 心跳保活:定期发送注释(
:开头)防止连接被中间代理关闭:
// 每 30 秒发送心跳注释
const heartbeat = setInterval(() => {
res.write(": heartbeat\n\n");
}, 30000);
EventSource的局限:原生EventSource不支持设置请求头和自定义 HTTP 方法(只能是 GET)。如需认证,可通过 URL 参数传递 token,或改用fetch+ReadableStream方案。
官方链接 Link to heading
[1] https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
[2] https://html.spec.whatwg.org/multipage/server-sent-events.html
[3] https://caniuse.com/eventsource
Signature Link to heading
本文由 AI 生成,不保证正确,仅作参考