Go原生http.Server支持SSE,但需设Content-Type和Cache-Control响应头、每次写后调Flush()、禁用Read/WriteTimeout超时,否则连接易断、事件不触发。

Go 原生 http.Server 完全能跑通 SSE,但直接照着“写个 handler + fmt.Fprintf”就上线,90% 会卡在连接断开、事件不触发、客户端反复重连上——问题不在逻辑,而在响应头、缓冲控制、超时配置这三处硬性门槛。
Content-Type 和 Cache-Control 必须显式设置
浏览器只认两个关键响应头,缺一不可:Content-Type: text/event-stream 和 Cache-Control: no-cache。漏掉 Content-Type,EventSource 状态永远停在 connecting;漏掉 Cache-Control,Nginx 或 Chrome 可能缓存初始响应,后续事件根本不会触发 message 事件。
- 别在中间件里设这些头——gzip 中间件可能重写
Content-Encoding,破坏流式响应 -
Connection: keep-alive虽非强制,但显式声明可防某些代理提前关闭连接 - 不要调用
w.WriteHeader(http.StatusOK)前就往w写内容,否则状态码可能被吞掉
每次写 data: 后必须调用 Flush()
Go 的 ResponseWriter 默认带缓冲,fmt.Fprintf(w, "data: hello\n\n") 只是把字节塞进内存,不刷出去,客户端永远收不到。必须先做类型断言,再强制刷新:
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "streaming unsupported", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "data: %s\n\n", msg)
flusher.Flush()
- 不能用
defer flusher.Flush()——它只在 handler 返回时执行一次,起不到流式作用 - 别用
log.Printf在循环里打日志,I/O 阻塞会导致Flush()延迟,客户端感知为卡顿 - 如果
msg含换行符(比如 JSON 字符串),得手动拆成多行data:,不能直接嵌\n
HTTP 超时必须禁用 ReadTimeout/WriteTimeout
默认 http.Server 的 ReadTimeout 和 WriteTimeout 是几分钟,而 SSE 连接常需维持几十分钟甚至数小时。一旦空闲超时,连接被静默关闭,前端看到的就是 error 事件后自动重连。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 设
ReadTimeout和WriteTimeout为 0(禁用),改用ReadHeaderTimeout控制请求头读取时间(如 10 秒) -
IdleTimeout推荐设为 0 或至少5 * time.Minute,否则空闲连接会被 Go 自身关掉 - 如果用了 Nginx,
proxy_read_timeout必须同步调大,否则它比 Go 先断 - 别依赖
r.Context().Done()唯一判断断连——HTTP/1.1 下它常滞后,Write/Flush 报io.ErrClosedPipe或write: broken pipe才是真实信号
goroutine 泄漏比格式错误更危险
每个 SSE 连接对应一个长生命周期 goroutine,客户端关页但服务端没感知,这个 goroutine 就一直卡在 time.Sleep 或 chan receive 上,内存和文件描述符持续上涨。
- 给每个连接 goroutine 加
context.WithTimeout(ctx, 5*time.Minute),作为兜底保护,不是等客户端断开,而是防意外堆积 - 推送逻辑别用全局
map[*http.ResponseWriter]bool存连接——ResponseWriter不线程安全,也无法检测断连 - 用
sync.Map存注册的事件接收chan,生产者往中心chan发,每个 handler 自己监听专属chan - 每次写入前加
select { case ,但更要紧的是 Write/Flush 后检查 error,立刻退出
真正难调的不是“怎么发第一条消息”,而是“怎么让第 1000 个连接稳定活过 2 小时还不泄漏”。格式错了一条,顶多前端收不到;超时没关、Flush 没做、context 没判,服务几天就 OOM。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










