[翻译]Filters

来源:互联网 发布:淘宝神兽金刚玩具 编辑:程序博客网 时间:2024/06/18 16:19

源https://www.playframework.com/documentation/2.5.x/JavaHttpFilters
Play提供了简单的过滤器api,可以将全局的过滤器应用于每个请求。

Filters vs action composition

过滤器api设计为无差别的应用于所有路由的横切关注点(AOP),有如下应用:
日志/度量数据收集(Logging/metrics collection)
GZIP编码(GZIP encoding)
安全头部(Security headers)
相对应的action组合设计为路由的特殊关注点,例如验证授权,缓存等等。如果你的过滤器不想用于每一个路由,action组合更有效。你能够创建自己的action构建器组合自定义的actions集合到每一个路由,以便最小化样板式代码。

日志过滤器

下面是一个简单的日志过滤器,用来记录每一个请求的时间和次数:

import java.util.concurrent.CompletionStage;import java.util.function.Function;import javax.inject.Inject;import akka.stream.Materializer;import play.Logger;import play.mvc.*;public class LoggingFilter extends Filter {    @Inject    public LoggingFilter(Materializer mat) {        super(mat);    }    @Override    public CompletionStage<Result> apply(            Function<Http.RequestHeader, CompletionStage<Result>> nextFilter,            Http.RequestHeader requestHeader) {        long startTime = System.currentTimeMillis();        return nextFilter.apply(requestHeader).thenApply(result -> {            long endTime = System.currentTimeMillis();            long requestTime = endTime - startTime;            Logger.info("{} {} took {}ms and returned {}",                requestHeader.method(), requestHeader.uri(), requestTime, result.status());            return result.withHeader("Request-Time", "" + requestTime);        });    }}

我们注意到的第一点是apply方法的使用。第一个参数nextFilter是一个方法,入参为请求头部出参为执行结果,第二个参数requestHeader是请求的真正的头部。
参数nextFilter是代表过滤器链中的下一个action,执行它会执行这个action。在大多数情况下你想要在将来的某个点上执行它。如果你想要阻塞这个请求你也可以选择不执行它。
我们在执行链上的下一个过滤器之前保存了一个时间点,执行下一个过滤器返回CompletionStage<Result>。看一下Handling asynchronous results这一张会对一步结果有更深的了解。我们通过调用thenApply方法获取将来的Result。计算并记录请求执行的时间,通过调用result.withHeader("Request-Time", "" + requestTime)还可以把结果放入response头部返回给客户端。

Using filters

使用过滤器最简单的方法是实现HttpFilters接口,例如根目录下的Filters。最典型的,你可以扩展DefaultHttpFilters 类,将你的过滤器作为构造器参数。

import play.mvc.EssentialFilter;import play.http.DefaultHttpFilters;import play.filters.gzip.GzipFilter;import javax.inject.Inject;public class Filters extends DefaultHttpFilters {  @Inject  public Filters(GzipFilter gzip, LoggingFilter logging) {    super(gzip, logging);  }}

如果你想在不同的环境中使用不同的过滤,或者想要不将这个类放在根目录下,你可以设置application.conf 下的play.http.filters参数,Play会字的弄查找这个具体类。例如:
play.http.filters=com.example.Filters

Where do filters fit in?

Filters会在路由器找到action之后包裹action,这意味着你不能使用过滤器改变路径(path,请求路径),方法或者查询参数来影响路由器。尽管你可以在过滤器中使请求调用另外的action,但这回绕开过滤器链中的其他过滤器。如果你想要在路由器之前改变请求,一种更好的方法是把你的逻辑放在HttpRequestHandler中。
因为过滤器在路由之后执行,所以通过RequestHeader的标签映射可以获取请求的路由信息。例如,你想记录执行的action方法。这种情况下,你可以这样更新过滤器:

import java.util.concurrent.CompletionStage;import java.util.function.Function;import java.util.Map;import javax.inject.Inject;import akka.stream.Materializer;import play.Logger;import play.mvc.*;import play.routing.Router.Tags;public class RoutedLoggingFilter extends Filter {    @Inject    public RoutedLoggingFilter(Materializer mat) {        super(mat);    }    @Override    public CompletionStage<Result> apply(            Function<Http.RequestHeader, CompletionStage<Result>> nextFilter,            Http.RequestHeader requestHeader) {        long startTime = System.currentTimeMillis();        return nextFilter.apply(requestHeader).thenApply(result -> {            Map<String, String> tags = requestHeader.tags();            String actionMethod = tags.get(Tags.ROUTE_CONTROLLER) +                "." + tags.get(Tags.ROUTE_ACTION_METHOD);            long endTime = System.currentTimeMillis();            long requestTime = endTime - startTime;            Logger.info("{} took {}ms and returned {}",                actionMethod, requestTime, result.status());            return result.withHeader("Request-Time", "" + requestTime);        });    }}

注意:路由标签是Play路由器的特性。如果你想要使用传统路由器参数是不可以的。

More powerful filters

Play提供了一个更低等级的过滤器API——EssentialFilter,它可以进去请求的body里面。这个API允许你用另一个action包裹EssentialAction。
以下是重写EssentialFilter的例子:

import java.util.concurrent.Executor;import javax.inject.Inject;import akka.util.ByteString;import play.Logger;import play.libs.streams.Accumulator;import play.mvc.*;public class EssentialLoggingFilter extends EssentialFilter {    private final Executor executor;    @Inject    public EssentialLoggingFilter(Executor executor) {        super();        this.executor = executor;    }    @Override    public EssentialAction apply(EssentialAction next) {        return EssentialAction.of(request -> {            long startTime = System.currentTimeMillis();            Accumulator<ByteString, Result> accumulator = next.apply(request);            return accumulator.map(result -> {                long endTime = System.currentTimeMillis();                long requestTime = endTime - startTime;                Logger.info("{} {} took {}ms and returned {}",                    request.method(), request.uri(), requestTime, result.status());                return result.withHeader("Request-Time", "" + requestTime);            }, executor);        });    }}

除了创建一个新的EssentialAction 包裹下一个action传过来的内容,关键的不同是当我们调用next的时候,我们得到了一个Accumulator。 You could compose this with an Akka Streams Flow using the through method some transformations to the stream if you wished. We then map the result of the iteratee and thus handle it.(你可以调用through方法将它和Akka Streams Flow组合…这两句翻译不好)
注意:尽管看上去好像有两个不同的API,但是实际上只有一个EssentialFilter。简单的Filter API是继承EssentialFilter的更早的例子,并且通过创建一个新的EssentialAction实现EssentialFilter。过去在回调中通过一条针对Result的规则跳过了body的parsing,虽然body parsing和action剩下的部分是异步执行的。

原创粉丝点击