我正在寻找Jersey应用程序中的正确方法 - 从传入请求中读取标头并自动将其安装在可能由我的应用程序正在使用的JAX-RS客户端发出的任何传出请求中。 / p>
理想情况下,我想这样做而不会污染任何类的内部逻辑,所以通过各种过滤器和拦截器。
对于简单的用例,我可以执行此操作:我在ClientRequestFilter
上注册了ClientBuilder
实现,并且该过滤器实现具有:
@Context
private HttpHeaders headers;
...这是一个上下文相关的代理(根据定义),因此在其filter
方法中,它可以引用出现所有这些的入站请求中存在的标头,并将其安装在传出请求。对于直截了当的情况,这似乎工作正常。
但是,在异步性的情况下,这会失败:如果我使用JAX-RS异步客户端API来生成一堆GET
,则仍会调用过滤器,但不能再调用该方法{ {1}}实例变量;泽西岛抱怨说,据我们所知,我们不再处于要求范围内。如果将请求范围定义为每个线程,则这是有意义的:生成的headers
在某个Jersey管理的线程池中运行,而不是与GET
代理所在的线程相同的线程已关联,因此当我的过滤器尝试与之对话时,代理会将headers
全部抛出。{/ p>
我觉得IllegalStateException
和ContainerRequestFilter
的某些组合应该能够在异步情况下完成工作,但我没有看到它。
答案 0 :(得分:4)
我要做的是制作一个预先配置了WebTarget
的{{1}}注射剂来添加标头。最好以这种方式配置ClientRequestFilter
,而不是WebTarget
,因为Client
是一个昂贵的对象来创建。
我们可以使用自定义注释和Client
进行WebTarget
注射。在InjectionResolver
中,我们可以获取InjectionResolver
并从中获取标题,我们会将其传递给ContainerRequest
。
这里正在行动
ClientRequestFilter
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WithHeadersTarget {
String baseUri();
String[] headerNames() default {};
}
这个实现的一个方面是它检查private static class WithHeadersTargetInjectionResolver
implements InjectionResolver<WithHeadersTarget> {
private final Provider<ContainerRequest> requestProvider;
private final Client client;
@Inject
public WithHeadersTargetInjectionResolver(Provider<ContainerRequest> requestProvider) {
this.requestProvider = requestProvider;
this.client = ClientBuilder.newClient();
}
@Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (injectee.getRequiredType() == WebTarget.class
&& injectee.getParent().isAnnotationPresent(WithHeadersTarget.class)) {
WithHeadersTarget anno = injectee.getParent().getAnnotation(WithHeadersTarget.class);
String uri = anno.baseUri();
String[] headersNames = anno.headerNames();
MultivaluedMap<String, String> requestHeaders = requestProvider.get().getRequestHeaders();
return client.target(uri)
.register(new HeadersFilter(requestHeaders, headersNames));
}
return null;
}
@Override
public boolean isConstructorParameterIndicator() {
return false;
}
@Override
public boolean isMethodParameterIndicator() {
return false;
}
private class HeadersFilter implements ClientRequestFilter {
private final MultivaluedMap<String, String> headers;
private final String[] headerNames;
private HeadersFilter(MultivaluedMap<String, String> headers, String[] headerNames) {
this.headers = headers;
this.headerNames = headerNames;
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
// if headers names is empty, add all headers
if (this.headerNames.length == 0) {
for (Map.Entry<String, List<String>> entry: this.headers.entrySet()) {
requestContext.getHeaders().put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
// else just add the headers from the annotation
} else {
for (String header: this.headerNames) {
requestContext.getHeaders().put(header, new ArrayList<>(this.headers.get(header)));
}
}
}
}
}
注释中的空headerNames
。如果它是空的,那么我们只转发所有标题。如果用户指定了某些标题名称,那么它只会转发那些
@WithHeadersTarget
new ResourceConfig()
.register(new AbstractBinder() {
@Override
protected void configure() {
bind(WithHeadersTargetInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<WithHeadersTarget>>() {
}).in(Singleton.class);
}
})
在此示例中,如果省略@Path("test")
public static class TestResource {
@WithHeadersTarget(
baseUri = BASE_URI
headerNames = {TEST_HEADER_NAME})
private WebTarget target;
@GET
public String get() {
return target.path("client").request().get(String.class);
}
}
,则默认为空数组,这将导致转发所有请求标头。
headerNames