PlayScala 2.5.x - Filter开发指南

原创
2016/06/01 12:04
阅读数 873

1. Filter简介

Filter是Play基于责任链模式(Chain of Responsibility)实现的过滤器,利用Filter可以过滤所有的请求和响应。Play的Filter实现非常灵活,你可以在Filter中修改请求和响应,或终止Filter链的传递,直接返回响应。Filter常用于以下几种场景:

  • 打印请求日志
  • 统计请求信息
  • 启用Gzip压缩
  • 添加安全响应头
  • 实现全局缓存

Play中实现的Filter API有两个,分别是EssentialFilter和Filter。其中EssentialFilter是底层API,功能更加强大,而Filter是基于EssentialFilter实现的用于简化开发的类,主要目的简化接口实现,隐藏Request Body的处理。本文的示例均使用底层的EssentialFilter实现。

2. Filters vs Action Composition vs Request Handler

1)  router调用次序不同

Filters和Action Composition发生在router调用之后,二者无法改变Request Path,但是仍然可以修改Request Headers和Body;Request Handler发生在router调用之前,可以通过修改Request Path将请求重定向到特定的Action。

2)  关注点不同

Request Handler关注点在于过滤非法请求或重定向路由;Filter的关注点在于请求和响应的过滤处理;Action Composition主要关注点在于权限验证和授权。

3. 使用Filter

首先基于EssentialFilter实现一个LoggingFilter:

class LoggingFilter @Inject() (implicit ec: ExecutionContext) extends EssentialFilter {
  def apply(nextFilter: EssentialAction) = new EssentialAction {
    def apply(requestHeader: RequestHeader) = {
      val startTime = System.currentTimeMillis      
      nextFilter(requestHeader).map { result =>
        val requestTime = System.currentTimeMillis - startTime
        Logger.info(s"${requestHeader.path} took ${requestTime}ms")
        result.withHeaders("Request-Time" -> requestTime.toString)
      }
    }
  }
}

在root package先添加一个Filters类:

class Filters @Inject() (
  log: LoggingFilter
) extends HttpFilters {
  val filters = Seq(log)
}

启动应用,在控制台查看日志输出。

4. 深入剖析

示例代码请参考这里,示例中定义了三个Filter,分别是Filter1, Filter2和Filter3,每个Filter在接收到请求和响应的时候会打印信息到控制台,在Filter链中的定义顺序如下:

Seq(filter1, filter2, filter3)

针对一次请求控制台输出如下:

Filter1 receive request
Filter2 receive request
Filter3 receive request
Filter3 receive response
Filter2 receive response
Filter1 receive response

上面的输出很有意思,看起来很像一次递归调用,Filter1递进调用Filter2, Filter2递进调用Filter3,Filter3取回结果回归到Filter2,Filter2取回结果回归到Filter1。

我们从源码来寻找一些蛛丝马迹,Filter Chain是在HttpRequestHandler中被调用的,代码如下:

  /**
   * Apply filters to the given action.
   */
  protected def filterAction(next: EssentialAction): EssentialAction = {
    filters.foldRight(next)(_ apply _)
  }

filters.foldRight的调用过程如下:

     apply
    /     \
filter1  apply
        /     \
    filter2  apply
            /     \
        filter3   EssentialAction

上图中apply节点的类型为EssentialAction,每个apply节点是其下EssentialAction的delegator对象。最上面的apply节点返回的EssentialAction便是Filter Chain的执行起点,由于最终的Result是由右下方的EssentialAction执行生成的,所以整个Filter Chain的执行过程看起来就像是一个递归调用,这也就解释了上面的控制台输出结果。

5. 灵活使用Filter

从上面的分析结果可以看出,Filter在Filter Chain中的顺序是很重要的,放错位置就会得到意想不到的结果。需要修改所有响应的Filter应该放在最前面,例如Gzip Filter,Security Headers Filter。因为其它的Filter可能会终止Filter Chain的传递直接返回响应,如果将Gzip Filter放在其后面,将导致Gzip Filter没有机会修改响应结果,从而导致返回非压缩响应。

-------------------------------------------------转载请注明作者joymufeng------------------------------------------

6. 参考

Play Framework - Filters

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
2 收藏
2
分享
返回顶部
顶部