Go原生net/http支持SSE但需严格配置:必须显式设置Content-Type: text/event-stream和Cache-Control: no-cache;每次写data:后须用http.Flusher.Flush()刷新;禁用ReadTimeout/WriteTimeout并调大IdleTimeout及Nginx proxy_read_timeout;Write/Flush失败时应立即退出循环。

Go 原生 net/http 完全能跑通 SSE,但直接写 fmt.Fprintf(w, "data: hello\n\n") 就上线,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 事件后自动重连。
立即学习“go语言免费学习笔记(深入)”;
- 设
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才是真实信号
客户端断连检测不能只靠 context.Done()
Go 不会主动通知你客户端已关页或网络中断,Write 或 Flush 失败时才暴露问题。这时候如果还往死里推,会卡 goroutine、积压内存。
- 每次
Write或Flush后检查err:若为io.ErrClosedPipe、http.ErrHandlerTimeout或write: broken pipe,说明客户端已掉线,应立即break循环并return - 加个简单心跳:每 15 秒写一次
:\n\n(SSE 注释行),既防代理超时,又能快速探测连接是否存活 - 给每个连接 goroutine 加上
context.WithTimeout(ctx, 5*time.Minute),超时自动退出——不是等客户端断开,而是兜底保护
最容易被忽略的是:SSE 不是“发完就算”,而是“持续活着”。从响应头格式、每次 Flush()、到超时配置和断连探测,任何一环松动,都会导致前端静默失败,且错误日志里往往只有一行 write: broken pipe —— 它不是偶然,是必然。


















