go 的 http.responsewriter 能多次写入,但需禁用缓冲、手动刷新并防止中间件断连;sse 需设正确 header、定时心跳、严格格式输出、绕过干扰中间件,并配置反向代理。

Go 的 http.ResponseWriter 为什么不能直接写多次?
因为 SSE 要求服务端保持连接、持续写入以 data: 开头的纯文本块,而默认的 http.ResponseWriter 在调用 Write 后可能被底层 HTTP/1.1 实现缓冲或提前关闭。关键不是“不能写多次”,而是必须禁用响应缓冲、手动刷新,并确保连接不被中间件(如反向代理、Cloudflare)断开。
实操建议:
- 设置
w.Header().Set("Content-Type", "text/event-stream") - 设置
w.Header().Set("Cache-Control", "no-cache")和w.Header().Set("Connection", "keep-alive") - 调用
w.(http.Flusher).Flush()每次写完后立即刷出,否则数据卡在 Go 的bufio.Writer里 - 避免在 handler 中 defer
defer w.(http.Flusher).Flush()—— 它只在函数退出时执行一次,起不到流式推送作用
如何用 net/http 实现带超时和心跳的 SSE 连接?
SSE 连接容易因网络空闲被中间设备(Nginx、ALB、运营商网关)静默断开,单纯写 data: 不够,得主动发心跳(: 注释行或 event: heartbeat)并控制连接生命周期。
实操建议:
- 用
time.Timer或time.Ticker每 15–30 秒发一次:\n\n(SSE 注释,客户端忽略) - 在写入前检查
w.(http.CloseNotifier).Done()(Go 1.8+ 已弃用,改用req.Context().Done()) - 对每个连接启动 goroutine 处理事件,但用
context.WithTimeout控制总存活时间(比如 5 分钟),防长连接堆积 - 别用
log.Printf频繁打日志 —— 日志 I/O 可能阻塞 flush,导致客户端卡住
客户端收到 error 事件或反复重连怎么办?
这是最常被忽视的信号:EventSource 触发 error 并非一定是服务端崩了,更可能是连接被重置、响应头缺失、或服务端没按 SSE 格式输出(比如多了一个空行、漏了 \n\n 结尾)。
实操建议:
- 服务端每次写入必须严格遵循格式:
data: xxx\n\n或event: xxx\ndata: yyy\n\n,结尾两个换行不可少 - 用 curl 测试:
curl -H "Accept: text/event-stream" http://localhost:8080/sse,观察原始输出是否含非法字符或乱序 - 客户端加
eventsource.onerror = () => console.log('reconnecting...'),但不要盲目重试 —— 先确认服务端日志有没有write: broken pipe或context canceled - Nginx 反向代理需显式配置:
proxy_buffering off;、proxy_cache off;、proxy_read_timeout 60;
用 gorilla/mux 或 chi 路由时,中间件会破坏 SSE 吗?
会。任何读取 req.Body、调用 req.ParseForm()、或包装 ResponseWriter 的中间件(比如日志、JWT 验证、gzip 压缩)都可能导致流中断或 header 被覆盖。
实操建议:
- SSE handler 路由必须放在中间件链最外层,或明确跳过所有中间件(
chi.ServerBaseContext或mux.SkipClean不管用,得用HandleFunc直接注册) - 绝对不要在 SSE handler 里调用
req.FormValue或req.PostForm—— 它们会隐式读 body 并关闭连接 - 如果要用 JWT,从
Authorizationheader 解析 token,别走表单或 JSON body 解析流程 - gzip 中间件必须排除
text/event-stream类型,否则压缩流会让客户端解析失败
真正麻烦的从来不是怎么发 event,而是连接什么时候断、为什么断、断了之后客户端能不能干净重建而不丢状态 —— 这些细节藏在 Nginx 配置、负载均衡器心跳、以及你写的那行 fmt.Fprintf(w, "data: %s\n\n") 后面有没有跟 flusher.Flush() 里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











