ドキュメント

§フィルター

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

§フィルターとアクションコンポジションの比較

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

対照的に、アクションコンポジションは、認証、承認、キャッシュなど、ルート固有の懸念事項を対象としています。フィルターをすべてのルートに適用したくない場合は、代わりにアクションコンポジションを使用することを検討してください。はるかに強力です。また、独自に定義したアクションセットを各ルートに構成する独自のアクションビルダーを作成して、定型コードを最小限に抑えることができることも忘れないでください。

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

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

import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import javax.inject.Inject;
import org.apache.pekko.stream.Materializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.mvc.*;

public class LoggingFilter extends Filter {

  private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);

  @Inject
  public LoggingFilter(Materializer mat) {
    super(mat);
  }

  @Override
  public CompletionStage<Result> apply(
      Function<Http.RequestHeader, CompletionStage<Result>> nextFilter,
      Http.RequestHeader requestHeader) {
    long startTime = System.currentTimeMillis();
    return nextFilter
        .apply(requestHeader)
        .thenApply(
            result -> {
              long endTime = System.currentTimeMillis();
              long requestTime = endTime - startTime;

              log.info(
                  "{} {} took {}ms and returned {}",
                  requestHeader.method(),
                  requestHeader.uri(),
                  requestTime,
                  result.status());

              return result.withHeader("Request-Time", "" + requestTime);
            });
  }
}

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

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

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

§フィルターの使用

フィルターを使用する最も簡単な方法は、ルートパッケージで`Filters`と呼ばれる`HttpFilters`インターフェースの実装を提供することです。通常は、`DefaultHttpFilters`クラスを拡張し、可変長引数コンストラクターにフィルターを渡す必要があります。

import javax.inject.Inject;
import play.filters.gzip.GzipFilter;
import play.http.DefaultHttpFilters;

public class Filters extends DefaultHttpFilters {
  @Inject
  public Filters(GzipFilter gzip, LoggingFilter logging) {
    super(gzip, logging);
  }
}

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

play.http.filters=com.example.Filters

§フィルターはどこに適合しますか?

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

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

import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import javax.inject.Inject;
import org.apache.pekko.stream.Materializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.api.routing.HandlerDef;
import play.mvc.*;
import play.routing.Router;

public class RoutedLoggingFilter extends Filter {

  private static final Logger log = LoggerFactory.getLogger(RoutedLoggingFilter.class);

  @Inject
  public RoutedLoggingFilter(Materializer mat) {
    super(mat);
  }

  @Override
  public CompletionStage<Result> apply(
      Function<Http.RequestHeader, CompletionStage<Result>> nextFilter,
      Http.RequestHeader requestHeader) {
    long startTime = System.currentTimeMillis();
    return nextFilter
        .apply(requestHeader)
        .thenApply(
            result -> {
              HandlerDef handlerDef = requestHeader.attrs().get(Router.Attrs.HANDLER_DEF);
              String actionMethod = handlerDef.controller() + "." + handlerDef.method();
              long endTime = System.currentTimeMillis();
              long requestTime = endTime - startTime;

              log.info("{} took {}ms and returned {}", actionMethod, requestTime, result.status());

              return result.withHeader("Request-Time", "" + requestTime);
            });
  }
}

**注:** ルーティング属性はPlayルーターの機能です。カスタムルーターを使用する場合、これらのパラメーターは使用できない場合があります。

§より強力なフィルター

Playは、リクエストの本文に完全にアクセスできる`EssentialFilter`と呼ばれる低レベルのフィルターAPIを提供します。このAPIを使用すると、EssentialActionを別のアクションでラップできます。

上記のフィルターの例を`EssentialFilter`として書き直したものがこちらです。

import java.util.concurrent.Executor;
import javax.inject.Inject;
import org.apache.pekko.util.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.libs.streams.Accumulator;
import play.mvc.*;

public class EssentialLoggingFilter extends EssentialFilter {

  private static final Logger log = LoggerFactory.getLogger(EssentialLoggingFilter.class);

  private final Executor executor;

  @Inject
  public EssentialLoggingFilter(Executor executor) {
    super();
    this.executor = executor;
  }

  @Override
  public EssentialAction apply(EssentialAction next) {
    return EssentialAction.of(
        request -> {
          long startTime = System.currentTimeMillis();
          Accumulator<ByteString, Result> accumulator = next.apply(request);
          return accumulator.map(
              result -> {
                long endTime = System.currentTimeMillis();
                long requestTime = endTime - startTime;

                log.info(
                    "{} {} took {}ms and returned {}",
                    request.method(),
                    request.uri(),
                    requestTime,
                    result.status());

                return result.withHeader("Request-Time", "" + requestTime);
              },
              executor);
        });
  }
}

渡された`next`アクションをラップする新しい`EssentialAction`を作成することとは別に、ここでの主な違いは、`next`を呼び出すと、`Accumulator`が返されることです。必要に応じて、`through`メソッドを使用してAkka Streams Flowとこれを構成して、ストリームにいくつかの変換を適用できます。次に、イテレータの結果を`map`して処理します。

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

**次へ:** エラー処理


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