§フィルター
Play は、あらゆるリクエストに適用するグローバルフィルター向けの、シンプルなフィルター API を提供しています。
§フィルター vs アクション合成
フィルター API は、すべてのルートに無差別に適用される横断的な関心事を対象としています。フィルターの一般的なユースケースは、例えば以下のようなものです。
- Logging/metrics collection
- GZIP encoding
- Security headers
対照的に、アクション合成 は認証や認可、キャッシュなど、特定のルートに対する関心事を対象としています。もし、フィルターをすべてのルートに適用したいのでなければ、代わりにアクション合成の使用を検討してみてください。アクション合成はフィルターよりも遙かに強力です。また、定型的なコードを最小限にするために、ルート毎に独自の定義済みアクション群を構成する、アクションビルダーを作成できることも忘れないでください。
§シンプルなロギングフィルター
以下は、Play framework があるリクエストを処理するためにどれくらい時間が掛かったのか計測してロギングする、シンプルなフィルターです:
import play.api.Logger
import play.api.mvc._
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 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)
}
}
}
Let’s understand what’s happening here. The first thing to notice is the signature of the apply
method. It’s a curried function, with the first parameter, nextFilter
, being a function that takes a request header and produces a result, and the second parameter, requestHeader
, being the actual request header of the incoming request.
The nextFilter
parameter represents the next action in the filter chain. Invoking it will cause the action to be invoked. In most cases you will probably want to invoke this at some point in your future. You may decide to not invoke it if for some reason you want to block the request.
We save a timestamp before invoking the next filter in the chain. Invoking the next filter returns a Future[Result]
that will redeemed eventually. Take a look at the Handling asynchronous results chapter for more details on asynchronous results. We then manipulate the Result
in the Future
by calling the map
method with a closure that takes a Result
. We calculate the time it took for the request, log it and send it back to the client in the response headers by calling result.withHeaders("Request-Time" -> requestTime.toString)
.
§A more concise syntax
You can use a more concise syntax for declaring a filter if you wish:
val loggingFilter = Filter { (nextFilter, requestHeader) =>
val startTime = System.currentTimeMillis
nextFilter(requestHeader).map { result =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms " +
s"and returned ${result.header.status}")
result.withHeaders("Request-Time" -> requestTime.toString)
}
}
これは val なので、なんらかのスコープ内においてのみ使用することができます。
§フィルターを使う
もっともシンプルにフィルターを使うには、Global
オブジェクトで WithFilters
トレイトを継承します:
import play.api.mvc._
import play.filters.gzip.GzipFilter
object Global extends WithFilters(LoggingFilter, new GzipFilter()) {
// ...
}
手動でフィルターを実行することもできます:
import play.api.mvc._
import play.filters.gzip.GzipFilter
import play.api.GlobalSettings
object Global extends GlobalSettings {
override def doFilter(next: EssentialAction): EssentialAction = {
Filters(super.doFilter(next), LoggingFilter, new GzipFilter())
}
}
§フィルターはどこに合う?
フィルターは、アクションがルーターによって見つけられた後に、そのアクションをラップします。これは、フィルターを使ってルーターに影響を与えるパスやメソッド、そしてクエリパラメーターを変換できないことを意味します。フィルターから別のアクションを実行することで、リクエストをそのアクションに移動させてしまうこともできますが、これをするとフィルターチェーンの残りをバイパスしてしまうことに気を付けてください。ルーターが実行される前にリクエストを変更する必要がある場合は、フィルターを使う代わりに、そのロジックを 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)
}
}
}
Routing tags are a feature of the Play router. If you use a custom router, or return a custom action in
Global.onRouteRequest
, these parameters may not be available.
§より強力なフィルター
Play は リクエストボディ全体にアクセスすることのできる、EssentialFilter
と呼ばれるより低レベルなフィルター API を提供しています。この API により、EssentialAction を他のアクションでラップすることができます。
上記のフィルター例を EssentialFilter
として書き直すと、以下のようになります:
import play.api.Logger
import play.api.mvc._
import play.api.libs.concurrent.Execution.Implicits.defaultContext
object 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)
}
}
}
}
The key difference here, apart from creating a new EssentialAction
to wrap the passed in next
action, is when we invoke next, we get back an Iteratee
. You could wrap this in an Enumeratee
to do some transformations if you wished. We then map
the result of the iteratee and thus handle it.
Although it may seem that there are two different filter APIs, there is only one,
EssentialFilter
. The simplerFilter
API in the earlier examples extendsEssentialFilter
, and implements it by creating a newEssentialAction
. The passed in callback makes it appear to skip the body parsing by creating a promise for theResult
, while the body parsing and the rest of the action are executed asynchronously.
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。