【问题标题】:Is there a way to automatically propagate an incoming HTTP header in a JAX-RS request to an outgoing JAX-RS request?有没有办法自动将 JAX-RS 请求中的传入 HTTP 标头传播到传出 JAX-RS 请求?
【发布时间】:2018-05-13 19:17:59
【问题描述】:

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

理想情况下,我希望在不污染任何类的内部逻辑的情况下做到这一点,因此通过各种过滤器和拦截器。

对于简单的用例,我可以这样做:我在 ClientBuilder 上注册了一个 ClientRequestFilter 实现,并且该过滤器实现具有:

@Context
private HttpHeaders headers;

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

但是,在异步的情况下这会失败:如果我使用 JAX-RS 异步客户端 API 生成一堆 GETs,过滤器仍会被调用,但不能再调用 headers 上的方法实例变量;泽西抱怨说,据它所知,我们不再在请求范围内。如果请求范围被定义为每个线程,这是有道理的:生成的GETs 在某个地方的某个 Jersey 管理的线程池中运行,而不是在与 headers 代理关联的线程池中运行,所以当我的过滤器尝试与之对话时,该代理会在整个地方抛出 IllegalStateExceptions。

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

【问题讨论】:

  • 您找到这个问题的答案了吗?面临完全相同的问题
  • 我做到了。没有一种开箱即用的方法来做到这一点。您需要做的是确保您保留对Client 实例的生产的控制权。为此,您必须确保通过@Inject 使用Client。完成此操作后,您可以使用Features 和Factorys 创建Clients,同时利用当前JAX-RS 应用程序的标头信息。这不是微不足道的;我可能会在博客条目中写下该过程,并会在此处更新。

标签: jersey jax-rs


【解决方案1】:

我要做的是制作一个 WebTarget 可注射,它预先配置了 ClientRequestFilter 以添加标题。最好以这种方式配置WebTarget,而不是Client,因为Client 的创建成本很高。

我们可以使用自定义注解和InjectionResolver 使WebTarget 可注入。在InjectionResolver 中,我们可以获取ContainerRequest 并从中获取标头,我们将其传递给ClientRequestFilter

它在行动

创建自定义注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WithHeadersTarget {
    String baseUri();
    String[] headerNames() default {};
}

使用自定义 ClientRequestFilter 制作 InjectionResolver

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

关于这个实现的一件事是它在@WithHeadersTarget 注释中检查一个空的headerNames。如果它是空的,那么我们只是转发所有的标题。如果用户指定了一些标题名称,那么它只会转发那些

注册InjectionResolver

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被省略了,那么它将默认为一个空数组,这将导致所有的请求头都被转发。

使用 Jersey 测试框架完成测试

import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import static org.assertj.core.api.Assertions.assertThat;

public class ForwardHeadersTest extends JerseyTest {

    private static final String BASE_URI = "http://localhost:8000";
    private static final String TEST_HEADER_NAME = "X-Test-Header";

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface WithHeadersTarget {
        String baseUri();
        String[] headerNames() default {};
    }

    @Path("test")
    public static class TestResource {

        @WithHeadersTarget(
                baseUri = BASE_URI
                )
        private WebTarget target;

        @GET
        public String get() {
            return target.path("client").request().get(String.class);
        }
    }

    @Path("client")
    public static class ClientResource {

        @GET
        public String getReversedHeader(@HeaderParam(TEST_HEADER_NAME) String header) {
            System.out.println(header);
            return new StringBuilder(header).reverse().toString();
        }
    }

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

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig()
                .register(TestResource.class)
                .register(ClientResource.class)
                .register(new AbstractBinder() {
                    @Override
                    protected void configure() {
                        bind(WithHeadersTargetInjectionResolver.class)
                                .to(new TypeLiteral<InjectionResolver<WithHeadersTarget>>() {
                                }).in(Singleton.class);
                    }
                })
                .register(new LoggingFilter(Logger.getAnonymousLogger(), true))
                .register(new ExceptionMapper<Throwable>() {
                    @Override
                    public Response toResponse(Throwable t) {
                        t.printStackTrace();
                        return Response.serverError().entity(t.getMessage()).build();
                    }
                });
    }

    @Override
    public URI getBaseUri() {
        return URI.create(BASE_URI);
    }

    @Test
    public void testIt() {
        final String response = target("test")
                .request()
                .header(TEST_HEADER_NAME, "HelloWorld")
                .get(String.class);

        assertThat(response).isEqualTo("dlroWolleH");
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-09
    • 2016-03-23
    • 1970-01-01
    • 1970-01-01
    • 2012-10-21
    相关资源
    最近更新 更多