Go net/http Flusher 如何安全发送增量响应
做 SSE、日志推送或长轮询时,很多人把 Flush() 当成“客户端马上能看到”。更稳妥的做法是:先在第一次写入前完成响应头,再运行时判断 ResponseWriter 是否支持刷新;发送循环同时监听请求取消和写入错误。这样能保证服务端确实把当前缓冲交给连接,但不能越过反向代理的额外缓冲。
官方文档:https://pkg.go.dev/net/http
Flusher是可选能力,包装器可能把它隐藏,不能直接强制断言。- 响应头和状态要在首个
Write或Flush前准备好,每个事件写完再刷新。 - 客户端断开、写入失败和代理缓冲都要单独处理,Flush 成功不等于浏览器已渲染。
一、先把增量响应的边界定清楚
下面的示例采用 SSE 格式,每条消息以空行结束。Content-Type、缓存策略和状态码必须在第一次写入前设置;一旦响应提交,再改 Header 就不会产生预期效果。示例只展示服务端发送逻辑,不代表已经在某个本机环境运行。

func stream(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("X-Content-Type-Options", "nosniff")
// 通过控制器获得带错误返回的 Flush,适合 Go 1.20 及以上。
controller := http.NewResponseController(w)
if err := controller.Flush(); err != nil {
// 不支持刷新时不要假装是流式响应,直接返回错误。
http.Error(w, "streaming is not supported", http.StatusInternalServerError)
return
}
}
ResponseController.Flush 会尝试调用底层的 Flush 或 FlushError,不支持时返回错误。若项目需要兼容更早版本,也可以用 flusher, ok := w.(http.Flusher) 做能力判断;不要把所有包装器都当作原生响应写入器。
二、每条消息写完再刷新,并处理写入失败
刷新应跟在一条完整消息之后,而不是把半截 JSON 或半行 SSE 内容交给客户端。下面把“写入”和“刷新”放在同一轮中,并检查写入错误;Flush 本身不负责重试,也不替你恢复断开的连接。
func stream(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
controller := http.NewResponseController(w)
if err := controller.Flush(); err != nil {
http.Error(w, "streaming is not supported", http.StatusInternalServerError)
return
}
ticker := time.NewTicker(time.Second)
defer ticker.Stop() // 退出 Handler 时释放定时器资源。
for seq := 1; seq
首个空刷新用于尽早提交响应头,循环中的刷新用于提交当前事件。生产代码还应限制单次消息大小,避免把大对象一次写入;如果下游任务本身支持 context.Context,要把 r.Context() 继续传入,而不是重新创建一个无法取消的 context.Background()。
三、为什么“服务端 Flush 了”客户端仍然收不到
官方文档明确提醒:即使响应写入器支持 Flush,HTTP 代理仍可能缓存数据,直到响应结束才转发。排查时把链路拆成三段:Handler 是否写入完整事件,Go 服务是否执行刷新,代理和客户端是否继续缓存。只看到服务端日志里的“已发送”不能证明浏览器已经收到。

测试时可先直连 Go 服务,再逐层加上反向代理;客户端用能逐块读取响应的方式观察空行分隔的消息。如果直连能够增量到达、经过代理后变成一次性到达,问题就在链路策略而非 Flusher 判断。还要注意 http.TimeoutHandler 不支持 Flusher,它不适合作为这种流式 Handler 的外层包装。
四、上线前的最小检查清单
- 首个写入前设置内容类型、缓存和必要的安全响应头。
- 对原始或包装后的
ResponseWriter做运行时能力判断,优先保留ResponseController.Flush的错误。 - 每个完整事件写完后刷新,并在下一轮前监听
r.Context().Done()。 - 分别验证直连、代理、HTTP/1.x 和 HTTP/2;不要把代理是否立即转发归因给 Go 的 Flush。
- Handler 返回后不再使用
ResponseWriter,也不要从另一个 goroutine 继续写同一个响应。
相关问题
http.Flusher 和 ResponseController.Flush 该选哪个?
新代码可优先使用 ResponseController.Flush,因为它能返回错误并适配底层的 FlushError;需要兼容旧 Go 版本时,再用 http.Flusher 做显式能力判断。
Flush 会自动让浏览器立刻显示内容吗?
不会。它只负责把 Go 服务端当前缓冲交给连接,代理、客户端读取方式和浏览器渲染策略仍可能造成延迟。
分类评测集不均衡时如何比较 macro 与 micro 指标
- 上一篇
- 分类评测集不均衡时如何比较 macro 与 micro 指标
- 下一篇
- GPU 集群观测从 CPU 指标扩展时要增加哪些维度
-
- Golang · Go教程 | 14分钟前 |
- Go crypto/x509 CertPool.AppendCertsFromPEM 如何判断导入成功
- 445浏览 收藏
-
- Golang · Go教程 | 30分钟前 | go · crypto/tls · TLS会话复用 ·
- Go crypto/tls ClientSessionCache 如何观察会话复用
- 463浏览 收藏
-
- Golang · Go教程 | 42分钟前 | go · crypto/tls · TLS版本 · tls Go crypto/tls Config.MinVersion
- Go crypto/tls Config.MinVersion 如何限制握手版本
- 448浏览 收藏
-
- Golang · Go教程 | 56分钟前 | 网络编程 · 标准库 · go · netip CIDR Prefix.Masked
- Go net/netip Prefix.Masked 如何规范化网段键
- 448浏览 收藏
-
- Golang · Go教程 | 1小时前 | go · netip · 网络地址 · netip.ParseAddrPort netip.AddrPort Go地址端口解析
- Go net/netip AddrPort 如何解析带端口地址
- 177浏览 收藏
-
- Golang · Go教程 | 1小时前 | 性能优化 · http/2 · Go教程 · net/http · 兼容性 · 资源加载 · Preload Go net/http ErrNotSupported http.Pusher HTTP/2 Server Push Early Hints
- Go net/http Push 已不支持时如何规划资源加载
- 484浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go net/http ServeContent 如何为大文件设置正确修改时间
- 407浏览 收藏
-
- Golang · Go教程 | 2小时前 | go · net/http · Transport · MaxConnsPerHost ·
- Go net/http Transport MaxConnsPerHost 如何设置上限
- 277浏览 收藏
-
- Golang · Go教程 | 2小时前 | 标准库 · Http请求 · Go教程 · 接口调试 · 请求体 · Go net/http http.NewRequest ContentLength Request.Body
- Go net/http NewRequest 如何区分空 body 和零长度 body
- 478浏览 收藏
-
- Golang · Go教程 | 2小时前 | net/url · url编码 · Go教程 · 接口签名 · 查询参数 · QueryEscape Go net/url url.Values.Encode 查询参数顺序 Go URL 编码
- Go net/url Values.Encode 如何固定查询参数顺序
- 139浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Go net/url JoinPath 如何避免路径双斜杠
- 180浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 33次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 135次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 72次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 28次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 18次使用
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- Golang实现HTTP编程请求和响应
- 2022-12-28 101浏览
-
- golangNewRequest/gorequest实现http请求的示例代码
- 2023-01-24 343浏览
-
- 一文详解Golang中net/http包的实现原理
- 2022-12-29 419浏览
-
- go语言中的defer关键字
- 2023-02-17 150浏览

