必须显式使用http.maxbytesreader包装r.body,因框架默认不限制请求体大小;若上游中间件(如jwt解析)提前调用r.parseform()或io.readall(r.body),则body已被消耗,后续限流失效;正确做法是在中间件开头立即赋值r.body并确保仅执行一次。

Go 微服务中不能靠框架默认行为拦住大请求体,必须在中间件里显式用 http.MaxBytesReader 包装 r.Body,否则恶意 500MB POST 会直接 OOM 或拖垮 goroutine 池。
为什么标准中间件链里 MaxBytesReader 容易失效
中间件执行顺序决定了它是否来得及介入——一旦上游中间件(比如 JWT 解析、日志记录)提前调用了 r.ParseForm() 或 io.ReadAll(r.Body),r.Body 就被消耗或关闭,后续再包装已无意义。
- 常见误操作:自定义 auth 中间件里写了
r.ParseForm(),然后才进业务 handler,此时MaxBytesReader已完全不生效 - 另一个坑:把
http.MaxBytesHandler当成中间件塞进 middleware 链,但它包装的是整个http.Handler,不是单个请求流,和中间件洋葱模型不兼容 - 框架如 Gin 的
c.Request.Body是封装过的,但如果你在中间件里直接读它而没先包装,限流就等于没设
正确写法:中间件内尽早包装并只做一次
真正起作用的中间件,必须在 ServeHTTP 最开头就完成 r.Body 替换,并确保下游所有 handler 都基于这个新 Body 读取。
- 必须把包装逻辑放在
next.ServeHTTP()调用之前,且只执行一次 - 第一个参数传
w(http.ResponseWriter),不是r;传错会 panic - 限制值建议按接口语义设定,比如
/login用 2MB,/upload用 50MB,避免一刀切 - 示例代码:
func bodySizeLimitMiddleware(max int64) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ⚠️ 必须在任何读取前设置
r.Body = http.MaxBytesReader(w, r.Body, max)
next.ServeHTTP(w, r)
})
}
}
// 使用
r := mux.NewRouter()
r.Use(bodySizeLimitMiddleware(2
<h3>multipart 场景下必须配合 <code>ParseMultipartForm</code> 参数</h3>
<p>仅靠中间件里的 <code>MaxBytesReader</code> 不足以防住 multipart 攻击:攻击者可发一个 1KB 文本字段 + 99MB 文件,总大小超限会被拦,但若你只设了 100MB 总限,它就完全绕过。</p>
-
r.ParseMultipartForm(maxMemory)的maxMemory控制内存缓存阈值,超出部分自动落盘,不阻断上传 - 安全组合是:
MaxBytesReader卡死总字节数(比如 50MB),再调r.ParseMultipartForm(8 把内存占用压到 8MB 以内 - 后续用
r.FormFile()拿文件句柄,别用r.FormValue()依赖已解析内存数据,否则可能触发二次读取
错误处理必须显式检查 http.ErrBodyTooLarge
MaxBytesReader 不会自动返回 413,它只是让后续 Read() 调用返回 http.ErrBodyTooLarge。你不检查,就变成静默失败或 io: read/write on closed pipe。
- 在 handler 内部解码前加判断:
if errors.Is(err, http.ErrBodyTooLarge) - 不要依赖中间件统一捕获——标准
http.Handler接口不暴露 error 返回值,中间件无法可靠拦截该错误类型 - 框架如 Echo/Gin 的 binding 方法(如
c.ShouldBindJSON())内部已处理该错误,但前提是你要确保它们读的是你包装过的c.Request.Body
最易被忽略的一点:MaxBytesReader 对 slowloris 类攻击无效,它只在读取时计数,连接仍保持打开。所以必须搭配 http.Server.ReadTimeout 和 MaxHeaderBytes 才算完整防护。











