echo实现sse的关键是手动干预响应头、缓冲控制和连接生命周期:需提前设置text/event-stream等响应头,禁用gzip与缓冲,显式writeheader并每事件后flush,加心跳防超时断连。

直接用 Echo 实现 SSE,关键不是“能不能”,而是响应头、缓冲控制和连接生命周期这三处必须手动干预;默认 echo.Context 的 Write 和 Flush 行为会破坏 text/event-stream 协议要求的流式输出节奏,不处理就会卡住或报 ERR_INVALID_CHUNKED_ENCODING。
设置正确的响应头与禁用默认缓冲
Echo 默认启用 gzip 和响应缓冲,而 SSE 要求:响应头必须在任何内容写出前就发送,且不能被中间层(如代理、gzip)截断或合并 chunk。否则浏览器解析失败,出现空白响应或连接立即关闭。
实操建议:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 调用
c.Response().Header().Set("Content-Type", "text/event-stream"),再加c.Response().Header().Set("Cache-Control", "no-cache")和c.Response().Header().Set("Connection", "keep-alive") - 必须调用
c.Response().Writer.WriteHeader(http.StatusOK)显式触发 header 发送,不能依赖 Echo 自动写状态码 - 禁用 gzip:
c.Response().Writer.(http.Hijacker)不够,更稳妥的是在 Echo 启动时全局禁用:e.Use(middleware.GzipWithConfig(middleware.GzipConfig{Skipper: func(c echo.Context) bool { return strings.Contains(c.Request().Header.Get("Accept"), "text/event-stream") }}))
手动控制 flush 与保持连接活跃
SSE 连接一旦建立,客户端期望持续收到以 data: 开头、结尾带双换行 \n\n 的消息块。Echo 的 c.Response().Writer 是 http.ResponseWriter,但底层可能包装了缓冲 writer;不显式 Flush(),数据就卡在内存里不出去。
实操建议:
- 获取原始
http.ResponseWriter:用rw := c.Response().Writer,然后断言为http.Flusher,确认支持Flush() - 每写完一个完整事件(
data: ...\n\n),立刻调用rw.(http.Flusher).Flush();不要等函数返回才 flush - 为防代理或负载均衡器超时断连,建议每 15–30 秒发一次空事件(
:\n\n)作为心跳,而不是依赖retry:字段——后者只影响重连间隔,不保活当前连接
处理客户端断连与 context 取消
Echo 的 c.Request().Context().Done() 在客户端关闭连接时会关闭,但这个信号不一定及时触发;尤其在反向代理后,TCP FIN 可能延迟到达,导致 goroutine 泄漏。
实操建议:
- 在事件循环中始终检查
select { case ,并在退出前清理资源(如关闭下游流、释放锁) - 避免用
time.Sleep阻塞主循环;改用time.AfterFunc或带 timeout 的 channel 操作,防止 goroutine 卡死 - 如果后端是调用另一个流式 API(如 LLM 接口),需确保下游
io.ReadCloser在 context 取消时能被正确关闭——Echo 本身不帮你做这件事
event:id、retry 和自定义 event 类型的实际作用
这些字段不是可选装饰,而是协议级功能点。浏览器靠 id: 记录最后接收的消息序号,断线重连时通过 Last-Event-ID 请求头带过去;retry: 告诉浏览器下次重试间隔(毫秒),但仅当连接异常断开时生效;event: 允许前端用 addEventListener('xxx', ...) 绑定特定类型事件。
实操建议:
-
id:必须是单调递增或唯一字符串,别用时间戳(并发下可能重复);推荐用原子计数器或 UUID -
retry:写一次即可(首次响应头里或第一个事件块中),浏览器会记住;设太小(如 100)会导致频繁无效重连 - 前端监听非默认事件时,必须确保服务端每次发对应
event:xxx,否则onmessage不会触发——这是最常被忽略的兼容性坑
真正难的不是写出第一个 data:,而是让成百上千个长连接稳定跑满 30 分钟以上:网络抖动、代理超时、客户端休眠、后台流中断……这些场景下,仅靠 Echo 默认行为撑不住,必须每一层都主动接管控制权。










