io.limitreader 不能直接用于 http 请求体防护,它仅是字节计数器,不处理 http 协议语义,不关闭连接,也不返回 413 状态码。

io.LimitReader 不能直接用于 HTTP 请求体防护
它只是个字节计数器,不处理 HTTP 协议语义,也不关连接、不返回 413 状态码。直接用 io.LimitReader(r.Body, 10 包装后调 <code>io.ReadAll,超限时会 panic 或读到一半就停,客户端收不到明确错误,服务端还可能漏关 body 导致连接泄漏。
常见错误现象:
-
http.ErrBodyTooLarge永远不会触发 —— 因为io.LimitReader不认识这个 error - 上传大文件时内存没爆,但临时磁盘被写满(
ParseMultipartForm落盘不受控) - 客户端断连后 goroutine 卡在
Read上,连接堆积
必须用 http.MaxBytesReader 替代 io.LimitReader 处理 HTTP Body
http.MaxBytesReader 是专为 HTTP 设计的封装:它会在超限时主动写入 413 Payload Too Large 响应头、关闭底层 r.Body、记录日志,并确保连接清理。
实操要点:
- 必须把返回值重新赋给
r.Body,否则无效:r.Body = http.MaxBytesReader(w, r.Body, 10 - 要在
ParseMultipartForm之前设置,否则 multipart 解析器可能绕过限制 - 不要和
io.ReadAll混用;超限后r.Body.Read会返回http.ErrBodyTooLarge,需显式判断 - 若用中间件统一限流,确保每个请求都走同一套包装逻辑,避免 handler 里重复赋值
io.LimitReader 的正确使用场景:非 HTTP 流式输入
它适合纯 io.Reader 场景,比如解析上传的单个文件流、读取日志管道、代理 TCP 连接数据等 —— 只要你不依赖协议层反馈,且能自己管理生命周期。
关键约束:
-
n为负数或 0 时,首次Read就返回0, io.EOF - 底层 reader 若提前返回
io.EOF(如文件本身只有 500KB),io.LimitReader不补零也不报错,直接结束 - 多次调用
Read时计数累积,重置必须新建实例,不能复用 - 和
bufio.Scanner混用会出问题 —— 扫描器内部预读可能触发提前io.EOF
真正防 DoS 的三道防线缺一不可
只靠一个限流机制远远不够。攻击者会绕过任何单点防护:
-
Content-Length快速拒绝:对已知长度的请求,开头就检查r.ContentLength > max并直接http.Error -
http.MaxBytesReader流式拦截:覆盖 chunked 编码等Content-Length == -1的情况 -
ParseMultipartForm参数协同:设maxMemory控制内存缓存,但总大小仍由http.MaxBytesReader限定
最容易被忽略的是:客户端断连时 http.MaxBytesReader 不会自动 cleanup,你得靠 context.Context 超时或主动监听连接关闭来回收资源。











