深入 Play 07:Filter 在路由、解析错误和业务失败之间怎样执行
未知路由返回 404,JSON parser 拒绝返回 400,实验 Gate 提前返回 403,这三种响应都带有外层 Filter 加上的头。控制器同步抛出或返回失败 Stage 时,客户端得到 500,却没有执行相同的成功回调。
状态码属于 HTTP 响应;Stage 成功或失败属于异步计算。二者不能混为一谈。一个 400 Result 可以由成功完成的 Stage 传给 Filter,而失败 Stage 在服务器错误处理中被转换为 500,未必再经过 Filter 的 thenApply。
Router 先选 handler,Filter 再包装
DefaultHttpRequestHandler 的普通请求路径,先执行 routeWithFallback,再调用 filterHandler 包装选定的 handler。选不到普通路由时也会构造处理未匹配请求的 handler,因此 Filter 有机会覆盖应用层的 404。
1 | |
顺序解释了两个容易混淆的现象。Filter 可以检查已经选定的路由信息,但在 Filter 内只改变 RequestHeader 的 path,不会让默认处理器重新选择另一条路由。真正需要改路由选择时,应在选择前的入口处理,或者显式设计自己的 HttpRequestHandler。
未知路由“经过 Filter”也有条件:请求已经进入当前应用处理器、路径处在适用范围,选定 handler 能由该链包装。协议层拒绝、服务器尚未加载应用、开发 web command 或其他 handler 类型都不由一次普通 HTTP 实验覆盖。
官方Java Filter 指南明确区分 Filter 与 Action 组合的范围。选择全局入口时应检查实际 handler 类型,不能从“全局”这个名字推导所有协议和启动失败都受保护。
两层 Filter 怎样折叠
工程按顺序启用 PipelineHeaderFilter 与 PipelineGateFilter。它们只在存在 X-Pipeline-Id 时记录实验事件,原有 TraceFilter 继续负责 00–05 的回归。这样可以把新时序与旧 trace 分开。
默认处理器使用 filters.foldRight(next) 组合,列表靠前者包住后者。本例的 Header 位于 Gate 外层。调用的进入方向与结果回传方向相反:
1 | |
配置列表与依赖注入创建顺序也不是同一事实。需要检查启用的 filter 列表、组合实现和请求 trace;某个对象先被 injector 构造,不足以证明它在请求中先调用。
Header Filter 通过下一层取得 Stage,再变换 Result:
1 | |
Result.withHeader 返回带新头的结果对象,Stage 将它传给外层;不能调用方法后丢弃返回值,再期望原结果原地变化。请求头也应通过不可变复制传递,不能用共享可变字段临时保存本次请求。
这里的 X-Pipeline-Security 是观测标记。它说明本次成功结果经过了外层变换,不提供真实浏览器安全策略。生产 CSP、代理头信任、CORS 与认证需要分别配置和验证。
提前返回为何省掉 parser
Gate 检查合成头 X-Pipeline-Block: yes。匹配时记录拒绝,直接返回一个已完成的 403 Stage:
1 | |
这一次调用没有执行 next.apply,下层 EssentialAction 的 parser 不会被 Gate 主动启动。外层 Header 仍然取得正常的 403 Result,所以能够继续增加响应头。
本批向 /pipeline/json 发送畸形 JSON {,同时设置 block 头,得到的稳定事件是:
1 | |
parserCalls 为零,requestBusinessCalls 为零。这比“接口返回 403”多证明了两项事实:应用 parser 没有调用,控制器业务方法没有进入。它仍不测量网络读取、后端缓冲或服务器为复用连接而处理剩余数据的方式。
Gate 位于 Header 内层,因此拒绝响应仍带观测头。如果把两个 Filter 交换,外层 Gate 可以在 Header 尚未调用时返回,拒绝响应就不会获得 Header 的修改。安全头与拒绝规则的排序必须以这些短路路径验收。
Java Filter 的接口为何没有请求体
Java Filter 接收的是 RequestHeader 和返回 Stage 的 next。它不像控制器那样直接取得已经解析的 Http.Request,但整个链仍然必须交给服务器一个消费 ByteString 的 Accumulator。
Java Filter 将接口桥接到 Scala Filter;Filters.scala 用两个 Promise 连接这两种表示。
一个 Promise 提供下一层产生的 Result;另一个提供交给框架的 body Accumulator。Filter 调用 next 时,适配器取得 next(rh) 的 Accumulator,后续消费完成再把 Result 交给前一个 Promise。Filter 自己生成结果、没有调用 next 时,适配器以 Accumulator.done 作为 fallback。
这不是把 body 先无条件读完再执行 Filter。适配器需要先知道 Filter 是否继续,才能确定采用下层 parser 还是直接完成。两个 Promise 对应的是“消费器是否就绪”和“结果是否就绪”两项状态。
EssentialFilter 直接包装 EssentialAction,能够参与更底层的 Accumulator 或流变换;普通 Java Filter 将这种细节收敛为 Stage 接口。若要检查完整 body,必须明确缓冲上限和新的消费流程,不能把字节流读取后假定下一层仍能再次取得相同数据。
请求体读取通常是一条流的消费。全局日志只需记录头与有限元数据时,使用 Filter 即可;需要流级计数、复制或改写时,应在合适的 EssentialFilter/parser 入口处理,并为额外缓冲与取消承担责任。
解析拒绝与失败 Stage 的差别
parser 的 Either.Left(Result) 表示提前生成响应。适配后它仍然可以正常完成结果 Stage,因此 Header、Gate 的 thenApply 能观察 400、413 或 415。
业务同步 throw 和失败 Stage 没有正常 Result。本例两个 Filter 都只提供 thenApply,它们没有失败回调。真实 HTTP 中,Action 的两层失败事件出现,而 gate-exit-500 与 header-exit-500 都没有出现。
| 场景 | 客户端状态 | 外层 header | parser/业务 | Filter 成功退出 |
|---|---|---|---|---|
| 合法 JSON | 200 | 有 | 均执行 | 两层 |
| 未知路径 | 404 | 有 | 业务不执行 | 两层 |
| route 参数绑定失败 | 400 | 有 | 业务不执行 | 两层 |
| 畸形 JSON | 400 | 有 | parser 执行,业务不执行 | 两层 |
| Gate 拒绝 | 403 | 有 | parser 与业务均不执行 | 两层 |
| 同步 throw / failed Stage | 500 | 无实验 header | 业务进入后失败 | 均未执行 |
这个差别决定了计时和日志应该使用哪一种回调。记录所有 Stage 终态,需要成功与失败都覆盖的观察;只调整正常 Result,可以使用 thenApply。将失败恢复为结果则会改变控制流,必须明确保留的状态和异常信息。
若产品要求所有应用错误响应都有某些安全头,应检查 HttpErrorHandler 与服务器错误转换路径,不能只加一个成功回调就宣布覆盖完成。传输层还可能在应用之外返回错误,保证范围应明确到入口。
Filter 计时的结束点
next 返回的 Stage 在 Result 可用时完成。本批 header-exit 发生在此时;它没有等待流式正文最终发送完。给指标命名为“响应完全结束”,会把计算完成与传输结束混在一起。
日志中至少应保留阶段名称:请求进入、Result 可用、响应流结束以及业务提交。哪个事件用于延迟统计,应由要回答的问题决定。远程业务完成之后也可能发送失败;客户端提前断开也未必停止业务。
Filter 是组合边界,不能自动承担连接池、文件和流资源的所有释放。持有资源的组件应该将终止信号连接到自身清理;仅在 Filter 增加 finally 不足以验证异步资源释放。
验证入口与适用范围
在 play-lab/ 按 README 固定 JDK 与 launcher 后运行:
1 | |
证据在 evidence/batch06-08/:两种服务器入口各 118 请求通过,trace 和计数均保存。JUnit 另覆盖已知与未知路径的 Gate 拒绝。上游 EnabledFiltersSpec 用于核对启用列表,不代表本批运行了整个上游测试。
| 要解决的问题 | 放置位置 | 容易遗漏的路径 |
|---|---|---|
| 应用请求头记录与正常响应变换 | Filter | failed Stage 与流终止 |
| 在路由选择前重写目标 | HttpRequestHandler 等前置入口 | fallback 与协议范围 |
| 指定业务方法的公共条件 | Action 组合 | 默认 parser 先拒绝 |
| 请求体流级操作 | EssentialFilter / parser | 缓冲上限、单次消费与取消 |
框架边界明确后,指标和拒绝规则才能使用同一套阶段定义;实际 trace 则用来核验配置是否建立了预期的包装关系。
参考资料与继续阅读
参考 HttpRequestHandler、Java Filter 指南 和 Scala Filter 适配。累计代码可从第 00 篇源码包下载。
上一篇:Action 组合。下一篇:BodyParser。
