PlayScala 2.5.x - Filter開發指南

1. Filter簡介

Filter是Play基於責任鏈模式(Chain of Responsibility)實現的過濾器,利用Filter能夠過濾全部的請求和響應。Play的Filter實現很是靈活,你能夠在Filter中修改請求和響應,或終止Filter鏈的傳遞,直接返回響應。Filter經常使用於如下幾種場景:git

  • 打印請求日誌
  • 統計請求信息
  • 啓用Gzip壓縮
  • 添加安全響應頭
  • 實現全局緩存

Play中實現的Filter API有兩個,分別是EssentialFilter和Filter。其中EssentialFilter是底層API,功能更增強大,而Filter是基於EssentialFilter實現的用於簡化開發的類,主要目的簡化接口實現,隱藏Request Body的處理。本文的示例均使用底層的EssentialFilter實現。github

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:app

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類:spa

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

啓動應用,在控制檯查看日誌輸出。scala

4. 深刻剖析

示例代碼請參考這裏,示例中定義了三個Filter,分別是Filter1, Filter2和Filter3,每一個Filter在接收到請求和響應的時候會打印信息到控制檯,在Filter鏈中的定義順序以下:日誌

Seq(filter1, filter2, filter3)

針對一次請求控制檯輸出以下:code

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。router

咱們從源碼來尋找一些蛛絲馬跡,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

相關文章
相關標籤/搜索