Go 的 http.ResponseWriter 不能直接写 SSE 数据,因其默认缓冲且连接易被关闭;需设 text/event-stream 类型、禁用缓存、手动 Flush、防断连并避免 goroutine 泄漏。

Go 的 http.ResponseWriter 为什么不能直接写 SSE 数据?
因为 SSE 要求服务端保持连接、持续写入带 data: 前缀的纯文本流,而 Go 默认的 http.ResponseWriter 在 Write 后可能被缓冲或提前关闭。一旦响应体写完或连接超时,流就断了——这不是浏览器问题,是服务端没按 SSE 协议维持长连接。
- 必须显式设置
Header().Set("Content-Type", "text/event-stream")和Header().Set("Cache-Control", "no-cache") - 必须禁用 HTTP/2 的流复用干扰:在
http.Server中设IdleTimeout和ReadTimeout,避免连接被中间代理(如 Nginx)或 Go 自身 idle 机制悄悄关掉 - 每次写入后要调用
Flush(),否则数据卡在缓冲区;但别用bufio.Writer包裹它——ResponseWriter已自带缓冲,再套一层反而延迟更严重
用 net/http 实现 SSE 时怎么处理客户端断连?
Go 不会主动通知你客户端已关页或网络中断,Write 或 Flush 失败时才暴露问题。这时候如果还往死里推,会卡 goroutine、积压内存。
- 每次
Write后检查err:若为io.ErrClosedPipe、http.ErrHandlerTimeout或write: broken pipe,说明客户端已掉线,应立即break循环并 return - 别依赖
context.Done()判断断连——HTTP/1.1 下它常滞后于实际断开,尤其在移动端弱网场景 - 加个简单心跳:每 15 秒写一次
:\n\n(SSE 注释行),既防代理超时,又能快速探测连接是否存活
gorilla/websocket 能不能拿来当 SSE 用?
不能。WebSocket 是双向全双工协议,SSE 是单向服务器推送,两者语义、帧格式、浏览器 API 完全不兼容。硬把 WebSocket 消息塞成 data: 格式,前端用 EventSource 根本收不到。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 浏览器只认
new EventSource(url)发起的 GET 请求,且要求响应头含Content-Type: text/event-stream -
gorilla/websocket返回的是101 Switching Protocols,和 SSE 的 200 + 流式文本冲突 - 如果真想统一后端推送通道,应该用 WebSocket + 自定义消息协议,前端改用
WebSocket对象,而不是强行模拟 SSE
并发推送时怎么避免 goroutine 泄漏?
每个 SSE 连接对应一个长生命周期 goroutine,如果用户刷新页面却不触发断连检测,旧 goroutine 就一直挂着,内存和 fd 持续上涨。
立即学习“go语言免费学习笔记(深入)”;
- 给每个连接 goroutine 加上
context.WithTimeout(ctx, 5*time.Minute),超时自动退出(注意:不是等客户端断开,而是兜底保护) - 用
sync.Map存活连接,key 为连接 ID(比如 session token 或随机 uuid),在 handler 开始时注册,defer 中清理 - 推送逻辑别直接
go func() { ... }()启动新 goroutine——SSE 是“一个连接一个 goroutine”,推送动作应在该连接专属 goroutine 内同步完成
最麻烦的其实是连接管理本身:没有标准库帮你做广播、分组、重连状态同步。这些得自己靠 channel + select 控制,稍不注意就会漏消息或重复发。别指望靠一个 for range 循环搞定所有事。

















