go 的 http.responsewriter 默认支持 chunked 编码,但 write() 仅写入约4kb内置缓冲区,不会立即发送;必须显式断言 http.flusher 并调用 flush() 才能实时推送,且 content-length 存在时 chunked 自动禁用。

Go 的 http.ResponseWriter 默认就支持 chunked transfer encoding,你不需要手动拼 chunk、不需设置 Transfer-Encoding 头,更不用导入额外包——只要别踩坑,它自己就工作。
为什么写了 w.Write() 却没立刻发到客户端?
因为 Write() 只是把数据塞进 server 内置的小缓冲区(通常 4KB 左右),不是发包指令。尤其在循环推送日志、SSE 或 CSV 行时,用户会卡在 loading 状态,实际数据早就写进去了,就是没刷出去。
- 必须显式判断并调用
Flusher:f, ok := w.(http.Flusher); if ok { f.Flush() } - 不能假设
fmt.Fprintf(w, ...)后自动 flush;json.NewEncoder(w).Encode()后也得 flush - 如果 client 断开,
Flush()可能返回write: broken pipe,建议检查 error -
http.TimeoutHandler会硬中断流式响应,绝不要套在流式 handler 上
哪些操作会悄悄禁用 chunked?
chunked 是 net/http 在“长度未知 + HTTP/1.1 + 连接未关闭”下的兜底行为,但很多常见写法会提前把它锁死成固定长度模式。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 显式设置了
w.Header().Set("Content-Length", "...")—— 即使设的是 "0" 或空字符串也不行 - 用了
http.ServeFile、io.Copy全读进内存再写、或返回[]byte字面量 —— Go 能直接算出长度,就走Content-Length - 某些中间件(如自定义 gzip、reverse proxy)会缓冲整个 body,导致 flush 失效;
gzip.Handler默认兼容,但顺序很重要 - 在
w.WriteHeader()前就调Flush(),会触发隐式 200,后续再设 header 就 panic
如何安全地做 JSON 行流(NDJSON)或 SSE?
这类场景核心不是“实现 chunked”,而是控制 MIME 类型、避免缓存、按逻辑块 flush,并及时感知 client 断连。
- NDJSON:设
w.Header().Set("Content-Type", "application/x-ndjson; charset=utf-8"),每行json.Encoder.Encode()后紧接flusher.Flush() - SSE:设
"Content-Type": "text/event-stream"和"Cache-Control": "no-cache",每条消息以data: ...\n\n结尾 - 检测断连别用已弃用的
http.CloseNotify(),改用select { case ,每次 <code>Write()或Flush()前都该检查 - Nginx/CDN 可能缓存首 chunk,可在开头写 2KB padding(如
w.Write(bytes.Repeat([]byte(" "), 2048)))骗过代理
真正难的不是让 Go 发 chunk,而是让每个 chunk 按你预期的节奏到达客户端——这取决于 flush 时机、中间件是否透传、代理是否缓存、以及你有没有在 context 取消时及时退出。漏掉任意一环,流就卡住或静默失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










