maxbyteshandler 不能限制 post body 大小,因它仅在 responsewriter.write 时统计响应字节数并触发 413;真正限制请求体需用 http.maxbytesreader 包装 r.body 并在读取前设置。

MaxBytesHandler 为什么不能直接限制 POST body 大小
net/http.MaxBytesHandler 实际上只限制 整个 HTTP 响应的字节总数,不是请求体。很多人误以为它能拦住超大的 POST 或 PUT 请求,结果发现上传 100MB 文件时服务照样接收、解包、内存暴涨——它根本没起作用。
真正生效的场景是:你用它包装一个 http.Handler,而这个 handler 在写响应时(比如调用 w.Write())累计写入超过设定上限,就会返回 413 Request Entity Too Large。但它对请求头、请求体、URL 长度等完全不检查。
- 它包装的是
http.Handler,不是http.Request - 它在
ResponseWriter.Write()时才开始计数,和请求解析无关 - 常见误用:把
MaxBytesHandler放在路由中间件最外层,期待它“挡住大请求”——实际无效
真正限制请求体大小的正确方式:Use http.MaxBytesReader
Go 标准库提供了专门用于限制请求体的工具:http.MaxBytesReader。它返回一个封装了 req.Body 的 io.ReadCloser,读取时一旦超过阈值就返回 http.ErrBodyTooLarge。
必须在 handler 内部显式调用它,并替换 req.Body,否则无意义。示例:
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// 允许最大 5MB 请求体
r.Body = http.MaxBytesReader(w, r.Body, 5*1024*1024)
defer r.Body.Close()
// 后续读取(如 io.ReadAll、r.ParseMultipartForm)会受限制
data, err := io.ReadAll(r.Body)
if err == http.ErrBodyTooLarge {
http.Error(w, "request too large", http.StatusRequestEntityTooLarge)
return
}
if err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
// ...处理 data
}
- 注意第一个参数是
http.ResponseWriter:当触发限流时,MaxBytesReader会自动调用w.WriteHeader(http.StatusRequestEntityTooLarge) - 必须在任何读取
r.Body之前设置,且只能设一次(r.Body是单次读取流) - 对
multipart/form-data也有效,但需配合r.ParseMultipartForm的maxMemory参数协同控制内存使用
结合 multipart 表单时的关键细节
如果处理的是带文件上传的表单,仅靠 http.MaxBytesReader 不够:Go 的 ParseMultipartForm 默认会把所有非文件字段缓存到内存,且对文件部分只限制内存缓冲(不阻断磁盘临时文件)。必须双管齐下:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
http.MaxBytesReader包装r.Body,防止整体请求体过大 - 调用
r.ParseMultipartForm(32 时,传入的 <code>maxMemory是内存中最多缓存多少字节(超出部分写临时文件),它不影响总大小限制 - 若要彻底禁用磁盘临时文件(即强制全部进内存或失败),需额外检查
r.MultipartForm.File中每个*multipart.FileHeader的Size字段
示例片段:
err := r.ParseMultipartForm(32 10*1024*1024 { // 单文件不超过 10MB
http.Error(w, "file too large", http.StatusRequestEntityTooLarge)
return
}
}
全局统一拦截:用中间件包装 HandlerFunc
手动在每个 handler 里写 http.MaxBytesReader 易遗漏。可封装为中间件,但要注意:中间件必须在 http.ServeHTTP 调用前完成 Body 替换,且不能提前读取 Body(否则后续 handler 读不到)。
推荐写法:
func withMaxBodySize(next http.Handler, limit int64) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, limit)
next.ServeHTTP(w, r)
})
}
// 使用
http.Handle("/upload", withMaxBodySize(http.HandlerFunc(uploadHandler), 5*1024*1024))
- 该中间件只改
r.Body,不执行任何读取,安全可靠 - 不要在中间件里调用
r.ParseForm()或io.ReadAll(r.Body),那会消耗流 - 若使用第三方路由器(如 chi、gorilla/mux),确保中间件注册顺序正确:限流中间件必须在业务 handler 之前,且在日志、认证等可能提前读 body 的中间件之后
最常被忽略的一点:如果你启用了 http.Server.ReadTimeout 或 ReadHeaderTimeout,它们不会因请求体过大而提前中断连接;只有 MaxBytesReader 触发时才会返回错误响应。别指望超时配置能替代体积限制。










