sse连接需服务端每30秒发送:keepalive注释重置超时,避免浏览器静默关闭;须处理nginx超时、框架缓冲、cors凭证、cookie安全属性及Error事件误判等四层问题。

html5 SSE 连接断开前没发心跳,浏览器自动关闭连接
服务端不主动发送数据,SSE 连接通常在 30–60 秒后被浏览器静默关闭(chrome/firefox 表现一致),这不是 bug,是规范行为。SSE 协议要求服务端定期发送注释行或空事件来重置超时计时器。
- 最简方案:服务端每
30秒发一条:keepalive注释(以:开头,浏览器忽略但重置心跳) - 别用
data:发空值——部分浏览器(如 safari)不认data:nn为有效事件,仍会断连 - Node.js express 示例中,别在
res.write()后漏掉nn(两个换行符),否则事件不完整,浏览器无法解析 - Nginx 默认
proxy_read_timeout是60秒,若后端心跳间隔 >60s,Nginx 会先于浏览器断开连接
用 EventSource 监听 error 事件不等于连接失败
error 事件触发频率高,且可能由网络抖动、短暂服务不可达或单次消息解析失败引发,并非都代表连接永久中断。盲目在 error 回调里立即 new EventSource() 会导致请求风暴。
- 检查
eventSource.readyState:只有0(closed)才需重建;0以外的error大概率正在自动重连 - 服务端返回非
200状态码(如503)时,EventSource会触发error并停止重试——此时必须手动恢复 - 重连间隔建议用指数退避:
math.min(1000 * 2 ** attempt, 30000),避免压垮服务端
服务端流式响应被框架缓冲,心跳延迟或丢失
很多 Web 框架(如 Express、flask、spring Boot)默认启用响应体缓冲,导致即使你调用了 write(),数据也卡在内存里不下发,心跳失效。
- Express 中必须设置
res.flushHeaders()+res.socket.setNoDelay(true),并禁用压缩:res.removeHeader('Content-Encoding') - Flask 需用
yield+Response(..., content_type='text/event-stream'),且禁用 Werkzeug 的Response自动缓冲(加direct_passthrough=True) - spring boot 的
SseEmitter要调用emitter.send(SseEmitter.event().name("heartbeat").data("")),不能只写原始流
跨域场景下 withCredentials 不生效,心跳携带不了 Cookie
前端用 new EventSource(url, { withCredentials: true }),但服务端没配 access-Control-Allow-Credentials: true 或 Access-Control-Allow-Origin 写死 *,会导致凭证被丢弃,后续心跳请求无登录态。
立即学习“前端免费学习笔记(深入)”;
-
Access-Control-Allow-Origin不能是*,必须显式写出请求来源域名(如https://example.com) - 如果服务端用 Nginx 做 CORS,注意
add_header默认不继承,需在location块内重复声明 - Cookie 的
SameSite=None和Secure属性必须同时设置,否则 Chrome 84+ 拒绝发送
服务端心跳不是“发个空行”就完事,它卡在协议层、框架层、代理层、浏览器层四道关卡上,少一个环节,连接就在你以为稳定的时候悄无声息断掉。