有没有办法自动将JAX-RS请求中的传入HTTP头传播到传出的JAX-RS请求?

时间:2017-11-29 23:38:07

标签: jersey jax-rs

我正在寻找Jersey应用程序中的正确方法 - 从传入请求中读取标头并自动将其安装在可能由我的应用程序正在使用的JAX-RS客户端发出的任何传出请求中。 / p>

理想情况下,我想这样做而不会污染任何类的内部逻辑,所以通过各种过滤器和拦截器。

对于简单的用例,我可以执行此操作:我在ClientRequestFilter上注册了ClientBuilder实现,并且该过滤器实现具有:

@Context
private HttpHeaders headers;

...这是一个上下文相关的代理(根据定义),因此在其filter方法中,它可以引用出现所有这些的入站请求中存在的标头,并将其安装在传出请求。对于直截了当的情况,这似乎工作正常。

但是,在异步性的情况下,这会失败:如果我使用JAX-RS异步客户端API来生成一堆GET,则仍会调用过滤器,但不能再调用该方法{ {1}}实例变量;泽西岛抱怨说,据我们所知,我们不再处于要求范围内。如果将请求范围定义为每个线程,则这是有意义的:生成的headers在某个Jersey管理的线程池中运行,而不是与GET代理所在的线程相同的线程已关联,因此当我的过滤器尝试与之对话时,代理会将headers全部抛出。{/ p>

我觉得IllegalStateExceptionContainerRequestFilter的某些组合应该能够在异步情况下完成工作,但我没有看到它。

1 个答案:

答案 0 :(得分:4)

我要做的是制作一个预先配置了WebTarget的{​​{1}}注射剂来添加标头。最好以这种方式配置ClientRequestFilter,而不是WebTarget,因为Client是一个昂贵的对象来创建。

我们可以使用自定义注释和Client进行WebTarget注射。在InjectionResolver中,我们可以获取InjectionResolver并从中获取标题,我们会将其传递给ContainerRequest

这里正在行动

创建自定义注释

ClientRequestFilter

使用自定义 ClientRequestFilter

创建 InjectionResolver
@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。如果它是空的,那么我们只转发所有标题。如果用户指定了某些标题名称,那么它只会转发那些

注册 InjectionResolver

@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); } } ,则默认为空数组,这将导致转发所有请求标头。

使用Jersey测试框架完成测试

headerNames