什么是SSE
- SSE是Server-Sent Events的简称,也会称为EventSource。
- Server-Sent Event 技术的作用是可以完成从服务器端到客户端(浏览器)单向的消息传送。因此我们可以用这个来做推送。
- SSE适用于更新频繁、低延迟并且数据都是从服务端到客户端。
客户端请求头要求
-
Accept: text/event-stream- 表明客户端期望接收事件流格式的数据
- 服务器必须返回此 MIME 类型
-
Connection: keep-alive(隐含必需)- HTTP/1.1 默认开启 keep-alive,但显式声明更可靠
- 保持 TCP 连接长期开放,用于持续发送事件
-
Cache-Control: no-cache(强烈推荐)- 防止中间代理缓存事件流
- 避免客户端获取过期事件
-
Last-Event-ID(可选)- 客户端重连时发送上次收到的最后一个事件ID
- 服务器据此决定发送哪些新事件
典型 SSE 请求头示例:
GET /updates HTTP/1.1
Host: api.example.com
Accept: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Last-Event-ID: 12345
服务器响应要求
-
响应头:
HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive Transfer-Encoding: chunked-
Content-Type: text/event-stream必须设置 - 建议禁用缓存(
no-store比no-cache更严格) - 保持连接开放(HTTP/2 自动处理持久连接)
-
-
响应体格式:
event: message\n id: 123\n retry: 3000\n data: {"user":"John","action":"login"}\n \n- 事件字段 (
event,id,data,retry) 以field: value格式 - 每个事件以两个换行符
\n\n结束(必须) - 字段顺序无关紧要,但
data可以多次出现(会被连接)
- 事件字段 (
事件格式规范
| 字段 | 描述 | 示例 |
|---|---|---|
data |
必需字段,事件内容(可多次出现) | data: Hello\n data: World\n |
id |
事件ID,用于断线重连时定位 | id: 12345\n |
event |
事件类型(默认 message) |
event: statusUpdate\n |
retry |
重连时间(毫秒),建议 >= 1000ms | retry: 3000\n |
: |
注释行(服务器发送,客户端忽略) | : ping\n |
完整事件流示例
: 连接建立\n
\n
event: userConnect\n
id: 1001\n
data: {"id": "user-123", "time": "2023-08-15T10:00:00Z"}\n
\n
data: 系统通知\n
data: 服务器将在5分钟后维护\n
id: 1002\n
\n
event: stockUpdate\n
id: 1003\n
data: {"symbol":"AAPL","price":182.91}\n
\n
关键协议要求
-
分隔符要求:
- 每个事件必须以
\n\n结束 - 字段行以
\n结束 - 空事件 (
\n\n) 是有效的心跳包
- 每个事件必须以
-
编码要求:
- 必须使用 UTF-8 编码
- 不支持二进制数据(需 base64 编码)
-
连接管理:
- 服务器不应主动关闭连接(除非错误)
- 客户端断开后应自动重连
- 建议设置 15-30 秒心跳机制
服务器端实现注意事项
-
框架支持:
// Node.js (Express) app.get('/events', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-store'); const timer = setInterval(() => { res.write(`data: ${JSON.stringify({time: Date.now()})}\n\n`); }, 1000); req.on('close', () => clearInterval(timer)); }); -
防超时配置:
- Nginx:
proxy_read_timeout 24h; proxy_buffering off; - Apache:
TimeOut 86400
- Nginx:
-
心跳机制:
# Flask @app.route('/stream') def stream(): def generate(): while True: yield ': heartbeat\n\n' # 每15秒发送 time.sleep(15) return Response(generate(), mimetype='text/event-stream')
调试工具
-
客户端验证:
curl -H "Accept: text/event-stream" https://api.example.com/events -
协议检查点:
mermaid.pnggraph TD A[客户端发起请求] --> B{请求头包含<br>Accept: text/event-stream?} B -->|是| C[服务器响应200] B -->|否| D[返回406 Not Acceptable] C --> E{响应头包含<br>Content-Type: text/event-stream?} E -->|是| F[持续发送事件] E -->|否| G[客户端断开连接] F --> H{事件以\n\n结束?} H -->|是| I[客户端处理事件] H -->|否| J[协议错误]
常见问题处理
-
连接过早关闭:
- 服务器:检查防火墙/代理超时设置
- 客户端:实现自动重连
- 解决方案:添加心跳事件
-
编码问题:
- 确保服务器使用 UTF-8
- 特殊字符需转义:
func escapeSSEString(_ string: String) -> String { return string .replacingOccurrences(of: "\n", with: "\\n") .replacingOccurrences(of: "\r", with: "\\r") }
-
跨域问题:
Access-Control-Allow-Origin: * Access-Control-Expose-Headers: Last-Event-ID
SSE 协议的设计简洁而强大,核心在于保持 HTTP 连接开放并通过严格的 \n\n 分隔符实现事件流传输。正确实现后,可支持实时更新、通知推送等多种场景。
