Go写SSE handler需设Content-Type和Cache-Control头、逐行写data:并双换行、每次调Flush;须禁用Read/WriteTimeout、设IdleTimeout;用chan+sync.Map实现安全广播,监听r.Context().Done()防泄漏。

Go怎么写一个能发SSE的HTTP handler
Go原生不带SSE专用类型,但用标准http.ResponseWriter和http.Request就能搞定,关键在响应头和流式写入方式。
常见错误是直接用fmt.Fprintf往ResponseWriter里写,结果被缓冲、没实时推送;或者忘了设Content-Type: text/event-stream,浏览器根本不认。
- 必须调用
w.Header().Set("Content-Type", "text/event-stream") - 必须调用
w.Header().Set("Cache-Control", "no-cache")(否则某些代理或浏览器会缓存整个流) - 每次写完一条事件后,必须调用
w.(http.Flusher).Flush()——这是实时推送的核心,ResponseWriter默认不自动刷缓冲 - 不要用
json.Marshal后一次性w.Write,要按SSE格式逐行写:data: ...,每条消息以双换行结束
为什么net/http的handler容易断连
SSE依赖长连接,而Go的http.Server默认有超时控制,客户端一静默几十秒就可能被关掉连接,表现为前端突然收不到新事件。
这不是代码bug,是服务端配置问题。默认ReadTimeout和WriteTimeout都只设了几分钟,但SSE要求连接维持更久(甚至几小时),且期间可能完全无数据。
立即学习“go语言免费学习笔记(深入)”;
- 必须显式禁用
ReadTimeout和WriteTimeout,改用ReadHeaderTimeout(只限制请求头读取时间) - 推荐设置
IdleTimeout为0(不限制空闲时间),或设为较大值如5 * time.Minute - 如果用了反向代理(如Nginx),它也有自己的超时配置,
proxy_read_timeout必须同步调大,否则它先断
怎么安全地向多个客户端广播事件
不能每个请求都起goroutine去轮询、再挨个Write——这会迅速耗尽连接和内存。得用发布-订阅模型,让handler只负责监听频道、转发,不参与业务逻辑。
典型坑是用全局map[*http.ResponseWriter]bool存连接,但ResponseWriter不是线程安全的,且无法检测客户端是否已断开。
- 用
chan做事件总线,比如type Event struct{ Data string },所有生产者往一个chan Event发 - 每个SSE handler启动时,注册一个独立的
chan Event到中心管理器(用sync.Map存),并启动一个for-select监听该chan - 务必在handler开头检查
r.Context().Done(),一旦返回就退出goroutine,避免泄漏 - 写入前先用
select { case 判断连接是否还活着,别硬写
前端接收SSE时Go后端要注意哪些格式细节
浏览器的EventSource对格式很敏感,哪怕多一个空格、少一个换行,都会导致解析失败或静默丢弃。
Go里手动拼接字符串极易出错,尤其换行符在Windows/Linux下可能混用,或
写成
。
- 每条消息必须以
data:开头,后面跟一个空格,再跟内容,结尾是(两个LF) - 如果要传JSON,别自己
json.Marshal后直接塞进data:——要确保JSON字符串本身不含换行,否则会被切碎;建议先bytes.ReplaceAll(jsonBytes, []byte(" "), []byte("\n")) - 支持
id:和event:字段,但非必需;若用id:,浏览器断线重连时会带上Last-Event-ID,后端可据此补发 - 别用
fmt.Fprintf(w, "data: %s ", msg)——%s可能含不可见字符;老实用w.Write([]byte("data: "))+w.Write(escapedData)+w.Write([]byte(" "))
最麻烦的其实是连接生命周期管理:客户端网络抖动、页面关闭、浏览器休眠都会让连接半死不活,而Go不会立刻通知你。得靠心跳+上下文检测+有限重试,而不是指望一次写成功就万事大吉。


















