EventSource 必须配对正确的服务端响应头和数据格式,否则连接会静默失败或卡在 pending 状态;根本原因是服务端未按 SSE 协议发送数据,如响应头缺失、顺序错乱或 res.write() 先于 res.writeHead() 执行。

EventSource 能直接用,但必须配对正确的服务端响应头和数据格式,否则连接会静默失败或卡在 pending 状态。
为什么 EventSource 连接一直 pending 或报错 ERR_INVALID_CHUNKED_ENCODING
根本原因在于服务端没按 SSE 协议发数据:响应头缺失、顺序错乱,或 res.write() 在 res.writeHead() 之前执行。Node.js 中尤其容易因异步逻辑导致写入早于响应头设置。
- 必须在第一次
res.write()前调用res.writeHead(200, { 'Content-Type': 'text/event-stream', ... }) -
Cache-Control: no-cache和Connection: keep-alive缺一不可,否则 Chrome 可能缓存响应或提前关闭连接 - PHP 用户注意:
ob_flush()和flush()都要调用,且需前置header('X-Accel-Buffering: no')关闭 Nginx 缓冲 - Go(Gin)用户注意:
c.Writer.Flush()必须显式调用,否则数据滞留在缓冲区
客户端 EventSource 如何正确处理重连与错误
EventSource 自带重连机制,但默认重试间隔是 3 秒,且不会告诉你上次断连时的 last-event-id。实际项目中常需手动干预。
- 监听
onerror时不要直接console.error就完事——检查eventSource.readyState:0=connecting,1=open,0=closed;只有 readyState === 0 才算真断连 - 重连前可加退避逻辑:
eventSource.close(); setTimeout(() => new EventSource(...), 5000) - 需要断点续传?服务端得支持
id:字段,客户端初始化时传new EventSource('/events', { withCredentials: true })并在服务端解析req.headers['last-event-id'] - 避免内存泄漏:页面卸载前务必调用
eventSource.close(),尤其在单页应用中
如何在鉴权场景下绕过 EventSource 的 GET + 无请求头限制
EventSource 只支持 GET,无法带 Authorization header 或 request body,JWT 或 session 鉴权会失效。
- 临时方案:把 token 拼进 URL,如
new EventSource('/events?token=xxx'),但要注意 URL 长度限制(通常 ≤2000 字符)和日志泄露风险 - 推荐方案:改用
fetch()+ReadableStream手动解析 SSE 流,这样能自由加 header、用 POST、甚至复用现有 axios 实例 - 后端代理层可统一处理鉴权,前端仍用原生
EventSource,把认证逻辑下沉到网关或反向代理(如 Nginx 校验 cookie 后透传)
真正难的不是“怎么推”,而是“怎么确保每条消息不丢、不错序、不断连后能续上”。SSE 的协议简单,但生产环境里 header 顺序、缓冲控制、ID 管理、跨域策略这四点,漏掉任何一环都会让实时提醒变成间歇性失联。










