SSE (Server-Sent Events)
服务器推送事件,单向实时通信
2. SSE (Server-Sent Events)
2.1 ⚠️ 核心理解:SSE 的本质是什么
在深入技术细节前,先理解一个关键事实:SSE 的本质是一种响应格式,而不是一种请求方式。
SSE 的技术本质
从服务端视角看,SSE 只包含两个核心要素:
- 响应头:
Content-Type: text/event-stream - 数据格式:
data: ...\n\n的文本流
这与客户端用什么 HTTP 方法(GET/POST)发起请求完全无关。
javascript
// 服务端视角:SSE 的核心逻辑(不管请求是 GET 还是 POST)
response.setHeader('Content-Type', 'text/event-stream');
response.write('data: {"content":"Hello"}\n\n');
response.write('data: {"content":" World"}\n\n');客户端的两种实现方式
| 实现方式 | 请求方法 | API | 适用场景 |
|---|---|---|---|
| 标准 SSE | 仅 GET | EventSource | 简单通知、公开数据 |
| Fetch Streaming | 任意 (GET/POST) | fetch() | LLM 对话、需认证 |
关键区别:
所谓的"不支持 POST",纯粹是浏览器端的 EventSource API 对象被设计得过于限制(不支持自定义请求配置)。
服务端其实非常灵活:只要你是一个 HTTP 请求,无论是 GET、POST 甚至 PUT 进来的,只要服务端回复 text/event-stream,就能实现 SSE。
EventSource 的局限性
在浏览器中使用原生的 new EventSource(url) 时,存在硬性限制:
- 只能发起 GET 请求
- 不能携带请求体(Request Body)
- 不能自定义 Headers(无法添加 Authorization)
- 只能通过 URL 参数(Query Params)传递数据
痛点: 对于 LLM 应用,用户的 Prompt 可能有几千字符,URL 长度有限制且不安全;同时需要携带 Authorization Token 进行身份认证。这就是为什么 ChatGPT 等应用都使用 POST + Fetch Streaming。
两种场景对比
┌─────────────────────────────────────────────────────────────────────────┐
│ 标准 SSE vs Fetch Streaming 对比 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 场景 A: 标准 SSE (EventSource) │
│ ────────────────────────── │
│ 适用于:股票行情、消息通知(不需要客户端向服务端发大量数据)。 │
│ │
│ const evtSource = new EventSource("/api/stock-updates?symbol=AAPL"); │
│ │
│ // 限制:只能 GET,参数只能拼在 URL 里 │
│ // 优势:自动处理断线重连,自动解析数据格式 │
│ evtSource.onmessage = (event) => { │
│ console.log("收到数据:", event.data); │
│ }; │
│ │
│ ──────────────────────────────────────────────────────────────────── │
│ │
│ 场景 B: Fetch 模拟 SSE (Fetch Streaming) │
│ ─────────────────────────────────────────── │
│ 适用于:AI 对话(客户端需要发送长文本给服务端,服务端流式返回结果)。 │
│ │
│ const response = await fetch("/api/chat-completion", { │
│ method: "POST", // 关键点:这里用了 POST │
│ headers: { "Content-Type": "application/json" }, │
│ body: JSON.stringify({ │
│ prompt: "写一篇关于 SSE 的长文..." // 发送长数据 │
│ }) │
│ }); │
│ │
│ // 优势:可以使用 POST,发送大量 JSON 数据 │
│ // 劣势:需要自己写代码处理流,没有自动重连 │
│ const reader = response.body.getReader(); │
│ const decoder = new TextDecoder(); │
│ │
│ while (true) { │
│ const { done, value } = await reader.read(); │
│ if (done) break; │
│ const chunk = decoder.decode(value); │
│ // 按照 SSE 格式解析流数据... │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────┘核心区别:
- 标准 SSE:使用
EventSourceAPI,服务端推送,客户端只收不发(或只发少量 URL 参数) - Fetch Streaming:使用
fetch()+ 手动解析流,客户端可以 POST 大量数据,服务端流式响应
本质理解: "Fetch Streaming 模拟 SSE" 的本质是:使用 Fetch API 的 POST 请求向服务端发送数据,然后通过 response.body.getReader() 逐块读取服务端的流式响应(ReadableStream),并按照 SSE 的格式去解析它。
这就是为什么现在的 AI 聊天应用(如 ChatGPT、Claude)在控制台的网络面板里看到的都是 POST 请求,但响应类型却是流(text/event-stream)。
2.2 SSE 协议格式
http
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
X-Accel-Buffering: no # Nginx 禁用缓冲┌─────────────────────────────────────────────────────────────────────────┐
│ SSE 消息格式 │
├─────────────────────────────────────────────────────────────────────────┤
│ 字段 │ 格式 │ 说明 │
├─────────────────────────────────────────────────────────────────────────┤
│ data │ data: 内容\n │ 消息内容 (必须) │
│ event │ event: 事件名\n │ 自定义事件名 (默认 message) │
│ id │ id: 123\n │ 消息 ID, 用于断线重连 │
│ retry │ retry: 3000\n │ 重连间隔 (毫秒) │
│ 注释 │ : 这是注释\n │ 心跳保活常用 │
├─────────────────────────────────────────────────────────────────────────┤
│ ⚠️ 每条消息必须以 \n\n (空行) 结尾 │
└─────────────────────────────────────────────────────────────────────────┘2.3 EventSource 客户端
javascript
// 基础用法 (仅支持 GET)
const eventSource = new EventSource('/api/stream');
eventSource.onopen = () => {
console.log('✅ 连接已建立');
};
eventSource.onmessage = (event) => {
if (event.data === '[DONE]') {
eventSource.close();
return;
}
const { content } = JSON.parse(event.data);
document.getElementById('output').textContent += content;
};
eventSource.onerror = (error) => {
if (eventSource.readyState === EventSource.CLOSED) {
console.log('❌ 连接已关闭');
} else {
console.log('⚠️ 连接错误, 正在重连...');
// EventSource 会自动重连
}
};
// 主动关闭
function stopGeneration() {
eventSource.close();
}2.4 EventSource 局限性
❌ 只支持 GET 请求 → 无法 POST prompt
❌ 无法自定义 Header → 无法携带 Authorization
❌ HTTP/1.1 同域最多 6 连接 → 多 SSE 会阻塞请求
🔧 解决方案: 使用 Fetch API + ReadableStream2.5 💡 核心认知总结
┌─────────────────────────────────────────────────────────────────────────┐
│ SSE 核心认知框架 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ❌ 常见误解: │
│ "SSE 不支持 POST" │
│ "SSE 只能用 EventSource" │
│ │
│ ✅ 正确理解: │
│ │
│ 1. SSE 是响应格式,不是请求方式 │
│ • 本质:Content-Type + data: 格式 │
│ • 与 HTTP 方法(GET/POST/PUT)无关 │
│ │
│ 2. EventSource 的限制 ≠ SSE 的限制 │
│ • EventSource 是浏览器 API,有硬编码限制 │
│ • 服务端完全可以用 POST 接收,返回 SSE 格式 │
│ │
│ 3. 服务端视角的统一模型 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 入口 │ → │ 业务逻辑 │ → │ 出口 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ GET vs POST (完全相同) SSE 响应头 │
│ 参数来源不同 data: 格式 │
│ │
│ 4. 实战选择 │
│ • 简单通知 → EventSource (GET) │
│ • LLM 对话 → Fetch Streaming (POST) │
│ • 双向通信 → WebSocket │
│ │
└─────────────────────────────────────────────────────────────────────────┘