Go实现SSE需用http.ResponseWriter保持长连接,手动写event:/data:及双换行,并禁用HTTP/2压缩与缓冲;因标准库未封装SSE协议,仅依赖Content-Type和格式约定,常见问题为底层缓冲或代理截断流。

Go 做 SSE 服务端推送,核心就一条:用 http.ResponseWriter 保持长连接、手动写入 event:、data: 和双换行,且必须禁用 HTTP/2 的流式压缩与缓冲。
为什么 net/http 默认不支持 SSE?
Go 标准库没有封装 SSE 协议,它只是 HTTP 的一种约定(Content-Type: text/event-stream + 特定格式响应体)。问题常出在:ResponseWriter 被底层自动缓冲、或 HTTP/2 强制分块压缩导致客户端收不到实时数据。
- HTTP/1.1 下,
Flush()可强制刷出;但 HTTP/2 默认启用流式压缩(如 gzip),会攒包 - 某些中间件(如
gzip.Handler)或反向代理(Nginx 默认)会截断或重写Transfer-Encoding: chunked - 客户端(浏览器)要求首行必须是
data:或带event:,且每条消息以\n\n结尾
怎么写出能跑通的 SSE handler?
关键不是“写对格式”,而是“让字节立刻发出去”。以下是最小可靠写法:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func sseHandler(w http.ResponseWriter, r *http.Request) {
// 必须设置,否则浏览器不识别为 SSE
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
<pre class="brush:php;toolbar:false;">// 禁用 Go 的内部缓冲(重要!)
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "streaming unsupported", http.StatusInternalServerError)
return
}
// 每次写完必须 flush,否则卡住
fmt.Fprintf(w, "data: %s\n\n", "hello")
flusher.Flush()
// 后续可循环发送……}
- 不要用
fmt.Fprintln(w, ...)—— 它可能触发隐式缓冲;用Fprintf+ 显式Flush() - 不要在 handler 外提前写 header(比如 middleware 中设了
Content-Encoding: gzip),会破坏流式 - 如果用
gorilla/mux或chi,确保没挂载自动压缩中间件
如何避免 Nginx / CDN 吞掉 SSE 流?
Nginx 是最常见断点。默认配置会缓存、超时、或丢弃 chunked 响应。需显式放行:
location /sse {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache off;
proxy_buffering off; # 关键:禁用缓冲
proxy_read_timeout 86400; # 长超时
chunked_transfer_encoding off; # Go 1.19+ 已默认关,但旧版 Nginx 可能仍干预
}
- CDN(如 Cloudflare)默认不支持 SSE,需关闭“优化”或改用专用流 CDN
- 本地测试绕过 Nginx 直连
go run main.go,确认逻辑无误后再查网关层 - 用
curl -N http://localhost:8080/sse验证:看到逐条输出即通,卡住或一次性返回就是被缓存了
真正难的从来不是拼格式,而是让 TCP 包不被任何中间层吞掉、压扁、或超时断开——从 http.Flusher 到 Nginx 的 proxy_buffering off,每一环都得亲手掰开确认。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










