Documentation

You are viewing the documentation for the 2.6.x development release. The latest stable release series is 2.4.x.

§フィルター

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

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

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

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

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

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

import play.api.Logger
import play.api.mvc._
import scala.concurrent.Future
import play.api.libs.concurrent.Execution.Implicits.defaultContext

class LoggingFilter extends Filter {

  def apply(nextFilter: RequestHeader => Future[Result])
           (requestHeader: RequestHeader): Future[Result] = {

    val startTime = System.currentTimeMillis

    nextFilter(requestHeader).map { result =>

      val endTime = System.currentTimeMillis
      val requestTime = endTime - startTime

      Logger.info(s"${requestHeader.method} ${requestHeader.uri} " +
        s"took ${requestTime}ms and returned ${result.header.status}")

      result.withHeaders("Request-Time" -> requestTime.toString)
    }
  }
}

ここで何が起こっているのかを見てみましょう。最初に注目すべきは apply メソッドのシグネチャです。これはカリー化された関数です。最初のパラメータ nextFilter はリクエストヘッダを取得して結果を生成する関数で、次のパラメータ requestHeader は受信するリクエストの実際のリクエストヘッダです。

nextFilter パラメータはフィルタチェーン内の次のアクションを表します。フィルタチェーンを呼び出すことで、次のアクションが呼び出されます。ほとんどの場合、未来のある同じ時点でこのフィルタを起動したい場合が多くあるでしょう。なんらかの理由でリクエストをブロックしたい場合は、これを呼び出したくない場合があるかもしれません。

チェーン内で次のフィルタを呼び出す前にタイムスタンプを保持します。次のフィルタを呼び出すと、最終的に使用される Future[Result] が返されます。非同期レスポンスの詳細については、非同期レスポンスの処理 の章を見てください。Result を受け取るクロージャを使って map メソッドを呼び出すことによって、Future 内の Result を操作します。リクエストに要した時間を計算し、ログに記録し、result.withHeaders("Request-Time" -> requestTime.toString) を呼び出してレスポンスヘッダでクライアントに送信します。

§フィルターを使う

もっともシンプルにフィルターを使うには、ルートパッケージで HttpFilters トレイトの実装を用意します。

import javax.inject.Inject
import play.api.http.HttpFilters
import play.filters.gzip.GzipFilter

class Filters @Inject() (
  gzip: GzipFilter,
  log: LoggingFilter
) extends HttpFilters {

  val filters = Seq(gzip, log)
}

異なる環境で異なるフィルターを使いたい場合や、このクラスをルートパッケージに入れたくない場合は、application.conf の中の play.http.filters にクラスの完全修飾クラス名を設定することで、Play がクラスを見つけるべき場所を設定できます。例を示します。

play.http.filters=com.example.MyFilters

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

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

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

import play.api.mvc.{Result, RequestHeader, Filter}
import play.api.{Logger, Routes}
import scala.concurrent.Future
import play.api.libs.concurrent.Execution.Implicits.defaultContext

object LoggingFilter extends Filter {
  def apply(nextFilter: RequestHeader => Future[Result])
           (requestHeader: RequestHeader): Future[Result] = {

    val startTime = System.currentTimeMillis

    nextFilter(requestHeader).map { result =>

      val action = requestHeader.tags(Routes.ROUTE_CONTROLLER) +
        "." + requestHeader.tags(Routes.ROUTE_ACTION_METHOD)
      val endTime = System.currentTimeMillis
      val requestTime = endTime - startTime

      Logger.info(s"${action} took ${requestTime}ms" +
        s" and returned ${result.header.status}")

      result.withHeaders("Request-Time" -> requestTime.toString)
    }
  }
}

ルーティングタグは、Play ルーターの機能です。カスタムルーターを使うか、Global.onRouteRequest でカスタムアクションを返すと、これらのパラメータは利用できないかもしれません。

§より強力なフィルター

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

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

import play.api.Logger
import play.api.mvc._
import play.api.libs.concurrent.Execution.Implicits.defaultContext

class LoggingFilter extends EssentialFilter {
  def apply(nextFilter: EssentialAction) = new EssentialAction {
    def apply(requestHeader: RequestHeader) = {

      val startTime = System.currentTimeMillis

      nextFilter(requestHeader).map { result =>

        val endTime = System.currentTimeMillis
        val requestTime = endTime - startTime

        Logger.info(s"${requestHeader.method} ${requestHeader.uri}" +
          s" took ${requestTime}ms and returned ${result.header.status}")
        result.withHeaders("Request-Time" -> requestTime.toString)

      }
    }
  }
}

next アクションとして渡されたものをラップするために EssentialAction を新しく作成していることは別として、ここでの主な違いは、next アクションを呼び出したときに Iteratee を受け取ることです。これを Enumeratee でラップしていくつかの変換を行うこともできます。その後、iteratee の結果を map して処理します。

2 つの異なるフィルタ API があるように見えるかもしれませんが、ただ 1 つ EssentialFilter があるだけです。以前の例の、より簡単な Filter API は、EssentialFilter を継承し、新しい EssentialAction を作成することでそれを実装します。コールバックに渡された次のアクションは、ボディ解析と残りのアクションが非同期に実行されている間、Result の promise を作ることでボディ解析をスキップするように見せます。

Next: HTTP リクエストの処理


このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。