Skip to content

SSE (Server-Sent Events)

服务器推送事件,单向实时通信

2. SSE (Server-Sent Events)

2.1 ⚠️ 核心理解:SSE 的本质是什么

在深入技术细节前,先理解一个关键事实:SSE 的本质是一种响应格式,而不是一种请求方式。

SSE 的技术本质

从服务端视角看,SSE 只包含两个核心要素:

  1. 响应头Content-Type: text/event-stream
  2. 数据格式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仅 GETEventSource简单通知、公开数据
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:使用 EventSource API,服务端推送,客户端只收不发(或只发少量 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 + ReadableStream

2.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                                             │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

前端面试知识库