Server-Sent Events(SSE)详解:轻量级服务端推送方案
适用场景:实时通知、AI 流式输出、日志监控、股票行情等 单向文本流推送
关键词:SSE、EventSource、服务端推送、流式响应、HTTP 长连接
一、什么是 SSE?
SSE(Server-Sent Events) 是一种基于 HTTP 的 单向服务器推送技术,允许服务端主动向浏览器客户端持续发送文本数据流。
- ✅ 标准 Web API:浏览器原生支持
EventSource对象 - ✅ 自动重连:断开后自动尝试恢复连接
- ✅ 轻量简单:比 WebSocket 更易实现,比轮询更高效
- ❌ 仅支持文本:不能传输二进制数据
- ❌ 单向通信:只能服务端 → 客户端(不能反向发)
💡 典型应用:ChatGPT 网页版早期流式回复、实时构建日志、系统状态推送
二、SSE vs 其他通信方式对比
| 特性 | SSE | WebSocket | 轮询(Polling) |
|---|---|---|---|
| 通信方向 | 单向(服务端 → 客户端) | 双向 | 客户端 → 服务端 |
| 协议 | HTTP/1.1 | ws/wss(独立协议) | HTTP |
| 实现复杂度 | ⭐ 简单 | ⭐⭐⭐ 复杂 | ⭐ 简单 |
| 自动重连 | ✅ 内置支持 | ❌ 需手动实现 | N/A |
| 二进制支持 | ❌ 仅文本 | ✅ 支持 | ✅(但低效) |
| 浏览器兼容 | IE 不支持,现代浏览器全支持 | 现代浏览器支持 | 全支持 |
| 适用场景 | 实时通知、流式文本 | 聊天、游戏、协作编辑 | 简单状态同步 |
✅ 结论:若只需 服务端推文本,优先选 SSE!
三、SSE 工作原理
1. 客户端发起请求
const eventSource = new EventSource('/api/stream');
2. 服务端返回特殊响应头
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
3. 服务端持续发送消息(以 \n\n 结尾)
data: 消息1event: update
data: {"status":"running"}id: 1001
data: 带ID的消息retry: 5000
data: 重连间隔5秒
4. 客户端自动接收并触发事件
四、SSE 消息格式规范
每条消息由若干字段组成,必须以 \n\n 结尾:
| 字段 | 说明 | 示例 |
|---|---|---|
data: | 消息内容(必填) | data: hello |
event: | 事件类型(可选,默认 message) | event: update |
id: | 消息 ID(用于断线重连) | id: 1001 |
retry: | 重连间隔(毫秒) | retry: 3000 |
⚠️ 注意:
- 每行以
\n结尾;- 多行
data会被拼接;- 以
:开头的行为注释(不触发事件),如: ping\n\n
五、前端使用:EventSource API
基础用法
const es = new EventSource('/api/stream');// 默认事件(无 event 字段 或 event: message)
es.onmessage = (event) => {console.log('收到:', event.data);
};// 监听自定义事件
es.addEventListener('update', (event) => {const data = JSON.parse(event.data);console.log('状态更新:', data);
});// 连接建立
es.onopen = () => console.log('SSE 连接成功');// 错误处理(含自动重连失败)
es.onerror = (err) => console.error('SSE 错误:', err);// 手动关闭
es.close();
带认证的请求
⚠️
EventSource不支持自定义请求头!
解决方案:通过 URL 参数或 Cookie 传递 token
// 方式1:URL 参数
const es = new EventSource('/api/stream?token=xxx');// 方式2:同域 Cookie(自动携带)
const es = new EventSource('/api/stream');// 方式3:自定义客户端
自定义一个SSE客户端,自定定义数据协议// 方式4:使用现成的js库
event-source-polyfill
(https://www.npmjs.com/package/event-source-polyfill)
六、服务端实现示例
Node.js + Express
import express from 'express';
import cors from 'cors';const app = express();
app.use(cors({ origin: 'http://localhost:5173' }));app.get('/api/stream', (req, res) => {res.setHeader('Content-Type', 'text/event-stream');res.setHeader('Cache-Control', 'no-cache');res.setHeader('Connection', 'keep-alive');res.flushHeaders(); // 立即发送响应头let count = 0;const interval = setInterval(() => {const msg = JSON.stringify({ time: new Date().toLocaleTimeString() });res.write(`event: update\ndata: ${msg}\n\n`);if (++count >= 5) {clearInterval(interval);res.end();}}, 1000);req.on('close', () => clearInterval(interval));
});app.listen(3000);
Python + Flask
from flask import Flask, Response
import json
import timeapp = Flask(__name__)@app.route('/stream')
def stream():def generate():for i in range(5):yield f"event: update\ndata: {json.dumps({'count': i})}\n\n"time.sleep(1)return Response(generate(), mimetype='text/event-stream')
七、Vue 3 中集成 SSE(Composition API)
<script setup>
import { ref, onUnmounted } from 'vue'const messages = ref([])
let es = nullconst connect = () => {es = new EventSource('/api/stream')es.onmessage = (e) => messages.value.push(e.data)es.addEventListener('update', (e) => {messages.value.push(JSON.parse(e.data))})es.onerror = () => {console.error('SSE 连接失败')es?.close()}
}onUnmounted(() => es?.close())
</script><template><button @click="connect">连接 SSE</button><div v-for="(msg, i) in messages" :key="i">{{ msg }}</div>
</template>
八、常见问题与解决方案
❌ 1. 跨域(CORS)被拦截
现象:请求失败,控制台报 CORS 错误
解决:服务端添加 CORS 头
app.use(cors({ origin: 'http://localhost:5173' }))
或使用 Vite 代理(开发环境):
// vite.config.ts
export default defineConfig({server: {proxy: {'/api': 'http://localhost:3000'}}
})
❌ 2. Safari 缓冲小 chunk
现象:消息延迟到达
解决:每条消息 ≥1KB,或加注释行:
res.write('data: small chunk\n\n');
res.write(': keep-alive\n\n'); // 注释行,不触发事件
❌ 3. require is not defined(Node.js ESM 错误)
原因:混用 import 和 require
解决:统一使用 ES 模块
// package.json
{ "type": "module" }
// server.js
import express from 'express'; // ✅ 正确
// const express = require('express'); // ❌ 错误
九、最佳实践建议
| 场景 | 建议 |
|---|---|
| 开发环境 | 使用 Vite/Webpack 代理避免 CORS |
| 生产环境 | Nginx 反向代理 + 后端 CORS |
| 认证 | 通过 URL 参数或 Cookie 传 token |
| 消息格式 | 使用 JSON,便于解析 |
| 错误处理 | 监听 onerror,避免无限重连 |
| 资源清理 | 组件卸载时调用 eventSource.close() |
十、总结
SSE = 简单 + 高效 + 原生支持 的服务端推送方案。
当你需要 从服务端向浏览器持续推送文本数据 时,SSE 是比轮询更优、比 WebSocket 更轻量的选择。
✅ 记住三要素:
- 服务端设置
Content-Type: text/event-stream - 消息格式符合 SSE 规范(
data:+\n\n) - 前端用
EventSource监听事件
📚 延伸阅读
- MDN: Server-Sent Events
- HTML Standard: EventSource
