Documentation

You are viewing the documentation for the 2.2.0 release in the 2.2.x series of releases. The latest stable release series is 2.4.x.

§フィルター

Play は、あらゆるリクエストに適用するグローバルフィルター向けの、シンプルなフィルター API を提供しています。

§フィルター vs アクション合成

フィルター API は、すべてのルートに無差別に適用される横断的な関心事を対象としています。フィルターの一般的なユースケースは、例えば以下のようなものです。

対照的に、アクション合成 は認証や認可、キャッシュなど、特定のルートに対する関心事を対象としています。もし、フィルターをすべてのルートに適用したいのでなければ、代わりにアクション合成の使用を検討してみてください。アクション合成はフィルターよりも遙かに強力です。また、定型的なコードを最小限にするために、ルート毎に独自の定義済みアクション群を構成する、アクションビルダーを作成できることも忘れないでください。

§シンプルなロギングフィルター

以下は、Play framework があるリクエストを処理するためにどれくらい時間が掛かったのか計測してロギングする、シンプルなフィルターです:

import play.api.mvc._

object LoggingFilter extends Filter {
  def apply(next: (RequestHeader) => Result)(rh: RequestHeader) = {
    val start = System.currentTimeMillis

    def logTime(result: PlainResult): Result = {
      val time = System.currentTimeMillis - start
      Logger.info(s"${rh.method} ${rh.uri} took ${time}ms and returned ${result.header.status}")
      result.withHeaders("Request-Time" -> time.toString)
    }
    
    next(rh) match {
      case plain: PlainResult => logTime(plain)
      case async: AsyncResult => async.transform(logTime)
    }
  }
}

ここで何が起きているのか理解してみましょう。まず最初に気付くべき点は apply メソッドのシグネチャです。これはカリー化された関数で、第一引数はリクエストヘッダーを受け取って結果を返す関数となる next で、第二引数はリクエストヘッダーとなる rh です。

next 引数は、フィルターチェーンにおける次のアクションを表現しています。これを実行すると、次に呼ばれるべきアクションが起動します。ほとんどの場合において、後続の処理のいくつかのタイミングで次のアクションを実行したくなることでしょう。何らかの理由により、リクエストをブロックしたいのであれば、次のアクションを実行しないと決断するのも良いかもしれません。

rh 引数は、このリクエストの実際のヘッダーです。

コードの次の部分は、リクエストのログを出力する関数です。この関数は PlainResult を受け取り、リクエストの処理に掛かった時間をログに出力した後、レスポンスにこの Request-Time を記録するヘッダを記録して返します。

最後に、次のアクションが実行され、このアクションが返す結果のパターンマッチが行われます。結果は PlainResult または AsyncResult のどちらかであり、AsyncResult は最終的には PlainResult として実行されます。いずれの場合においても logTime 関数が実行される必要がありますが、その実行方法はそれぞれ少しだけ異なっています。PlainResult の場合、結果はすぐに利用できるので logTime を直ちに実行します。一方、AsyncResult の場合、結果はまだ利用できません。そのため logTime 関数は、PlainResult が利用できるようになってから実行されるよう transform メソッドに渡されます。

§簡易な文法

より簡易な文法でフィルタを宣言することができます:

val loggingFilter = Filter { (next, rh) =>
  val start = System.currentTimeMillis

  def logTime(result: PlainResult): Result = {
    val time = System.currentTimeMillis - start
    Logger.info(s"${rh.method} ${rh.uri} took ${time}ms and returned ${result.header.status}")
    result.withHeaders("Request-Time" -> time.toString)
  }
    
  next(rh) match {
    case plain: PlainResult => logTime(plain)
    case async: AsyncResult => async.transform(logTime)
  }
}

これは val なので、なんらかのスコープ内においてのみ使用することができます。

§フィルターを使う

もっともシンプルにフィルターを使うには、Global オブジェクトで WithFilters トレイトを継承します:

import play.api.mvc._

object Global extends WithFilters(LoggingFilter, new GzipFilter()) {
  ...
}

手動でフィルターを実行することもできます:

import play.api._

object Global extends GlobalSettings {
  override def doFilter(action: EssentialAction) = LoggingFilter(action)
}

§フィルターはどこに合う?

フィルターは、アクションがルーターによって見つけられた後に、そのアクションをラップします。これは、フィルターを使ってルーターに影響を与えるパスやメソッド、そしてクエリパラメーターを変換できないことを意味します。フィルターから別のアクションを実行することで、リクエストをそのアクションに移動させてしまうこともできますが、これをするとフィルターチェーンの残りをバイパスしてしまうことに気を付けてください。ルーターが実行される前にリクエストを変更する必要がある場合は、フィルターを使う代わりに、そのロジックを Global.onRouteRequest に配置するのが、より良いやり方でしょう。

フィルターはルーティングが完了した後に適用されるので、RequestHeadertags マップによってリクエストからルーティング情報にアクセスすることができます。例えば、アクションメソッドに対する実行時間をログに出力したいとします。この場合、logTime を以下のように書き換えることができます:

  def logTime(result: PlainResult): Result = {
    val time = System.currentTimeMillis - start
    val action = rh.tags(Routes.ROUTE_CONTROLLER) + "." + rh.tags(Routes.ROUTE_ACTION_METHOD)
    Logger.info(s"${action} took ${time}ms and returned ${result.header.status}")
    result.withHeaders("Request-Time" -> time.toString)
  }

ルーティングタグは Play ルーターの機能です。独自のルーターを使ったり、Glodal.onRouteRequest から独自のアクションを返す場合は、これらのパラメーターは利用できない場合があります。

§より強力なフィルター

Play は リクエストボディ全体にアクセスすることのできる、EssentialFilter と呼ばれるより低レベルなフィルター API を提供しています。この API により、EssentialAction を他のアクションでラップすることができます。

上記のフィルター例を EssentialFilter として書き直すと、以下のようになります:

import play.api.mvc._

object LoggingFilter extends EssentialFilter {
  def apply(next: EssentialAction) = new EssentialAction {
    def apply(rh: RequestHeader) = {
      val start = System.currentTimeMillis

      def logTime(result: PlainResult): Result = {
        val time = System.currentTimeMillis - start
        Logger.info(s"${rh.method} ${rh.uri} took ${time}ms and returned ${result.header.status}")
        result.withHeaders("Request-Time" -> time.toString)
      }
    
      next(rh).map {
        case plain: PlainResult => logTime(plain)
        case async: AsyncResult => async.transform(logTime)
      }
    }
  }
}

next に渡されたアクションをラップするために EssentialAction を新しく作っている点は置いておくとして、ここでポイントとなる差異は next を実行すると Iteratee が得られる点です。お望みであれば、Enumeratee 内においてこれをラップすることで、様々な変換を行うことができます。その後、シンプルなフォームにおいて行ったのと同じ方法で、この iteratee の結果を map し、partial function と共に取り扱います。

異なるふたつのフィルター API が存在するように見えるかもしれませんが、存在するのはただひとつ、EssentialFilter だけです。先の例で登場したシンプルな Filter API は EssentialFilter を継承し、EssentialAction を新しく作ることでこれを実装しています。引数に渡されたコールバックは、リクエストボディを解析して残りのアクションが非同期に実行されている間、Result 用の promise を作り、その結果を AsyncResult として返すことで、リクエストボディの解析をスキップしているように見せています。