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