问题
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
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