直接用Echo实现SSE必须手动干预三处:提前设置text/event-stream响应头、禁用gzip与缓冲、每事件后显式Flush()并加心跳;否则EventSource卡在connecting状态。

直接用 Echo 实现 SSE,三处不手动干预就卡死:响应头必须提前发、gzip 和缓冲必须禁掉、每条消息后必须显式 Flush()。否则前端 EventSource 一直停在 connecting 状态,控制台没报错,但数据就是不来。
为什么 c.String() 或 c.JSON() 不能直接用于 SSE
Echo 的 c.String()、c.JSON()、c.Render() 都会自动调用 WriteHeader() 并写入完整响应体,然后关闭连接——这和 SSE 要求的“长连接+持续流式写入”完全冲突。它们还默认启用 gzip 和中间件缓冲,导致响应头被覆盖或内容被截断。
- 调用
c.String("hello")后连接立即结束,浏览器收不到后续任何消息 -
Content-Type被中间件(如 CORS、Gzip)重写或延迟设置,text/event-stream失效 - 即使你手动设置了 header,若未显式调用
c.Response().Writer.WriteHeader(http.StatusOK),Echo 可能等到 handler 返回才发 header,此时浏览器已超时放弃
如何安全获取并使用 http.Flusher
SSE 必须每写完一个事件块(data: ...\n\n)就立刻刷新到客户端,不能等 handler 结束。而 c.Response().Writer 是个接口,底层可能不支持 Flush(),必须先断言。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 用
rw := c.Response().Writer获取原始 writer - 必须做类型断言:
f, ok := rw.(http.Flusher);如果!ok,说明当前环境(如某些测试 wrapper 或代理中间件)不支持 flush,应返回错误 - 每次写完事件后立刻调用
f.Flush(),并检查 error:if err != nil && !errors.Is(err, net.ErrClosed) && !errors.Is(err, syscall.EPIPE),其他错误需记录并退出循环 - 别用
defer f.Flush()——那只是在 handler 结束时刷一次,毫无意义
怎样防止反向代理或负载均衡器主动断连
Nginx、ALB、Cloudflare 等默认 60 秒无数据就关闭空闲连接,而 SSE 允许长时间静默。仅靠 retry: 字段无法保活,它只影响断线后的重连间隔。
- 必须在服务端每 15–30 秒发送一次心跳事件:
fmt.Fprintf(rw, ":\n\n")(注意开头是冒号,表示注释行,客户端忽略但连接保持活跃) - 不要依赖
c.Request().Context().Done()做唯一退出判断——TCP FIN 在代理后可能延迟数秒才到达,goroutine 容易泄漏 - 在事件循环中用
select同时监听:ctx.Done()、下游数据源 channel、心跳定时器,任一触发即清理资源并 return - 全局禁用 gzip 更稳妥:
e.Use(middleware.GzipWithConfig(...))中用Skipper函数匹配Accept头含text/event-stream的请求
最容易被忽略的是:所有中间件都得绕开。CORS、日志、认证中间件只要在 SSE 路由上挂了,就可能提前 WriteHeader() 或阻塞 Flush()。生产环境务必用独立路由(如 e.GET("/stream", sseHandler))且不附加任何 middleware —— 这不是可选项,是必须项。

















