ドキュメント

§フィルター

Playは、各リクエストにグローバルフィルターを適用するためのシンプルなフィルターAPIを提供します。

§フィルターとアクション合成の比較

フィルターAPIは、すべてのルートに無差別に適用される横断的な関心事のために設計されています。たとえば、フィルターの一般的なユースケースを以下に示します。

対照的に、アクション合成は、認証と認可、キャッシングなど、ルート固有の関心事のために設計されています。フィルターをすべてのルートに適用したくない場合は、代わりにアクション合成の使用を検討してください。アクション合成の方がはるかに強力です。また、独自のカスタム定義のアクションセットを各ルートに構成する独自のアクションビルダーを作成して、ボイラープレートを最小限に抑えることができることを忘れないでください。

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

以下は、Play Frameworkでリクエストの実行にかかる時間を計測してログに記録するシンプルなフィルターです。これはFilterトレイトを実装しています。

import javax.inject.Inject

import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import org.apache.pekko.stream.Materializer
import play.api.mvc._
import play.api.Logging

class LoggingFilter @Inject() (implicit val mat: Materializer, ec: ExecutionContext) extends Filter with Logging {
  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} took ${requestTime}ms and returned ${result.header.status}"
      )

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

ここで何が起こっているのかを理解しましょう。最初に気づくことは、applyメソッドのシグネチャです。これはカリー化された関数で、最初のパラメータnextFilterはリクエストヘッダーを受け取って結果を生成する関数であり、2番目のパラメータrequestHeaderは着信リクエストの実際のリクエストヘッダーです。

nextFilterパラメータは、フィルターチェーンの次のアクションを表します。これを呼び出すと、アクションが呼び出されます。ほとんどの場合、将来のある時点でこれを呼び出すことになるでしょう。何らかの理由でリクエストをブロックしたい場合は、呼び出さないことを決定する場合があります。

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

§フィルターの使用

フィルターを使用する最も簡単な方法は、ルートパッケージにHttpFiltersトレイトの実装を提供することです。Playのランタイム依存性注入サポート(Guiceなど)を使用している場合は、DefaultHttpFiltersクラスを拡張し、可変長引数のコンストラクタにフィルターを渡すことができます。

import javax.inject.Inject

import play.api.http.DefaultHttpFilters
import play.api.http.EnabledFilters
import play.filters.gzip.GzipFilter

class Filters @Inject() (
    defaultFilters: EnabledFilters,
    gzip: GzipFilter,
    log: LoggingFilter
) extends DefaultHttpFilters(defaultFilters.filters :+ gzip :+ log: _*)

異なる環境で異なるフィルターを使用したい場合、またはこのクラスをルートパッケージに配置したくない場合は、application.confplay.http.filtersをクラスの完全修飾名に設定することで、Playがクラスを検索する場所を設定できます。例えば

play.http.filters=com.example.MyFilters

コンパイル時依存性注入BuiltInComponentsを使用している場合は、httpFilters lazy valをオーバーライドするだけで済みます。


import play.api._ import play.filters.gzip._ import play.filters.HttpFiltersComponents import router.Routes class MyComponents(context: ApplicationLoader.Context) extends BuiltInComponentsFromContext(context) with HttpFiltersComponents with GzipFilterComponents { // implicit executionContext and materializer are defined in BuiltInComponents lazy val loggingFilter: LoggingFilter = new LoggingFilter() // gzipFilter is defined in GzipFilterComponents override lazy val httpFilters = Seq(gzipFilter, loggingFilter) lazy val router: Routes = new Routes( /* ... */ ) }

Playが提供するフィルターはすべて、BuiltInComponentsで動作するトレイトを提供しています。
- GzipFilterComponents
- CSRFComponents
- CORSComponents
- SecurityHeadersComponents
- AllowedHostsComponents

§フィルターはどこに適合するのか?

フィルターは、ルーターによってアクションが検索された後、アクションをラップします。これは、フィルターを使用してパス、メソッド、またはクエリパラメータを変換して、ルーターに影響を与えることができないことを意味します。ただし、フィルターから直接アクションを呼び出すことで、リクエストを別のアクションに転送できます。ただし、これによりフィルターチェーンの残りの部分がバイパスされることに注意してください。ルーターが呼び出される前にリクエストを変更する必要がある場合は、代わりに`HttpRequestHandler`にロジックを配置することをお勧めします。

フィルターはルーティングが完了した後に適用されるため、RequestHeaderattrsマップを介してリクエストからルーティング情報にアクセスできます。たとえば、アクションメソッドに対して時間をログに記録する場合があります。その場合、次のようにフィルターを更新することができます。

import javax.inject.Inject

import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import org.apache.pekko.stream.Materializer
import play.api.mvc.Filter
import play.api.mvc.RequestHeader
import play.api.mvc.Result
import play.api.routing.HandlerDef
import play.api.routing.Router
import play.api.Logging

class LoggingFilter @Inject() (implicit val mat: Materializer, ec: ExecutionContext) extends Filter with Logging {
  def apply(nextFilter: RequestHeader => Future[Result])(requestHeader: RequestHeader): Future[Result] = {
    val startTime = System.currentTimeMillis

    nextFilter(requestHeader).map { result =>
      val handlerDef: HandlerDef = requestHeader.attrs(Router.Attrs.HandlerDef)
      val action                 = handlerDef.controller + "." + handlerDef.method
      val endTime                = System.currentTimeMillis
      val requestTime            = endTime - startTime

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

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

ルーティング属性は、Playルーターの機能です。カスタムルーターを使用する場合、またはカスタムリクエストハンドラーを介してカスタムアクションを返す場合、これらのパラメーターは使用できない場合があります。

§より強力なフィルター

Playは、リクエストの本文へのフルアクセスを可能にする、より低レベルのフィルターAPIであるEssentialFilterを提供します。このAPIを使用すると、EssentialActionを別のアクションでラップできます。

上記フィルターの例をEssentialFilterとして書き直したものを次に示します。

import javax.inject.Inject

import scala.concurrent.ExecutionContext

import org.apache.pekko.util.ByteString
import play.api.libs.streams.Accumulator
import play.api.mvc._
import play.api.Logging

class LoggingFilter @Inject() (implicit ec: ExecutionContext) extends EssentialFilter with Logging {
  def apply(nextFilter: EssentialAction): EssentialAction = new EssentialAction {
    def apply(requestHeader: RequestHeader) = {
      val startTime = System.currentTimeMillis

      val accumulator: Accumulator[ByteString, Result] = nextFilter(requestHeader)

      accumulator.map { result =>
        val endTime     = System.currentTimeMillis
        val requestTime = endTime - startTime

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

ここで重要な違いは、渡されたnextアクションをラップする新しいEssentialActionを作成することとは別に、nextを呼び出すと、Accumulatorが返されることです。

必要に応じて、AccumulatorをPekko Streams Flowとthroughメソッドを使用して構成し、ストリームへの変換を追加できます。その後、イテレータの結果をmapし、処理します。

class AccumulatorFlowFilter @Inject() (actorSystem: ActorSystem)(implicit ec: ExecutionContext)
    extends EssentialFilter {
  private val logger = org.slf4j.LoggerFactory.getLogger("application.AccumulatorFlowFilter")

  private implicit val logging: LoggingAdapter = Logging(actorSystem.eventStream, logger.getName)

  override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
    override def apply(request: RequestHeader): Accumulator[ByteString, Result] = {
      val accumulator: Accumulator[ByteString, Result] = next(request)

      val flow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString].log("byteflow")
      val accumulatorWithResult = accumulator.through(flow).map { result =>
        logger.info(s"The flow has completed and the result is $result")
        result
      }

      accumulatorWithResult
    }
  }
}

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

次へ: アプリケーションのテスト


このドキュメントにエラーを見つけましたか?このページのソースコードはこちらにあります。ドキュメントのガイドラインを読んだ後、プルリクエストを自由に投稿してください。質問や共有するアドバイスがありますか?コミュニティフォーラムにアクセスして、コミュニティとの会話を始めましょう。