Skip to content

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()

readyStateCONNECTING(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
  • 自动重连不等于消息可靠送达;需要可靠性时,使用 idLast-Event-ID 和服务端事件存储补发。
  • 同一域名的并发连接数在 HTTP/1.1 下有限;大量标签页或高并发场景优先启用 HTTP/2,并评估连接与内存占用。
  • EventSource 的 URL 可能出现在日志中,不应放入长期有效的敏感令牌。

与 WebSocket 的选择

需求选择
服务端单向推送、自动重连、实现简单SSE
聊天、协作编辑、实时控制等双向通信WebSocket
需要自定义请求头或 POST 流式请求fetch + ReadableStream

参阅

基于 MIT 许可发布