go标准库不自动解压gzip请求体或处理分块编码,需手动检查content-encoding头、用gzip.newreader包装并显式close;大文件须用http.maxbytesreader限流防oom。

Go 语言标准库本身不自动解压 Content-Encoding: gzip 的请求体,也不支持 HTTP/1.1 分块传输编码(Transfer-Encoding: chunked)的自动重组——这两件事都得你自己做,否则要么收不到数据,要么直接 OOM。
服务端如何安全解压 gzip 请求体
客户端发了 Content-Encoding: gzip,服务端不能靠 r.Body 原样读;必须手动识别头、包装 reader、并确保错误路径下资源释放。
- 先检查
r.Header.Get("Content-Encoding") == "gzip",不是就跳过,避免误解压非压缩内容 - 用
gzip.NewReader(r.Body)包裹原始 body,但必须在 handler 返回前调用gr.Close()(否则底层 zlib state 泄漏,长期运行会卡死) - 别在中间件里无条件 wrap —— 如果后续 handler 又调了一次
r.ParseMultipartForm,它内部会再读一次 body,而 gzip reader 已经 EOF,导致解析失败或 panic - 常见错误现象:
gzip: invalid header,90% 是因为 body 已被提前读空(比如日志中间件调了io.ReadAll(r.Body)),或重复 wrap
大报文(>100MB)必须设限 + 流式落地
不设限的 r.Body 是定时炸弹。Go 默认不限制请求体大小,恶意上传 10GB 就能干掉整个进程。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 在 handler 开头立刻用
http.MaxBytesReader包裹:limitReader := http.MaxBytesReader(w, r.Body, 2*1024*1024*1024)(限 2GB) r.ParseMultipartForm(32 必须显式调用,且参数 ≤32MB;超过部分自动落盘到 <code>os.TempDir(),但临时文件不会自动清理,得自己加 defer 清理- 不要用
r.FormFile直接读大文件——它底层仍会把整个文件载入内存或临时文件,然后给你一个*os.File;你应该用r.MultipartReader()拿到multipart.Reader,再配合io.Copy直接写磁盘 - 若需校验(如 SHA256),必须边读边算:
hash := sha256.New(); io.Copy(hash, limitedReader),而不是等全读完再算
分块上传(非 HTTP/1.1 chunked)的客户端与服务端协同要点
所谓“分块上传”在 Go 实现中,是应用层协议,不是 HTTP 协议自带的 chunked。你得自己定义块元数据,并保证顺序与完整性。
- 客户端每块 POST 必须带唯一标识:
X-File-ID、X-Chunk-Index、X-Total-Chunks、X-Chunk-Hash(如 blake3 32B) - 服务端收到后,不拼内存,直接
os.OpenFile(..., os.O_CREATE|os.O_WRONLY|os.O_APPEND)追加写入临时文件(注意:并发写同一文件需加sync.Mutex或按块号分文件) - 最后一块到达后触发合并:用
os.Rename替换目标文件,避免中间状态暴露;同时删掉所有已接收的临时块文件 - 断点续传靠
X-Chunk-Index和服务端已存块列表比对,返回 200 表示已存在,跳过重传
最容易被忽略的是:gzip 解压后的流无法 Seek,所以如果你需要多次读取(比如先校验再保存),只能一次性读完进 buffer 再分发;而大文件分块场景下,这个 buffer 本身就是风险点——真正的稳态做法,是让解压、校验、落盘三者串成一条流,中间不暂存完整明文。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










