http.MaxBytesReader 是手动套在 Request.Body 上的流量限制闸门,用于防止大请求体导致 OOM;不显式使用时,ReadAll、json.Unmarshal 等会无限制读取全部请求体,引发崩溃或高 CPU。

Go 的 http.MaxBytesReader 是什么,为什么不能只靠 ReadAll 或 json.Unmarshal 拦截大请求体
它不是中间件,也不是自动生效的防护机制——它是你手动套在 Request.Body 上的一层“流量闸门”。不加这层,攻击者发一个 2GB 的 POST /login,你的服务可能先 OOM 再崩溃,而你写的 json.Unmarshal(r.Body, &v) 还没开始解析就卡死了。
常见错误现象:panic: runtime out of memory、http: request body too large(这个其实是标准库某些地方抛的,但不可靠)、CPU 飙高但无响应。
-
ReadAll会把整个请求体读进内存,不管多大 -
json.Unmarshal和xml.Unmarshal默认也不设限,底层照样全读 - 用
r.ParseForm()处理表单时,如果含大文件或恶意字段,一样中招 - HTTP/2 下更隐蔽:多个小请求复用连接,但每个都绕过你没设限的 Body 读取逻辑
怎么给每个 handler 正确套上 http.MaxBytesReader
必须在读取 Body 前、且仅一次地包装。不能在中间件里统一 wrap 后再传给 handler——因为 MaxBytesReader 返回的是新 io.ReadCloser,你得把它赋回 r.Body,否则后续读取还是原始未限制的流。
典型误用:http.MaxBytesReader(w, r.Body, limit) 但没赋值给 r.Body,等于白写。
立即学习“go语言免费学习笔记(深入)”;
- 正确姿势:
r.Body = http.MaxBytesReader(w, r.Body, 5 - 必须在
defer r.Body.Close()之前做,否则可能 panic - 参数顺序别错:
MaxBytesReader(w http.ResponseWriter, r io.ReadCloser, n int64),第一个是w不是r - 如果 handler 里用了
r.MultipartReader(),也要在调用前完成包装,否则 multipart 解析仍可能爆内存
全局限制 vs 路由级限制:什么时候该用 http.Server.MaxHeaderBytes 和 http.Request.Body 分开控
MaxHeaderBytes 只管 header 大小(默认 1
比如攻击者发一个 header 里塞 1000 个 X-Forwarded-For 字段,占满 1MB,但 body 空着——这时候 MaxBytesReader 不起作用,得靠 Server.MaxHeaderBytes 拦;反过来,body 是 100MB 二进制垃圾,header 很干净,那就全靠 MaxBytesReader。
http.Server{MaxHeaderBytes: 8 —— 控 header,单位字节,建议设小点(如 8KB)-
MaxBytesReader控 body,单位字节,按接口语义设(如登录接口 2MB,上传接口 50MB) - 不要依赖
Content-Length头做校验:HTTP/1.1 允许 chunked 编码,没有这个头也能传大 body - gRPC over HTTP/2?
MaxBytesReader依然有效,因为底层仍是Request.Body接口
为什么 MaxBytesReader 读超限时返回 http.ErrBodyTooLarge,但很多框架不直接暴露它
这个 error 是标准库定义的,类型是 *http.maxBytesError,实现了 error 接口,但不会自动触发 HTTP 413。你得自己判断并写响应——否则用户看到的是 500 或连接中断。
容易踩的坑:用 gin 或 echo 时,以为开了 MaxMultipartMemory 就万事大吉,其实那只是控制内存 buffer,不等于拒绝超限请求;或者写了 MaxBytesReader 却没检查 error,导致 panic 后 fallback 到默认 500 页面,掩盖了真实原因。
- 检查方式:
_, err := io.Copy(io.Discard, r.Body); if errors.Is(err, http.ErrBodyTooLarge) { http.Error(w, "too large", http.StatusRequestEntityTooLarge); return } - 别用
err == http.ErrBodyTooLarge,要用errors.Is,因为实际返回的是包装过的 error - 如果你用
json.NewDecoder(r.Body).Decode(&v),它内部读取失败时也会返回http.ErrBodyTooLarge,同样要errors.Is判断 - 日志里记下
r.RemoteAddr和r.URL.Path,方便事后追查是不是被扫描器盯上了
MaxBytesReader,而是想清楚每个 endpoint 的 body 合理上限是多少,以及当它被突破时,你是静默丢弃、返回 413,还是记录告警——这些决策没法交给库自动做。










