Revel中间件本质是Filter函数而非HTTP handler,类型为func(*revel.Controller, []revel.Filter),作用于Controller实例;必须插入revel.Filters全局切片,位置关键;Filter与Interceptor用途不同,前者管道级、后者Action粒度;读取请求体需在ParamsFilter前手动处理。

Revel中间件本质是Filter函数,不是HTTP handler
Revel的中间件不是标准的http.Handler,而是类型为func(c *revel.Controller, filterChain []revel.Filter)的函数。它不操作http.ResponseWriter和*http.Request,而是直接作用于*revel.Controller实例——这个对象已经封装了请求解析、session、flash、参数绑定等上下文。写错成普通handler会导致编译失败或运行时panic。
常见错误现象:
- 误用
http.HandlerFunc签名,导致无法访问c.Session或c.Params - 忘记调用
filterChain[0](c, filterChain[1:]),整个过滤器链中断,后续所有逻辑(包括路由、action)都不执行 - 在filter里返回
revel.Result但没终止流程,结果被覆盖或重复应用
全局Filter必须插入到revel.Filters切片中
Revel只认revel.Filters这个全局变量作为入口过滤器链,它在app/init.go的init()函数中初始化。你不能靠注册函数动态添加,必须显式重写该切片。
实操建议:
- 不要在
init()外修改revel.Filters,否则可能被框架初始化覆盖 - 插入位置很关键:比如鉴权Filter必须放在
SessionFilter之后(才能读session),但必须在ActionInvoker之前(才能阻止未授权调用) - 若需跳过某个默认Filter(如禁用
FlashFilter),直接从切片中删掉对应项即可,无需额外配置开关
示例:revel.Filters = append(revel.Filters[:7], MyAuthFilter, revel.Filters[7:]...) —— 在I18nFilter后、InterceptorFilter前插入
拦截器(Interceptor)和Filter用途完全不同
别混淆Filter和Interceptor:Filter是请求管道级的,影响所有请求;Interceptor是Controller/Action粒度的,只对特定方法生效,且只能在BEFORE/AFTER等四个生命周期点运行。
关键区别:
-
Filter能终止整个请求(设置c.Result并return),Interceptor返回revel.Result也会终止,但仅限于当前生命周期阶段(如BEFORE返回Result会跳过action,AFTER返回则可能被后续拦截器覆盖) -
Interceptor可定义为方法(func(c *MyController) AuthCheck() revel.Result),能直接访问controller字段;Filter只能通过c参数间接访问 -
Interceptor不参与请求链路调度,它由InterceptorFilter统一调用,而InterceptorFilter本身只是revel.Filters中的一项
Filter里读取请求体要格外小心
Revel默认在ParamsFilter阶段就已读取并解析body(用于表单、JSON绑定),所以你在自定义Filter中再次调用c.Request.Body.Read()会得到空内容或报http: read on closed response body错误。
正确做法:
- 如果只需检查Content-Type或Header,直接读
c.Request.Header,安全 - 如果真要读原始body(比如做签名验签),必须在
ParamsFilter之前插入你的Filter,并手动ioutil.ReadAll(c.Request.Body),然后用bytes.NewBuffer重新包装回c.Request.Body - 避免在Filter中调用
c.Params.Get或c.Params.All()后再读body,顺序错乱会导致参数解析失败
最容易被忽略的一点:Revel没有提供“body重放”机制,一旦body被读取一次,后续所有Filter和Action都拿不到原始字节流——这点和Express/Koa的req.rawBody或Gin的c.ShouldBind行为完全不同。











