Revel框架默认不限制请求体大小,因基于net/http且无内置限流机制,必须手动用http.MaxBytesReader包装r.Body,否则恶意大请求将导致OOM或连接池耗尽。

Revel 框架本身不提供类似 Gin 的 MaxRequestBodySize 配置项,也没有内置的全局请求体大小限制机制——所有限制必须在 Go 标准库层面手动介入,否则恶意大请求会直接耗尽内存或拖垮连接池。
为什么 Revel 默认不限制 POST/PUT 表单大小
Revel 基于 net/http,而 Go 标准库的 http.Server 默认对 r.Body 完全不设上限。即使你调用 c.Request.ParseForm() 或 c.Request.ParseMultipartForm(8 ,也只是控制内存缓存部分;攻击者仍可提交一个 500MB 的 <code>multipart/form-data 请求,其中大部分落盘但依然被完整读取——除非你提前掐断。
常见误判点:
- 以为
c.Params.Get("xxx")触发了安全解析 → 实际上它只是从已存在的c.Request.Form里取值,而该 map 只有在显式调用ParseForm()后才填充,且无大小校验 - 在控制器里写
c.Request.ParseMultipartForm(8 就以为限了总大小 → 错,<code>maxMemory参数只管内存缓冲区,磁盘部分完全放行 - 依赖 Nginx 的
client_max_body_size就高枕无忧 → 生产中必须服务端+网关双保险,否则绕过代理直连时失效
必须用 http.MaxBytesReader 包装 r.Body
这是唯一可靠、生效位置可控的方式。你得在任何可能读取 r.Body 之前完成包装,典型位置是 Revel 的 Filter 链(比如 BODY_READER_FILTER)。
示例(放入 app/init.go 的 filter 注册处):
revel.InterceptFunc(func(c *revel.Controller, fc revel.FilterConfig) revel.Result {
// 仅对 POST/PUT/PATCH 且含表单类 Content-Type 的请求限流
ct := c.Request.Header.Get("Content-Type")
if (c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "PATCH") &&
(strings.HasPrefix(ct, "application/x-www-form-urlencoded") ||
strings.HasPrefix(ct, "multipart/form-data")) {
const maxBodySize = 10 << 20 // 10MB
c.Request.Body = http.MaxBytesReader(c.Response.Out, c.Request.Body, maxBodySize)
}
return nil
}, revel.BODY_READER_FILTER)
关键点:
-
http.MaxBytesReader在第一次Read()时才开始计数,所以必须放在BODY_READER_FILTER(早于PARAMS_FILTER和VALIDATION_FILTER) - 不要用
http.MaxBytesHandler包裹整个revel.Handler→ Revel 的路由匹配逻辑依赖原始http.Request,包装后会导致c.Params初始化失败 - 若需差异化限制(如
/upload放宽到 100MB,其余接口保持 10MB),应在 filter 中按c.Request.URL.Path分支判断
multipart 场景要组合两层限制
只靠 http.MaxBytesReader 卡总大小还不够:攻击者可构造一个超多小字段的表单,触发大量内存分配(如 10 万个 name="field" 字段),绕过字节限制但压垮 GC。
正确做法是两步收紧:
- 先用
http.MaxBytesReader设硬上限(如 50MB),防止裸奔上传 - 再在控制器里调
c.Request.ParseMultipartForm(4 ,把 <code>maxMemory设小(如 4MB),确保多数字段进内存、大文件落盘,避免ParseMultipartForm自身因解析开销崩溃 - 后续取值务必用
c.Request.MultipartForm.File["file"]获取磁盘句柄,别用c.Request.FormValue("text_field")—— 后者依赖已加载的内存数据,可能因字段过多 OOM
最易被忽略的是:Revel 的 ParseForm() 和 ParseMultipartForm() 都不校验总长度,也不重置 r.Body 的读取位置。一旦你在中间件或控制器里提前调了 io.ReadAll(r.Body) 或 r.ParseForm(),再套 MaxBytesReader 就完全无效——因为此时 r.Body 已是空的 bytes.Reader 或 nil。


















