[Java] Filters in RESTful Java

原文はこちら。
https://community.oracle.com/docs/DOC-1003506

フィルタ(Filter)はJAX-RSフレームワークが提供する重要な機能の一つであり、RESTful Webサービスを開発する際には様々なコンテキストで利用されます。
フィルタを使ってリクエスト・エンティティやレスポンス・エンティティ、ヘッダ、その他のパラメータを変更します。この記事では、様々な種類のフィルタとその使い方を見ていきます。全てのコードサンプルはJerseyフレームワークをベースにしています。

[注意]
この記事のサンプルコードはGitHubからご利用いただけます。
RESTskol
https://github.com/cloudskol/restskol

Types of Filters

フィルタはサーバ(コンテナ)フィルタ、もう一つはクライアントフィルタという2種類の主要なカテゴリに分類されます。これらの2個のカテゴリはさらにリクエスト・フィルタやレスポンス・フィルタに分類されます(図1)。
f1.jpg
Figure 1. Types of filters

Container Filters

以下ではコンテナ・フィルタと利用可能なアノテーションをご紹介します。

Container Response Filters

レスポンス・ヘッダを変更したり、リソースメソッドを実行した後にパラメータを変更したりしたい場合に、コンテナ・レスポンス・フィルタを利用できます。利用するためには、ContainerResponseFilterインターフェースの実装を提供する必要があります(Listing 1)。
Listing 1. コンテナ・レスポンス・フィルタの例
public class RestSkolResponseFilter implements ContainerResponseFilter {
  @Override
  public void filter(ContainerRequestContext containerRequestContext,
           ContainerResponseContext containerResponseContext) throws IOException {
    containerResponseContext.getHeaders().add("X-Powered-By", "RESTSkol");
  }
}
これをプロバイダとしてリソース構成に登録する必要があります。上記の例では、X-Powered-Byと呼ばれるパラメータをレスポンスに注入しました(このサンプルはJersey Webサイトにインスパイアされました)。このように、レスポンスでよく使われるヘッダ値を簡単に注入することができます。

Container Request Filters

コンテナ・リクエスト・フィルタはリソースメソッドを実行する前に検証を強制する場合に有用です。そのため、コンテナ・リクエスト・フィルタはリソースメソッドの盾として振る舞います。任意の検証を強制することができ、その検証に失敗した場合、その時点でリソース実行を中止することができます。

Listing 2の例では、API Keyの検証をリソースメソッドの呼出しで強制しています。期待されるAPI Keyがこのリクエストヘッダにない場合、これ以後の呼出し実行は中止され、このレイヤでレスポンスを生成します。
Listing 2. Example container request filter
public class APIKeyCheckRequestFilter implements 
  ContainerRequestFilter { private static final String API_KEY = "X-API-KEY";

  @Override
  public void filter(ContainerRequestContext containerRequestContext) throws 
    IOException { final String apiKey = 
    containerRequestContext.getHeaders().getFirst(API_KEY); System.out.println("API KEY: " + apiKey);

    if (apiKey == null || apiKey.isEmpty()) { containerRequestContext
      .abortWith(
        Response
          .status(Response.Status.UNAUTHORIZED)
      .entity("Please provide a valid API Key")
      .build());
    }
  }
}

@PreMatching Annotation

デフォルトでは、適切なリクエスト・実行メソッドが見つかった後にコンテナ・リクエスト・フィルタ実装を実行します。なお、必要とされるリクエストメソッドを発見したり選択したりすることを制御することはできません。
この挙動を実現するため、コンテナ・リクエスト・フィルタのための @PreMatching アノテーションがあります。@PreMatching を使ってコンテナ・リクエスト・フィルタに注釈を付けた場合、リクエストメソッドに影響を及ぼすことができます。Listing 3の実装例では、  /books/v1 へのリクエストを the /books/v2 リソースに委任しています。これはリソース実装を廃止したいときに有用かもしれません。
Listing 3. Example of using the @PreMatching annotation
@PreMatching
public class PreMatchingFilter implements ContainerRequestFilter {
  @Override
  public void filter(ContainerRequestContext containerRequestContext) throws 
    IOException { final URI absolutePath = 
    containerRequestContext.getUriInfo().getAbsolutePath(); String path = 
    absolutePath.getPath();
    System.out.println("Path: " + path);

    if (path.contains("/books/v1")) {
      path = path.replace("/books/v1", "/books/v2");
    }

    System.out.println("After replaced Path: " + path);
    try {
      containerRequestContext.setRequestUri(new URI(path));
    } catch (URISyntaxException e) { 
        e.printStackTrace();
    }
  }
}

Client Filters

クライアント・フィルタではクライアント・リクエスト・フィルタとクライアント・レスポンス・フィルタの2種類が用意されています。これらのフィルタはクライアントAPI層にのみ適用します。つまり、Jersey(JAX-RS)クライアントAPIを使ってRESTエンドポイントを呼び出す場合、これらのフィルタをClient オブジェクトの一部として追加することができます。

Client Request Filters

このフィルタはフィルタのワークフロー開始点です。クライアント・リクエスト・フィルタがClientオブジェクトに登録されている場合、リクエストがサーバに送信される前にワークフローを開始します。そのため、クライアント層自身で何らかの検証を実施したい場合に、このフィルタを使うことができます。
Listing 4に示すクライアント・リクエスト・フィルタの目的は、PATCHメソッドを呼び出すときに検証する、というものです。検証を実施してサポートされないメソッドを使えないようにすることができます。この呼出しで検証に失敗した場合、リクエストを中止することができ、ワークフローが終了します。検証が成功した場合はサーバーレベルにリクエストを渡します。
Listing 4. Example client request filter
public class RestSkolClientRequestFilter implements ClientRequestFilter {
  private static final Logger logger = 
  LogManager.getLogger(RestSkolClientRequestFilter.class);

  @Override
  public void filter(ClientRequestContext clientRequestContext) throws IOException { 
    logger.info("Client request called");
    final String methodName = clientRequestContext.getMethod(); if 
    (methodName.equals("PATCH")) {
    clientRequestContext.abortWith( 
        Response.status(Response.Status.METHOD_NOT_ALLOWED)
          .entity("HTTP Patch is NOT supported")
          .build());
    }
  }
}

Client Response Filters

クライアント・レスポンス・フィルタはワークフローの最終ステップです。実行がサーバレベルで完了した後、サーバ層が成功のレスポンスもしくは失敗のレスポンスを生成し、クライアント層にこのレスポンスを渡します。クライアント・レスポンス・フィルタはレスポンスをサーバから受け取った際に実行されるもので、クライアント・レスポンス・フィルタのレベルで検証したり、レスポンスにさらにエンティティデータを注入したりすることができます。その後レスポンスを呼出し元に渡します。

How to Use Client Filters


既に述べたように、クライアント・フィルタ(リクエスト、レスポンスとも)をListing 5に示すようにClientオブジェクトに登録する必要があります。
Listing 5. Example of registering client filters
final Client client = ClientBuilder.newBuilder()
    .register(RestSkolClientRequestFilter.class)
    .register(RestSkolClientResponseFilter.class)
    .build();
その後、これらのクライアント・フィルタをメソッド呼び出し時に実行します。

Filter Execution Workflow and Output


図2はフィルタ実行パスのワークフロー(ステップ)全体を示しています。
f2.png
Figure 2. Steps in the filter execution path

以下の出力は、上記の全てのフィルタ実装が存在する場合の実行順序を示しています。ソースコードを書くにし、プロジェクトをローカルで実行して以下の出力を確認してください。図2で示した順序に出力が一致するはずです。
[INFO ] 2016-04-12 23:29:38.212 [main] RestSkolClientRequestFilter - Client request called
[INFO ] 2016-04-12 23:29:38.509 [http-bio-8080-exec-1] PreMatchingFilter - Pre matching container request filter
[INFO ] 2016-04-12 23:29:38.519 [http-bio-8080-exec-1] APIKeyCheckRequestFilter - Container request filter
[INFO ] 2016-04-12 23:29:38.530 [http-bio-8080-exec-1] RestSkolResponseFilter - Container response filter
[INFO ] 2016-04-12 23:29:38.935 [main] RestSkolClientResponseFilter - Client response filter

Conclusion

サンプルのソースコードは以下にあります。
RESTskol
https://github.com/cloudskol/restskol
詳細を知りたい方はご連絡ください(訳注:当然ながら著者にお願いします)。できる限りお答えしたいと考えています。

See Also

フィルタに関する詳細情報は、Jerseyのドキュメントをご覧ください。
Filters and Interceptors
https://jersey.java.net/documentation/latest/filters-and-interceptors.html

About the Author

ThamizharasuはインドのChennaiにすむJava開発者で、特にプログラミング、Java EEに夢中です。また、ThamizharasuはJSRアクティビティ(JSONB)に参加し、現在Madras Java User Groupのリーダーを務めています。自身のブログやTwitterでプログラミングやフレームワークを啓蒙しています。
Cloudskol - Digital School for Learning
http://cloudskol.com/

0 件のコメント:

コメントを投稿