Jersey 2 filter uses Container Request Context in Client Request Filter

喜夏-厌秋 提交于 2020-05-10 02:58:55

问题


I have a Jersey 2 Web Service that upon receiving a request, makes another request to another web service in order to form the response for the original request. So, when client "A" makes a request to my web service "B", "B" makes a request to "C" as part of forming the response to "A".

A->B->C

I want to implement a filter for a Jersey 2 web service that essentially does this:

  • Client "A" will send a request that has a header like "My-Header:first"

  • When my web service "B" then makes a client request "C", it should append to that header, so it sends a request with this header "My-Header:first,second".

I want to implement this as a filter so all of my resources don't have to duplicate the logic of appending to the request header.

However, in Jersey 2, you get these 4 filters:

  • ContainerRequestFilter - Filter/modify inbound requests
  • ContainerResponseFilter - Filter/modify outbound responses
  • ClientRequestFilter - Filter/modify outbound requests
  • ClientResponseFilter - Filter/modify inbound responses

Jersey Filter Diagram

I need to use the header from an inbound request, modify it, then use it an outbound request, so essentially I need something that is both a ContainerRequestFilter and a ClientRequestFilter. I don't think implementing both in the same filter will work, as you don't know which Client Request maps to which Container Request, or do you?


回答1:


I found a nice way to do this that doesn't use ThreadLocal to communicate between the ContainerRequestFilter and the ClientRequestFilter, as you can't assume that client requests made in response to a container request will be on the same thread.

The way I achieved this is by setting a property in the ContainerRequestConext object in the ContainerRequestFilter. I can then pass the ContainerRequestContext object (either explicity or through dependency injection) into my ClientRequestFilter. If you use dependency injection (if you're using Jersey 2 then you are probably using HK2), then all of this can be achieved without modifying any of your resource level logic.

Have a ContainerRequestFilter like this:

public class RequestIdContainerFilter implements ContainerRequestFilter {

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

And a ClientRequestFilter that takes a ContainerRequestContext in its constructor:

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);
    }
}

Then it's just a case of tying this all together. You will need a factory to create any Client or WebTarget that you need:

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) {

    }
}

Then register the filter and bind your factory on your main application ResourceConfig:

public class MyApplication extends ResourceConfig {

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



回答2:


A container filter can implement both, ContainerRequestFilter and ContainerResponseFilter in one single class. The same is true for client filters, ClientRequestFilter and ClientResponseFilter can both be implemented in one single filter implementation.

But you cannot mix as far as I know. Instead, you can have two separate filters that communicate with each other e.g. using ThreadLocal pattern:

// 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
        }
    }
}


来源:https://stackoverflow.com/questions/24995307/jersey-2-filter-uses-container-request-context-in-client-request-filter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!