gin/echo默认中间件会静默破坏sse:gzip重写content-type致浏览器拒绝解析,cors提前writeheader覆盖关键头,日志中间件阻塞flush;必须用无中间件路由、每次消息后立即f.flush()并检查error,配合心跳和超时控制防断连。

Go 标准库和主流 Web 框架(如 Gin、Echo)都能支持 SSE,但必须绕过中间件干扰、手动控制 flush、严格遵循事件格式——漏掉任一环节,前端 EventSource 就卡在 connecting 状态,且不报错。
为什么 Gin/Echo 的默认 handler 会静默失败
Gin 和 Echo 默认启用 gzip 中间件、日志中间件、CORS 中间件,这些都会篡改或覆盖关键响应头:
-
Content-Type: text/event-stream被 gzip 中间件重写为Content-Encoding: gzip,浏览器直接拒绝解析 - CORS 中间件可能提前调用
WriteHeader,导致你后续设置的Cache-Control或Connection头被忽略 - 日志中间件在 handler 返回后才写日志,但 SSE 要求连接长期存活,它反而可能阻塞
Flush()
正确做法是:单独注册一个无中间件的路由,例如 Gin 中用 r.NoRoute() 或新建一个 gin.New() 实例专跑 SSE;Echo 中用 e.GET("/stream", sseHandler) 并确保没挂任何 middleware。
如何安全地写入并刷新每条 SSE 消息
不能依赖 defer f.Flush(),也不能只在 handler 结束时 flush——那等于没流式推送。每次写完一条完整事件(以 \n\n 结尾),必须立刻 f.Flush():
- 先做类型断言:
f, ok := w.(http.Flusher),失败就返回 500 - 每条消息用
fmt.Fprintf(w, "data: %s\n\n", msg),注意结尾是两个换行符 - 写完立即调
f.Flush(),并检查 error:if err != nil && !errors.Is(err, net.ErrClosed) && !errors.Is(err, syscall.EPIPE) - 别用
json.Marshal直接塞进data:字段——中文、双引号、换行都会破坏格式;应先strings.ReplaceAll(msg, "\n", "\ndata: ")再拼
怎么防 Nginx / ALB / Cloudflare 断连
纯写 data: 不够,空闲超时是最大杀手。真实链路中,Nginx 默认 proxy_read_timeout 60s,ALB 是 60–3600s 可配,但 Go 默认 http.Server.WriteTimeout 也会在几分钟后静默关连接:
- Go 侧:设
srv := &http.Server{WriteTimeout: 0, ReadHeaderTimeout: 10 * time.Second} - Nginx 侧:在 location 块里加
proxy_read_timeout 300;、proxy_buffering off;、proxy_cache off; - 服务端每 25 秒发一次心跳:
fmt.Fprint(w, ":heartbeat\n\n")(冒号开头的行是注释,客户端忽略,但能保活) - 别用
r.Context().Done()唯一判断断连——HTTP/1.1 下它常滞后;f.Flush()返回write: broken pipe才是第一手信号
用 channel 管理连接时 goroutine 泄露怎么防
每个 SSE 连接启一个 goroutine 监听 channel,但客户端关了、网络断了、超时了,goroutine 就永远卡在 for msg := range ch 里:
- 给每个连接绑定带 timeout 的 context:
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute) - 监听
ctx.Done()和 channel 关闭两个信号,用select退出循环 - channel 关闭前,从全局 map 中删掉该 client,并 close(ch),避免向已关闭 channel 发送 panic
- 别用无缓冲 channel 接收广播消息——高并发下容易阻塞 flush;用带缓冲 channel(如
make(chan string, 64))
最易被忽略的一点:所有错误检查都得落在 f.Flush() 后,而不是 fmt.Fprintf 后——因为数据还在缓冲区,真正失败发生在刷出网卡那一刻。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











