req.Body 读一次就空是因为其底层是单次读取的 io.ReadCloser 流,读完后偏移至末尾,再次读返回空;需用 io.NopCloser(bytes.NewReader(bodyBytes)) 重建可重放 Body。

为什么 req.Body 读一次就空了?
Go 的 http.Request.Body 是一个 io.ReadCloser,底层通常是单次读取的流(比如网络 socket 缓冲区)。调用 ioutil.ReadAll(req.Body) 或 io.ReadAll(req.Body) 后,Body 内部偏移已到末尾,再次读会返回空字节切片 —— 这不是 bug,是设计使然。
常见错误现象:
• 第二次调用 io.ReadAll(req.Body) 得到空字符串
• 用 json.NewDecoder(req.Body) 解析失败,报 invalid character '}' after top-level value(因为 Body 已被提前读空)
• 中间件里读了一次 Body,后续 handler 拿不到数据
- 必须在需要时「只读一次」,或手动重置/复制 Body
- 如果要多次使用,得用
req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))重建可重复读的 Body - Go 1.16+ 推荐用
io.ReadAll替代已弃用的ioutil.ReadAll
怎么安全地把 Body 读成字符串?
最简场景:你只需要原始内容,不关心后续是否复用 Body。直接读、转 string 即可,但记得检查 error 和关闭 Body(虽然 ReadAll 不依赖 Close,但习惯上仍建议 defer req.Body.Close())。
body, err := io.ReadAll(req.Body)
if err != nil {
http.Error(w, "read body failed", http.StatusBadRequest)
return
}
defer req.Body.Close() // 实际上 ReadAll 后 Close 无副作用,但保留更稳妥
s := string(body) // 注意:Body 原始编码未必是 UTF-8,需按 Content-Type 判断
- 别用
req.Body.Read()自己分配 buffer,容易漏读或阻塞 - 如果 Body 很大(>几 MB),
io.ReadAll会一次性分配内存,可能 OOM;此时应改用流式处理(如json.Decoder直接解码) -
Content-Type头若含charset=gbk,string(body)不会自动转码,需用golang.org/x/text/encoding显式转换
想在中间件里读 Body 又不影响后续 handler 怎么办?
典型需求:日志中间件打印请求体、鉴权中间件校验签名、或统一解密。这时必须让 Body「可重放」。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
核心思路:读出原始字节 → 用 bytes.NewReader 包装 → 赋给 req.Body 新值。
body, err := io.ReadAll(req.Body)
if err != nil {
http.Error(w, "read body failed", http.StatusBadRequest)
return
}
req.Body.Close() // 必须关原 Body
// 重建可重复读的 Body
req.Body = io.NopCloser(bytes.NewReader(body))
// 此时可安全打印或传给下游
log.Printf("body: %s", string(body))
-
io.NopCloser只是给bytes.Reader补个Close()方法,它本身不执行任何操作 - 不要用
strings.NewReader(string(body)),UTF-8 字符串转 bytes 再转 string 可能因多字节字符截断出错 - 如果 handler 后续还要解析 JSON,直接传
bytes.NewReader(body)给json.NewDecoder更高效,避免重复 copy
Body 为空或超时的常见原因
读出来是空字符串,不一定是代码写错了,先排查这些:
- 前端发的是
GET请求 —— 它本就不该有 Body;检查是否误用了POST以外的方法 - curl 没加
-d或--data,比如curl http://localhost:8080默认无 Body - 客户端设置了
Content-Length: 0,或写了Transfer-Encoding: chunked但没发数据块 - 反向代理(如 Nginx)默认不转发 Body 给后端,需确认配置含
proxy_pass_request_body on; - 客户端在写 Body 时 panic / 提前断开,服务端
ReadAll会立即返回当前已收到的部分(可能为空)+io.ErrUnexpectedEOF
调试时加一行 log.Printf("Content-Length: %s", req.Header.Get("Content-Length")) 很快能定位是不是前端根本没发。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










