ジャージーフィルターとインターセプター

1前書き

この記事では、フィルターとインターセプターがJerseyフレームワークでどのように機能するのか、およびそれらの主な違いについて説明します。

ここではJersey 2を使用し、Tomcat 9サーバーを使用してアプリケーションをテストします。

2アプリケーション設定

まず、サーバー上に単純なリソースを作成しましょう。

@Path("/greetings")
public class Greetings {

    @GET
    public String getHelloGreeting() {
        return "hello";
    }
}

また、アプリケーションに対応するサーバー設定を作成しましょう。

@ApplicationPath("/** ")
public class ServerConfig extends ResourceConfig {

    public ServerConfig() {
        packages("com.baeldung.jersey.server");
    }
}

JerseyでAPIを作成する方法をもっと深く知りたいのなら、 この記事 をチェックしてください。

また、 クライアント中心の記事 を見て、JerseyでJavaクライアントを作成する方法を学ぶこともできます。

3フィルター

それでは、フィルタから始めましょう。

簡単に言えば、 フィルタはリクエストやレスポンスのプロパティを変更することを可能にします - 例えばHTTPヘッダ。フィルタはサーバー側とクライアント側の両方に適用できます。

リソースが見つかったかどうかにかかわらず、フィルタは常に実行されることに注意してください。

3.1. リクエストサーバフィルタの実装

サーバー側のフィルターから始めて、リクエストフィルターを作成しましょう。

  • ContainerRequestFilter インターフェースを実装し、それをサーバーの Provider として登録することで、それを実現します。

@Provider
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        if (ctx.getLanguage() != null && "EN".equals(ctx.getLanguage()
          .getLanguage())) {

            ctx.abortWith(Response.status(Response.Status.FORBIDDEN)
              .entity("Cannot access")
              .build());
        }
    }
}

この単純なフィルタは、 abortWith() メソッドを呼び出すことによって、要求内の言語 “ EN” を持つ要求を拒否します。

例が示すように、リクエストのコンテキストを受け取るメソッドを1つだけ実装する必要がありました。これは必要に応じて変更できます。

このフィルタはリソースが一致した後に実行されることに注意してください。

リソースマッチングの前にフィルタを実行したい場合は、フィルタに @ PreMatching アノテーションを付けることで事前マッチングフィルタを使用できます。

@Provider
@PreMatching
public class PrematchingRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        if (ctx.getMethod().equals("DELETE")) {
            LOG.info("\"Deleting request");
        }
    }
}

今すぐリソースにアクセスしようとすると、事前一致フィルタが最初に実行されることを確認できます。

2018-02-25 16:07:27,800[http-nio-8080-exec-3]INFO  c.b.j.s.f.PrematchingRequestFilter - prematching filter
2018-02-25 16:07:27,816[http-nio-8080-exec-3]INFO  c.b.j.s.f.RestrictedOperationsRequestFilter - Restricted operations filter

3.2. 応答サーバーフィルタの実装

サーバーサイドにレスポンスフィルタを実装して、レスポンスに新しいヘッダを追加するだけです。

そのためには、** 私たちのフィルタは ContainerResponseFilter インタフェースを実装し、その唯一のメソッドを実装する必要があります。

@Provider
public class ResponseServerFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext,
      ContainerResponseContext responseContext) throws IOException {
        responseContext.getHeaders().add("X-Test", "Filter test");
    }
}

ContainerRequestContext パラメーターは読み取り専用として使用されていることに注意してください - 既に応答が処理されているためです。

2.3. クライアントフィルタの実装

クライアント側でフィルタを使って作業します。これらのフィルタはサーバフィルタと同じように機能し、実装しなければならないインタフェースはサーバ側のものと非常に似ています。

リクエストにプロパティを追加するフィルタを使って実際に動作してみましょう。

@Provider
public class RequestClientFilter implements ClientRequestFilter {

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.setProperty("test", "test client request filter");
    }
}

このフィルタをテストするためのJerseyクライアントも作成しましょう。

public class JerseyClient {

    private static String URI__GREETINGS = "http://localhost:8080/jersey/greetings";

    public static String getHelloGreeting() {
        return createClient().target(URI__GREETINGS)
          .request()
          .get(String.class);
    }

    private static Client createClient() {
        ClientConfig config = new ClientConfig();
        config.register(RequestClientFilter.class);

        return ClientBuilder.newClient(config);
    }
}

登録するには、クライアント設定にフィルタを追加する必要があります。

最後に、クライアントでレスポンス用のフィルタも作成します。

これはサーバーのものと非常に似た方法で働きますが、 ClientResponseFilter インターフェースを実装します。

@Provider
public class ResponseClientFilter implements ClientResponseFilter {

    @Override
    public void filter(ClientRequestContext requestContext,
      ClientResponseContext responseContext) throws IOException {
        responseContext.getHeaders()
          .add("X-Test-Client", "Test response client filter");
    }

}

繰り返しますが、 ClientRequestContext は読み取り専用です。

4インターセプター

インターセプターは、要求と応答に含まれるHTTPメッセージ本文のマーシャリングとアンマーシャリングにより密接に関係しています。それらはサーバー側でもクライアント側でも使用できます。

  • これらはフィルタの後で、メッセージ本文が存在する場合にのみ実行されることに注意してください。

インターセプターには ReaderInterceptor WriterInterceptor の2種類があり、サーバー側とクライアント側の両方で同じです。

次に、サーバー上に別のリソースを作成します。POST経由でアクセスされ、本体内のパラメーターを受け取るため、アクセス時にインターセプターが実行されます。

@POST
@Path("/custom")
public Response getCustomGreeting(String name) {
    return Response.status(Status.OK.getStatusCode())
      .build();
}

この新しいリソースをテストするために、Jerseyクライアントに新しいメソッドも追加します。

public static Response getCustomGreeting() {
    return createClient().target(URI__GREETINGS + "/custom")
      .request()
      .post(Entity.text("custom"));
}

4.1. ReaderInterceptor を実装する

リーダーインターセプターを使用すると、受信ストリームを操作できるため、それらを使用してサーバー側の要求またはクライアント側の応答を変更できます。

インターセプトされたリクエストの本文にカスタムメッセージを書き込むために、サーバー側でインターセプターを作成しましょう。

@Provider
public class RequestServerReaderInterceptor implements ReaderInterceptor {

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context)
      throws IOException, WebApplicationException {
        InputStream is = context.getInputStream();
        String body = new BufferedReader(new InputStreamReader(is)).lines()
          .collect(Collectors.joining("\n"));

        context.setInputStream(new ByteArrayInputStream(
          (body + " message added in server reader interceptor").getBytes()));

        return context.proceed();
    }
}

チェーン内の次のインターセプタを呼び出すには、 proceed() メソッドを呼び出す必要があることに注意してください。すべてのインターセプターが実行されると、適切なメッセージ本文リーダーが呼び出されます。

3.2. WriterInterceptor を実装する

ライターインターセプターは、リーダーインターセプターと非常によく似た方法で機能しますが、アウトバウンドストリームを操作するため、クライアント側の要求またはサーバー側の応答でそれらを使用できます。

クライアント側で、リクエストにメッセージを追加するためのwriterインターセプターを作成しましょう。

@Provider
public class RequestClientWriterInterceptor implements WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
      throws IOException, WebApplicationException {
        context.getOutputStream()
          .write(("Message added in the writer interceptor in the client side").getBytes());

        context.proceed();
    }
}

繰り返しますが、次のインターセプターを呼び出すにはメソッド proceed() を呼び出す必要があります。

すべてのインターセプタが実行されると、適切なメッセージボディライターが呼び出されます。

  • クライアントフィルタを使って以前に行ったように、このインターセプタをクライアント設定に登録する必要があることを忘れないでください。

private static Client createClient() {
    ClientConfig config = new ClientConfig();
    config.register(RequestClientFilter.class);
    config.register(RequestWriterInterceptor.class);

    return ClientBuilder.newClient(config);
}

5実行順序

これまで見てきたことすべてを、クライアントからサーバーへのリクエスト中にフィルターとインターセプターが実行されるタイミングを示す図にまとめましょう。

リンク:/uploads/Jersey2-768x446.png%20768w[]

ご覧のとおり、 フィルタは常に最初に実行され、インターセプタは適切なメッセージボディリーダーまたはライター を呼び出す直前に実行されます。

作成したフィルタとインターセプタを見てみると、それらは次の順序で実行されます。

  1. RequestClientFilter

  2. RequestClientWriterInterceptor

  3. PrematchingRequestFilter

  4. RestrictedOperationsRequestFilter

  5. RequestServerReaderInterceptor

  6. ResponseServerFilter

  7. ResponseClientFilter

さらに、フィルタやインターセプタが複数ある場合は、それらに @ Priority アノテーションを付けて実行順序を正確に指定できます。

優先順位は Integer で指定され、フィルターとインターセプターを要求の昇順と応答の降順で並べ替えます。

RestrictedOperationsRequestFilter に優先順位を付けましょう。

@Provider
@Priority(Priorities.AUTHORIZATION)
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
   //...
}

承認を目的として、事前定義された優先順位が使用されていることに注意してください。

6. ネームバインディング

これまで見てきたフィルターとインターセプターはグローバルと呼ばれています。なぜなら、それらはすべての要求と応答に対して実行されるからです。

しかし、 それらは特定のリソースメソッド に対してのみ実行されるように定義することもできます。

** 6.1. 静的バインディング

ネームバインディングを行う1つの方法は、目的のリソースで使用される特定の注釈を静的に作成することです。このアノテーションは @ NameBinding メタアノテーションを含まなければなりません。

アプリケーションで作成しましょう。

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloBinding {
}

その後、この @ HelloBinding アノテーションを使用していくつかのリソースにアノテーションを付けることができます。

@GET
@HelloBinding
public String getHelloGreeting() {
    return "hello";
}

最後に、私たちのフィルタの1つにこのアノテーションも付けるつもりです。そのため、このフィルタは getHelloGreeting() メソッドにアクセスしているリクエストとレスポンスに対してのみ実行されます。

@Provider
@Priority(Priorities.AUTHORIZATION)
@HelloBinding
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
   //...
}

__RestrictedOperations RequestFilterは、残りのリソースに対してはトリガーされなくなります。

6.2. 動的バインディング

これを行うもう1つの方法は、起動時に設定に読み込まれる動的バインディングを使用することです。

このセクションのために、最初にサーバーに別のリソースを追加しましょう。

@GET
@Path("/hi")
public String getHiGreeting() {
    return "hi";
}

それでは、 DynamicFeature インターフェースを実装して、このリソースのバインディングを作成しましょう。

@Provider
public class HelloDynamicBinding implements DynamicFeature {

    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
        if (Greetings.class.equals(resourceInfo.getResourceClass())
          && resourceInfo.getResourceMethod().getName().contains("HiGreeting")) {
            context.register(ResponseServerFilter.class);
        }
    }
}

この場合、 getHiGreeting() メソッドを以前に作成した ResponseServerFilter に関連付けています。

DynamicFeature を使って設定しているので、このフィルタから @ Provider アノテーションを削除しなければならなかったことを覚えておくことは重要です。

そうしないと、フィルタは2回実行されます。1回はグローバルフィルタとして、もう1回は getHiGreeting() メソッドにバインドされたフィルタとして実行されます。

7. 結論

このチュートリアルでは、Jersey 2でフィルタとインターセプタがどのように機能するのか、そしてそれらをWebアプリケーションでどのように使用できるのかを理解することに焦点を当てました。

いつものように、例の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/jersey[GitHubで利用可能]です。