不能直接用 io.ReadFull 或 bufio.Scanner 限制请求体长度,因二者无法精确控制总字节数、错误模糊且不自动关闭连接;应优先使用 http.MaxBytesReader,在 ParseForm 前包裹 req.Body 并显式检查 http.ErrBodyTooLarge。

为什么不能直接用 io.ReadFull 或 bufio.Scanner 限制请求体长度
因为 http.Request.Body 是流式读取的,io.ReadFull 不会提前终止读取,而 bufio.Scanner 默认最大 token 长度是 64KB,且错误信息模糊(比如只报 scanner: token too long),无法精确控制“整个请求体”的字节上限。更关键的是,如果客户端发送超长数据,服务端仍会持续接收并缓冲,可能被恶意利用耗尽内存。
用 http.MaxBytesReader 包裹 req.Body 是最稳妥的做法
http.MaxBytesReader 是 Go 标准库专为此类场景设计的包装器,它会在读取超过指定字节数后返回 http.ErrBodyTooLarge,并自动关闭底层连接,防止后续数据继续流入。
- 必须在调用
req.ParseForm()、req.FormValue()或任何读取 Body 的操作前设置,否则已读部分无法回退 - 传入的
max值应包含所有可能解析的内容:比如表单中多个字段 + 分隔符 + 编码开销,建议留出 1–2KB 余量 - 错误需显式检查:
if errors.Is(err, http.ErrBodyTooLarge),而不是用字符串匹配
maxLen := int64(1024 * 1024) // 1MB
req.Body = http.MaxBytesReader(w, req.Body, maxLen)
err := req.ParseForm()
if err != nil {
if errors.Is(err, http.ErrBodyTooLarge) {
http.Error(w, "request body too large", http.StatusRequestEntityTooLarge)
return
}
http.Error(w, "bad request", http.StatusBadRequest)
return
}如果要用 io.LimitReader,得自己处理边界和错误语义
io.LimitReader 只是截断读取,不会主动返回 http.ErrBodyTooLarge,也不会关闭连接。它适合你明确知道后续只读固定长度(比如只取前 1KB 做签名校验),但不适合做通用请求体限制。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 读取结束后,剩余数据仍留在网络缓冲区,可能干扰下一次请求(尤其复用连接时)
- 无法区分“刚好读完”和“被截断”,需额外判断
n < expected并手动返回 413 - 不兼容
req.ParseMultipartForm,因为 multipart 解析器内部会多次调用Read,LimitReader无法保证整体长度守恒
注意 Content-Length 和传输编码的影响
客户端可能省略 Content-Length(比如用 Transfer-Encoding: chunked),此时 http.MaxBytesReader 依然有效,它按实际读取字节数计数,与头部声明无关。但要注意:
立即学习“go语言免费学习笔记(深入)”;
- 若前端用
fetch发送 FormData,浏览器自动设Content-Type: multipart/form-data; boundary=...,此时req.ParseMultipartForm内部会先读 boundary 行再解析,MaxBytesReader的限制要覆盖这部分开销 - 某些代理或网关会重写
Content-Length,以实际读取为准更可靠 - 不要依赖
req.ContentLength做判断——它可能为 -1(未知长度),也可能被伪造
实际部署时最容易忽略的是:限制逻辑必须放在中间件最外层,且要在任何其他读取 Body 的操作之前生效。一旦 req.Body 被某处无意读取(比如日志中间件调用了 io.ReadAll),再包装就无效了。

















