Jersey 2 过滤器在客户端请求过滤器中使用容器请求上下文

13
我有一个Jersey 2 Web Service,当收到请求时,会向另一个Web服务发送请求,以形成原始请求的响应。所以,当客户端"A"向我的Web服务"B"发出请求时,"B"会作为响应"A"的一部分向"C"发出请求。
A->B->C
我想为Jersey 2 Web服务实现一个过滤器,它的功能基本如下:
客户端"A"将发送一个具有标题"My-Header:first"的请求
当我的Web服务"B"接着做一个客户端请求"C"时,它应该附加到该标头,因此它发送一个带有这个标头的请求 "My-Header:first,second"。
我想将其作为一个过滤器来实现,这样就不需要所有我的资源都复制附加请求标头的逻辑了。
但是,在Jersey 2中,您会得到这4个过滤器:
ContainerRequestFilter - 过滤/修改入站请求 ContainerResponseFilter - 过滤/修改出站响应 ClientRequestFilter - 过滤/修改出站请求 ClientResponseFilter - 过滤/修改入站响应
我需要使用来自入站请求的标头,对其进行修改,然后使用它作为出站请求,因此实际上我需要一个既是ContainerRequestFilter 又是 ClientRequestFilter的东西。我认为在同一个过滤器中实现两者都不起作用,因为您不知道哪个客户端请求映射到哪个容器请求,或者您可否?

“当我的 Web 服务发出客户端请求时” 是什么意思?通常情况下,Web 服务会根据客户端请求创建响应。 - isnot2bad
1
现在让问题更清晰了。 - oggmonster
2个回答

5
我找到了一个不需要使用ThreadLocal在ContainerRequestFilter和ClientRequestFilter之间通信的好方法,因为你不能假定响应容器请求而创建的客户端请求将在同一线程上。
我实现这个方法是通过在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");
}

需要一个在其构造函数中采用ContainerRequestContextClientRequestFilter

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()在哪里?谢谢! - Nelson Monterroso
@NelsonMonterroso 我已经修正了我的答案,现在更加清晰明了。 - oggmonster
根据您想要在ContainerRequestFilter中管理逻辑的方式,您可以完全删除它,因为从注入的ContainerRequestContext中,您可以获取原始请求(包括标头等)。我正在使用这个技巧来实现一个客户端,自动将请求的标头传递到新的HTTP调用。 - Logan Pickup
为每个请求创建一个新的“Client”是昂贵的,因此需要高效使用Jersey Client。如何高效使用Jersey Client - Martynas Jusevičius

3
一个容器过滤器可以在一个类中实现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
        }
    }
}

所以,过滤器的生命周期是按请求计算的? - oggmonster
不,通常每个过滤器和应用程序都有一个实例。但是这里并不重要,因为每个请求都绑定到一个线程上。因此,ThreadLocal变量确保并行请求不会混淆其静态持有者内容。 - isnot2bad
嗯,考虑了一下,我认为这行不通,因为如果我有另一种不需要客户端调用的方法/资源时,ThreadLocal 对象就无法被清除。 - oggmonster
1
不,那不是真的。ThreadLocal 在第二个过滤器方法中被清理,该方法属于 ContainerResponseFilter,即使 B -> C 不发生,它也会在请求 (A -> B) 之后被调用。 - isnot2bad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接