§フィルター
Play は、あらゆるリクエストに適用するグローバルフィルター向けの、シンプルなフィルター API を提供しています。
§フィルター vs アクション合成
フィルター API は、すべてのルートに無差別に適用される横断的な関心事を対象としています。フィルターの一般的なユースケースは、例えば以下のようなものです。
- ロギング / メトリクス コレクション
- GZIP エンコーディング
- セキュリティヘッダ
対照的に、アクション合成 は認証や認可、キャッシュなど、特定のルートに対する関心事を対象としています。もし、フィルターをすべてのルートに適用したいのでなければ、代わりにアクション合成の使用を検討してみてください。アクション合成はフィルターよりも遙かに強力です。また、定型的なコードを最小限にするために、ルート毎に独自の定義済みアクション群を構成する、アクションビルダーを作成できることも忘れないでください。
§シンプルなロギングフィルター
以下は、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
に配置するのが、より良いやり方でしょう。
フィルターはルーティングが完了した後に適用されるので、RequestHeader
の tags
マップによってリクエストからルーティング情報にアクセスすることができます。例えば、アクションメソッドに対する実行時間をログに出力したいとします。この場合、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 チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。