maxbytesreader 是 go net/http 中用于限制请求体读取字节数的包装器,它在 handler 内部首次调用 r.body.read() 时生效,通过封装 r.body 实现字节级拦截,必须显式赋值回 r.body 才起作用。

MaxBytesReader 是什么,它在哪个环节起作用
net/http.MaxBytesReader 不是中间件,也不是请求解析后的校验逻辑,它是 http.Request.Body 的一层包装器,在读取请求体(比如 io.ReadAll(r.Body) 或 json.NewDecoder(r.Body).Decode())时才生效。它不阻止请求到达 handler,也不影响 header 解析或路由匹配,只限制后续对 Body.Read() 的累计字节数。
- 它作用于
http.Handler内部,必须显式调用并替换r.Body - 如果 handler 没有读 body(比如只看 header 或 method),它完全不触发
- 它无法防止慢速 POST 攻击(如 slowloris),因为连接仍保持打开,只是读操作会提前返回错误
怎么正确包装 r.Body:常见写法和典型错误
正确做法是在 handler 开头立即用 http.MaxBytesReader 包装原始 r.Body,并赋值回 r.Body:
func myHandler(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 10<p>容易踩的坑:</p>
- 忘记把包装后的
Body赋值回r.Body,导致限制无效 - 在调用
MaxBytesReader前已读过r.Body(比如用了r.ParseForm()),此时r.Body可能已被消耗或关闭 - 把
w传错成r(第二个参数是ResponseWriter,用于写错误响应;传r会导致 panic)
和 http.MaxHeaderBytes、Gin 的 binding、FastHTTP 的区别
-
http.MaxHeaderBytes 控制的是 header 大小(默认 1MaxBytesReader 无关,需在 http.Server 配置中设置
- Gin 等框架的
c.ShouldBindJSON() 默认不自动启用 body 限制,仍需手动包装 c.Request.Body,否则大 payload 会直接 OOM
- FastHTTP 自带
Server.MaxRequestBodySize,是连接层硬限,比 MaxBytesReader 更早拦截,但 Go 标准库没有等价配置项
http.MaxHeaderBytes 控制的是 header 大小(默认 1MaxBytesReader 无关,需在 http.Server 配置中设置c.ShouldBindJSON() 默认不自动启用 body 限制,仍需手动包装 c.Request.Body,否则大 payload 会直接 OOMServer.MaxRequestBodySize,是连接层硬限,比 MaxBytesReader 更早拦截,但 Go 标准库没有等价配置项性能上:包装本身无开销,但每次 Read() 都要检查累计字节数,对小请求可忽略;对高频小请求,建议用更轻量的前置判断(如检查 r.ContentLength 是否超限)快速拒绝
Content-Length 缺失时的行为和流式上传场景
当客户端不发 Content-Length(比如分块传输、curl -H "Transfer-Encoding: chunked"),MaxBytesReader 依然有效,它按实际读到的字节计数,直到达到上限后返回 http.ErrContentLength。
但要注意:
- 客户端可能持续发送数据,而服务端在读满后就关闭读通道,剩余数据被丢弃(TCP 层可能仍接收)
- 若 handler 中用了
multipart.Reader解析表单文件,MaxBytesReader对整个r.Body限流,包括 form 字段和文件内容总和,无法单独限制某个文件字段 - 没有内置机制区分“合法大文件”和“恶意大 payload”,业务上需结合路径、认证状态做白名单放行(例如
/upload允许 100MB,/api/login仅限 16KB)
真正难处理的是边界情况:比如一个 9.9MB 的 JSON 请求,在解码中途因嵌套过深触发栈溢出,这时 MaxBytesReader 已经没机会介入——它只管字节数,不管结构合法性。











