基于标准 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

维度SSEWebSocket轮询
通信方向单向(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 生成,不保证正确,仅作参考