echo的sse handler卡在connecting状态,根本原因是默认中间件破坏响应头与刷新时机:gzip重写content-type致浏览器拒收,cors提前writeheader覆盖关键头,日志中间件阻塞flush;必须使用无中间件路由、每次发送后立即f.flush()并检查error,配合心跳、代理超时调优及goroutine清理。

为什么Echo的SSE handler总卡在connecting状态
这不是前端代码问题,而是Echo默认中间件破坏了SSE必需的响应头和刷新时机。gzip中间件会把Content-Type: text/event-stream悄悄替换成Content-Encoding: gzip,浏览器直接拒收;CORS中间件提前调用WriteHeader(),导致你后设的Cache-Control或Connection头被忽略;日志中间件则在handler返回后才写日志,但SSE要求连接持续存活,它反而阻塞Flush()。
必须绕过所有中间件:不要用e.GET("/stream", handler)挂全局中间件的路由,而要新建一个干净的Echo实例,或用e.Any("/stream", sseHandler)并确保没注册任何middleware。
如何安全地写出第一条SSE事件并flush
漏掉Flush()是90%“没反应”问题的根源——数据卡在bufio.Writer缓冲区里,客户端永远等不到完整事件。
- 先做类型断言:
f, ok := c.Response().Writer.(http.Flusher),失败就c.AbortWithStatus(500) - 设置关键响应头(必须在
WriteHeader()前):c.Header("Content-Type", "text/event-stream")、c.Header("Cache-Control", "no-cache")、c.Header("Connection", "keep-alive") - 每条事件严格格式:
fmt.Fprintf(w, "data: %s\n\n", msg),结尾是两个换行符,不是\r\n\r\n - 写完立刻
f.Flush(),并检查error:if err != nil && !errors.Is(err, syscall.EPIPE) && !errors.Is(err, net.ErrClosed),其余错误需记录并退出goroutine
心跳、断连检测和goroutine泄漏怎么防
不发心跳,Nginx/ALB/Cloudflare会在空闲60秒左右静默断连;只依赖req.Context().Done()检测断连,在HTTP/1.1下常滞后几秒甚至完全失效。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
真实断连信号来自Flush()返回的error:
- 每25–30秒发一次心跳:
fmt.Fprint(w, ":keepalive\n\n")(冒号开头,客户端自动忽略) - 每次
f.Flush()后必须检查error,syscall.EPIPE和net.ErrClosed可安全退出goroutine - 别用
defer f.Flush()——它只在handler返回时刷一次,起不到流式作用 - 每个连接必须绑定独立goroutine + channel,channel关闭时要显式清理,否则goroutine永久泄漏
反向代理和超时配置不调大,Go代码再对也白搭
Go服务端没崩,但用户收不到推送?八成是代理层先动手了。Cloudflare默认100秒超时,Nginx默认60秒proxy_read_timeout,ALB/Traefik也有类似限制。
必须同步调整两端:
- Go侧:
http.Server的WriteTimeout和ReadTimeout设为0(禁用),改用ReadHeaderTimeout: 10 * time.Second - Nginx:
proxy_read_timeout 300,且proxy_buffering off(否则它攒着data:不往下推) - Cloudflare:进Origin Rules → Timeout → 改成300s
- ALB/Traefik:查对应文档,把idle timeout调到5分钟以上
这些配置漏掉任一环,前端EventSource就永远停在connecting,还不报错——因为问题不在协议层,而在传输链路中途被截断。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










