echo默认路由静默卡住sse连接,因其默认启用的gzip、cors、logger中间件会篡改content-type、提前writeheader、阻塞flush;必须用无中间件路由、每次写完立即f.flush()并检查error,配合心跳与nginx proxy_buffering off等配置。

为什么Echo默认路由会静默卡住SSE连接
因为Echo默认启用的gzip、CORS、logger中间件会破坏SSE关键响应头和流式写入节奏。gzip中间件会把Content-Type: text/event-stream偷偷改成Content-Encoding: gzip,浏览器直接拒绝解析;CORS中间件可能提前调用WriteHeader(),导致你后面写的Cache-Control或Connection头被忽略;logger中间件在handler返回后才刷日志,反而阻塞Flush()调用。
必须绕过所有中间件——不是“关掉”,是彻底不挂载:
- 用
e.GET("/stream", sseHandler)注册路由,确保没套任何Use() - 不要复用带中间件的
echo.Echo实例,必要时新建一个干净实例专跑SSE - 检查
echo.Debug是否开启,它会注入额外日志逻辑,干扰flush时机
如何正确写出每条SSE消息并强制刷新
Go标准库对流式响应极其敏感:漏一次Flush(),前端就永远停在connecting状态,且不报错。
核心三步不能少:
- 先做类型断言:
f, ok := c.Response().Writer.(http.Flusher),失败直接c.String(500, "flusher not supported") - 每条消息严格按格式写:
fmt.Fprintf(c.Response().Writer, "data: %s\n\n", msg),结尾必须是两个\n,不能用fmt.Println()或拼接PHP_EOL - 写完立刻
f.Flush(),并检查error:if err != nil && !errors.Is(err, net.ErrClosed) && !errors.Is(err, syscall.EPIPE),其他错误要记录并中断连接
Nginx代理下首条消息发不出的真正原因
即使Echo代码完全正确,Nginx默认配置也会吞掉第一条消息——因为它等64KB或响应结束才转发,而SSE响应永不结束。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
必须在location块里加这三行:
proxy_buffering off;proxy_cache off;-
proxy_read_timeout 86400;(别用默认60秒)
同时补上add_header X-Accel-Buffering "no";,防止Nginx内部缓冲层二次拦截。
心跳和超时控制为什么不能省略
Cloudflare、某些企业防火墙、甚至部分运营商NAT设备,会在连接空闲30–60秒后主动断开HTTP长连接。SSE协议本身不定义心跳,但data:\n\n这种空事件能有效维持连接活跃。
建议每25秒发一次:
- 用
time.Ticker驱动,别依赖业务逻辑间隔 - 心跳消息必须带
id:字段,例如fmt.Fprintf(w, "id: %d\n\n", time.Now().Unix()),否则Last-Event-ID无法延续 - 客户端
EventSource的retry值建议设为3000,避免指数退避导致重连延迟飙升
真正的难点不在写数据,而在让数据穿过PHP/Go/Nginx/CDN四层缓冲不被截断、不被压缩、不被超时杀掉。每个环节都得单独验证,缺一不可。










