SSE(Server-Sent Events)
SSE 适合这种场景:
浏览器发起一次 HTTP 请求,服务端后续持续把消息推回来。
sequenceDiagram
participant B as Browser
participant S as Server
B->>S: GET /events
S-->>B: event: progress\ndata: {"percent":30}
S-->>B: event: done\ndata: finished
什么时候用 SSE
- 长任务进度推送
- AI 输出流
- 日志流
- 通知流
如果只需要“服务端单向推给浏览器”,SSE 往往比 WebSocket 更轻。
SSE 的几个关键点
- 基于 HTTP
- 单向通信:服务端 -> 浏览器
- 浏览器原生支持
EventSource - 断线后通常会自动重连
和轮询、WebSocket 怎么选
| 方案 | 特点 | 适合什么 |
|---|---|---|
| 轮询 | 简单,但有很多空请求 | 更新频率不高的场景 |
| SSE | 单向流式推送,浏览器友好 | 任务进度、日志、AI 输出 |
| WebSocket | 双向实时通信 | 聊天、协同编辑、实时互动 |
SSE 消息格式
id: 101
event: progress
retry: 5000
data: {"percent": 60, "status": "running"}
要点只有两个:
Content-Type必须是text/event-stream- 每条消息必须以空行结束
前端怎么接
const source = new EventSource('/events');
source.addEventListener('progress', (event) => {
const payload = JSON.parse(event.data);
console.log(payload.percent);
});
source.addEventListener('done', () => {
source.close();
});
后端怎么发
import asyncio
import json
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
async def event_stream():
for percent in range(0, 101, 20):
payload = {"percent": percent}
yield f"event: progress\ndata: {json.dumps(payload)}\n\n"
await asyncio.sleep(1)
yield "event: done\ndata: finished\n\n"
@app.get('/events')
async def events():
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
生产环境最常见的坑
- 代理层开了缓冲,导致前端迟迟收不到消息
- 长时间没心跳,被网关或 Nginx 断开
EventSource不方便自定义请求头,认证方案要提前设计- 把 SSE 当成双向通信来用
Nginx 配置要点
location /events {
proxy_pass http://app_backend;
proxy_buffering off;
proxy_read_timeout 1h;
add_header X-Accel-Buffering no;
}
最关键的是关闭缓冲,否则 SSE 很容易变成“假实时”。
总结
SSE 很适合“轻实时、单向推送”的 Web 场景。
一句话判断:
浏览器只负责接收流式消息,就优先考虑 SSE;需要双向实时交互,再考虑 WebSocket。