Jersey 2 filter uses Container Request Context in Client Request Filter
Asked Answered
A

2

13

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?

Acrodont answered 28/7, 2014 at 12:29 Comment(2)
What do you mean by "when my web service then makes a client request"? Usually,a web service creates a response due to a client request.Rupe
Made the question a bit clearer nowAcrodont
A
5

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);
            }
        }
    }
}
Acrodont answered 13/8, 2014 at 12:2 Comment(4)
I would like to do exactly this. The only question I have is - what are you calling .register() on in the last step? Thanks!Bailor
@NelsonMonterroso I've fixed my answer to make it clear nowAcrodont
Depending on how you want to manage the logic you are doing in the ContainerRequestFilter, you can remove it entirely, since from the injected ContainerRequestContext you can get the original request (including headers, etc.). I'm using that trick to implement a client that automatically passes through headers from the request to new HTTP calls.Synclastic
Creating a new Client for each request is expensive though: How To Use Jersey Client EfficientlyBowra
R
3

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
        }
    }
}
Rupe answered 28/7, 2014 at 14:36 Comment(4)
So the lifetime of a filter is per-request?Acrodont
No, usually there is one instance per filter and application. But that doesn't matter here as every request is bound to a thread. So the ThreadLocal variable ensures that parallel requests do not mix up their static holder content.Rupe
Hmm thinking about this I don't think this would work, as if I have another method/resource that doesn't make a client call, the ThreadLocal object doesn't get cleaned up.Acrodont
No, that's not true. ThreadLocal gets cleaned up anyway in the second filter method, which belongs to ContainerResponseFilter, i.e. it will be called AFTER the request (A -> B), no matter if B -> C happens or not.Rupe

© 2022 - 2024 — McMap. All rights reserved.