web-dev-qa-db-ja.com

Jersey 2フィルターは、クライアント要求フィルターでコンテナー要求コンテキストを使用します

リクエストを受信すると、元のリクエストに対する応答を形成するために、別のWebサービスに別のリクエストを行うJersey 2Webサービスがあります。したがって、クライアント「A」が私のWebサービス「B」にリクエストを送信すると、「B」は「A」への応答を形成する一環として「C」にリクエストを送信します。

A-> B-> C

基本的にこれを行うJersey2Webサービスのフィルターを実装したいと思います。

  • クライアント「A」は、「My-Header:first」のようなヘッダーを持つリクエストを送信します

  • 次に、私のWebサービス「B」がクライアント要求「C」を作成すると、そのヘッダーに追加する必要があるため、このヘッダー「My-Header:first、second」を使用して要求を送信します。

これをフィルターとして実装して、すべてのリソースがリクエストヘッダーに追加するロジックを複製する必要がないようにします。

ただし、Jersey 2では、次の4つのフィルターを使用できます。

  • ContainerRequestFilter-インバウンドリクエストをフィルタリング/変更します
  • ContainerResponseFilter-アウトバウンド応答をフィルタリング/変更します
  • ClientRequestFilter-アウトバウンドリクエストをフィルタリング/変更します
  • ClientResponseFilter-インバウンド応答をフィルタリング/変更します

Jersey Filter Diagram

インバウンドリクエストのヘッダーを使用して変更し、アウトバウンドリクエストを使用する必要があるため、基本的にContainerRequestFilterとClientRequestFilterの両方であるものが必要です。どのクライアントリクエストがどのコンテナリクエストにマップされているかわからないので、同じフィルターに両方を実装してもうまくいくとは思いませんか?

11
oggmonster

ThreadLocalを使用せずにContainerRequestFilterClientRequestFilterの間で通信する、これを行うための優れた方法を見つけました。これは、クライアントの要求が応答として行われたとは想定できないためです。コンテナへのリクエストは同じスレッドになります。

これを実現する方法は、ContainerRequestConextContainerRequestFilterオブジェクトにプロパティを設定することです。次に、ContainerRequestContextオブジェクトを(明示的にまたは依存性注入を介して)ClientRequestFilterに渡すことができます。依存性注入を使用する場合(Jersey 2を使用している場合は、おそらくHK2を使用しています)、リソースレベルのロジックを変更することなく、これらすべてを実現できます。

このようなContainerRequestFilterを持っている:

public class RequestIdContainerFilter implements ContainerRequestFilter {

@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
    containerRequestContext.setProperty("property-name", "any-object-you-like");
}

そして、コンストラクターでClientRequestFilterをとるContainerRequestContext

public class RequestIdClientRequestFilter implements ClientRequestFilter {

    private ContainerRequestContext containerRequestContext;

    public RequestIdClientRequestFilter(ContainerRequestContext containerRequestContext) {
        this.containerRequestContext = containerRequestContext;
    }

    @Override
    public void filter(ClientRequestContext clientRequestContext) throws IOException {
        String value = containerRequestContext.getProperty("property-name");
        clientRequestContext.getHeaders().putSingle("MyHeader", value);
    }
}

それなら、これをすべてまとめるだけの場合です。必要なClientまたはWebTargetを作成するには、ファクトリが必要です。

public class MyWebTargetFactory implements Factory<WebTarget> {

    @Context
    private ContainerRequestContext containerRequestContext;

    @Inject
    public MyWebTargetFactory(ContainerRequestContext containerRequestContext) {
        this.containerRequestContext = containerRequestContext;
    }

    @Override
    public WebTarget provide() {
        Client client = ClientBuilder.newClient();
        client.register(new RequestIdClientRequestFilter(containerRequestContext));
        return client.target("path/to/api");
    }

    @Override
    public void dispose(WebTarget target) {

    }
}

次に、フィルターを登録し、ファクトリをメインアプリケーションにバインドしますResourceConfig

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        register(RequestIdContainerFilter.class);
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindFactory(MyWebTargetFactory.class).to(WebTarget.class);
            }
        }
    }
}
5
oggmonster

コンテナフィルターは、ContainerRequestFilterContainerResponseFilterの両方を1つのクラスに実装できます。クライアントフィルターについても同じことが言えます。ClientRequestFilterClientResponseFilterは両方とも1つのフィルター実装で実装できます。

しかし、私の知る限り、混ぜることはできません。代わりに、相互に通信する2つの別個のフィルターを使用できます。 ThreadLocalパターンの使用:

// Container filter that stores the request context in a ThreadLocal variable
public class MyContainerRequestFilter implements ContainerRequestFilter, ContainerResponseFilter {
    public static final ThreadLocal<ContainerRequestContext> requestContextHolder;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        requestContextHolder.set(requestContext);
    }

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        // clean up after request
        requestContextHolder.remove();
    }
}

// Client request filter that uses the info from MyContainerRequestFilter
public class MyClientRequestFilter implements ClientRequestFilter {
    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        ContainerRequestContext containerRequestContext =
            MyContainerRequestFilter.requestContextHolder.get();
        if (containerRequestContext != null) {
            // TODO: use info from containerRequestContext to modify client request
        }
    }
}
3
isnot2bad