主题
SSE(Server-Sent Events)
SSE 是基于 HTTP 的服务端单向推送机制:客户端发起一次长连接请求,服务端持续写入事件;客户端仍可通过普通 HTTP 请求向服务端提交数据。
适合通知、任务进度、行情、日志与 AI 文本流。需要低延迟双向消息时,使用 WebSocket。
事件格式
响应类型必须是 text/event-stream。一条事件由若干字段组成,并以空行结束:
text
id: 42
event: progress
retry: 3000
data: {"percent":50}data:事件数据;多行data会在客户端以换行拼接。event:可选的自定义事件名;未指定时触发message。id:事件 ID;重连时浏览器会携带Last-Event-ID,可用于断点续传。retry:建议的重连间隔,单位为毫秒。- 以
:开头的行是注释,常用于心跳。
浏览器客户端
EventSource 仅支持 GET,不能自定义请求头或请求体;跨域时服务端需要正确配置 CORS。需要 Bearer Token、POST 请求或更细粒度流控制时,使用 fetch 读取响应流。
js
const source = new EventSource('/api/events', { withCredentials: true })
source.addEventListener('progress', (event) => {
const { percent } = JSON.parse(event.data)
console.log(`进度:${percent}%`)
})
source.onmessage = (event) => {
console.log('默认事件:', event.data)
}
source.onerror = () => {
// 非正常断开时 EventSource 会自动重连;关闭后不会重连。
console.warn('SSE 连接中断或正在重连')
}
source.close()readyState:CONNECTING(0)、OPEN(1)、CLOSED(2)。调用 close() 后需要新建实例才能再次连接。
Node.js 服务端
js
import express from 'express'
const app = express()
app.get('/api/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache, no-transform',
'Connection': 'keep-alive',
})
res.flushHeaders()
// 代理和负载均衡器可能有空闲超时;定时发送注释保持连接。
const heartbeat = setInterval(() => res.write(': ping\n\n'), 15_000)
let id = 0
const timer = setInterval(() => {
id += 1
res.write(`id: ${id}\n`)
res.write('event: progress\n')
res.write(`data: ${JSON.stringify({ percent: id % 101 })}\n\n`)
}, 1_000)
req.on('close', () => {
clearInterval(timer)
clearInterval(heartbeat)
res.end()
})
})
app.listen(3000)使用要点
- 事件必须以
\n\n结束,否则浏览器不会派发。 - 长连接不要被响应压缩或代理缓冲。Nginx 反向代理需关闭
proxy_buffering。 - 自动重连不等于消息可靠送达;需要可靠性时,使用
id、Last-Event-ID和服务端事件存储补发。 - 同一域名的并发连接数在 HTTP/1.1 下有限;大量标签页或高并发场景优先启用 HTTP/2,并评估连接与内存占用。
EventSource的 URL 可能出现在日志中,不应放入长期有效的敏感令牌。
与 WebSocket 的选择
| 需求 | 选择 |
|---|---|
| 服务端单向推送、自动重连、实现简单 | SSE |
| 聊天、协作编辑、实时控制等双向通信 | WebSocket |
| 需要自定义请求头或 POST 流式请求 | fetch + ReadableStream |
