Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jersey 2 filter uses Container Request Context in Client Request Filter

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?

like image 723
oggmonster Avatar asked Jul 28 '14 12:07

oggmonster


People also ask

What is container request filter?

An extension interface implemented by container request filters. By default, i.e. if no name binding is applied to the filter implementation class, the filter instance is applied globally, however only after the incoming request has been matched to a particular resource by JAX-RS runtime.

What is container request context?

Container request filter context. A mutable class that provides request-specific information for the filter, such as request URI, message headers, message entity or request-scoped properties. The exposed setters allow modification of the exposed request-specific information.


2 Answers

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);
            }
        }
    }
}
like image 151
oggmonster Avatar answered Oct 27 '22 00:10

oggmonster


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
        }
    }
}
like image 31
isnot2bad Avatar answered Oct 27 '22 00:10

isnot2bad