web-SSE

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。

github