【问题标题】:Jersey 2 filter uses Container Request Context in Client Request FilterJersey 2 过滤器在客户端请求过滤器中使用容器请求上下文
【发布时间】:2014-09-19 15:27:54
【问题描述】:

我有一个 Jersey 2 Web 服务,它在收到请求后,向另一个 Web 服务发出另一个请求,以形成对原始请求的响应。因此,当客户端“A”向我的 Web 服务“B”发出请求时,“B”向“C”发出请求,作为对“A”的响应的一部分。

A->B->C

我想为 Jersey 2 Web 服务实现一个过滤器,它基本上是这样做的:

  • 客户端“A”将发送一个请求,其标题如下 “我的标题:第一”

  • 当我的 Web 服务“B”随后发出客户端请求“C”时,它应该 附加到该标头,因此它使用此标头发送请求 “我的标题:第一,第二”。

我想将其实现为过滤器,这样我的所有资源就不必重复附加到请求标头的逻辑。

但是,在 Jersey 2 中,您会获得以下 4 个过滤器:

  • ContainerRequestFilter - 过滤/修改入站请求
  • ContainerResponseFilter - 过滤/修改出站响应
  • ClientRequestFilter - 过滤/修改出站请求
  • ClientResponseFilter - 过滤/修改入站响应

我需要使用入站请求的标头,对其进行修改,然后将其用作出站请求,因此基本上我需要既是 ContainerRequestFilter 又是 ClientRequestFilter 的东西。我不认为在同一个过滤器中实现两者会起作用,因为你不知道哪个客户端请求映射到哪个容器请求,或者你知道吗?

【问题讨论】:

  • “当我的 Web 服务发出客户端请求时”是什么意思?通常,Web 服务会根据客户端请求创建响应。
  • 现在让问题更清楚了

标签: java web-services jersey jersey-2.0 jersey-client


【解决方案1】:

我找到了一种很好的方法来做到这一点,它不使用ThreadLocalContainerRequestFilterClientRequestFilter 之间进行通信,因为您不能假设响应容器请求的客户端请求将是在同一个线程上。

我实现这一点的方法是在ContainerRequestFilter 中的ContainerRequestConext 对象中设置一个属性。然后我可以将ContainerRequestContext 对象(显式或通过依赖注入)传递给我的ClientRequestFilter。如果您使用依赖注入(如果您使用的是 Jersey 2,那么您可能使用的是 HK2),那么所有这些都可以在不修改任何资源级别逻辑的情况下实现。

有一个像这样的ContainerRequestFilter

public class RequestIdContainerFilter implements ContainerRequestFilter {

@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
    containerRequestContext.setProperty("property-name", "any-object-you-like");
}

还有一个 ClientRequestFilter 在其构造函数中采用 ContainerRequestContext

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

那么这只是将这一切捆绑在一起的情况。您将需要一个工厂来创建您需要的任何 ClientWebTarget

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) {

    }
}

然后注册过滤器并将你的工厂绑定到你的主应用ResourceConfig:

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        register(RequestIdContainerFilter.class);
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindFactory(MyWebTargetFactory.class).to(WebTarget.class);
            }
        }
    }
}

【讨论】:

  • 我想这样做。我唯一的问题是 - 你在最后一步调用什么 .register() ?谢谢!
  • @NelsonMonterroso 我已经修正了我的答案,以便现在说清楚
  • 根据您希望如何管理您在ContainerRequestFilter 中执行的逻辑,您可以将其完全删除,因为从注入的ContainerRequestContext 中您可以获得原始请求(包括标头等.)。我正在使用该技巧来实现一个客户端,该客户端自动将请求中的标头传递给新的 HTTP 调用。
  • 为每个请求创建一个新的Client 成本很高:How To Use Jersey Client Efficiently
【解决方案2】:

容器过滤器可以在一个类中同时实现ContainerRequestFilterContainerResponseFilter。客户端过滤器也是如此,ClientRequestFilterClientResponseFilter 都可以在一个过滤器实现中实现。

但据我所知,你不能混音。相反,您可以拥有两个相互通信的独立过滤器,例如使用ThreadLocal 模式:

// 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
        }
    }
}

【讨论】:

  • 所以过滤器的生命周期是每个请求的?
  • 不,通常每个过滤器和应用程序都有一个实例。但这并不重要,因为每个请求都绑定到一个线程。因此 ThreadLocal 变量确保并行请求不会混淆它们的静态持有者内容。
  • 嗯,想想这个我认为这行不通,好像我有另一个不进行客户端调用的方法/资源,ThreadLocal 对象没有被清理。跨度>
  • 不,这不是真的。 ThreadLocal 无论如何都会在属于 ContainerResponseFilter 的第二个过滤方法中被清理,即它会在请求 (A -> B) 之后被调用,无论 B -> C i> 发生与否。
猜你喜欢
  • 1970-01-01
  • 2017-05-23
  • 2014-07-01
  • 2017-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多