Java 无法使用Jersey客户端筛选器覆盖响应标头

Java 无法使用Jersey客户端筛选器覆盖响应标头,java,jersey,Java,Jersey,我正在尝试使用Jersey客户端API来使用第三方REST服务。我计划使用自动POJO反序列化从JSON响应转到Java对象 不幸的是,第三方服务使用内容类型“text/javascript”返回响应。我的Jersey客户端无法理解这应该被视为JSON对象,并且无法反序列化该对象 我编写了一个简单的Jersey服务器应用程序,通过将内容类型从“text/javascript”更改为“application/json”来验证反序列化是否有效 有了这些信息,我准备使用Jersey客户端过滤器来修改响

我正在尝试使用Jersey客户端API来使用第三方REST服务。我计划使用自动POJO反序列化从JSON响应转到Java对象

不幸的是,第三方服务使用内容类型
“text/javascript”
返回响应。我的Jersey客户端无法理解这应该被视为JSON对象,并且无法反序列化该对象

我编写了一个简单的Jersey服务器应用程序,通过将内容类型从
“text/javascript”
更改为
“application/json”
来验证反序列化是否有效

有了这些信息,我准备使用Jersey客户端过滤器来修改响应头。该代码来自的作者的评论。事实上,这个问题似乎与我的问题完全相同——但是回答者错误地回答了这个问题,并展示了如何修改请求头(而不是响应头)。最初的作者能够使用这个答案来创建他的解决方案,但是,他所陈述的解决方案似乎不起作用

过滤代码为:

client.addFilter(new ClientFilter() {
  @Override public ClientResponse handle(ClientRequest cr) 
      throws ClientHandlerException {
    ClientResponse response = getNext().handle(cr); 
    response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); 
    return response;
  }
});
但是,当执行时,会引发一个
不支持操作异常

Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableCollection.clear(Collections.java:1035)
at com.sun.jersey.core.util.StringKeyIgnoreCaseMultivaluedMap.putSingle(StringKeyIgnoreCaseMultivaluedMap.java:78)
at com.sun.jersey.core.util.StringKeyIgnoreCaseMultivaluedMap.putSingle(StringKeyIgnoreCaseMultivaluedMap.java:56)
at App$1.handle(App.java:49)
at com.sun.jersey.api.client.Client.handle(Client.java:648)
at com.sun.jersey.api.client.WebResource.handle(WebResource.java:680)
at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:507)
at App.main(App.java:63)
返回的标题似乎包装在不可修改的集合中

然后,我尝试将所有标题复制到一个新集合中,但无法将标题映射设置回响应中

最后,我想也许我可以创建一个新的
ClientResponse
,其中包含我修改过的标题。但是,
ClientResponse
的构造函数具有以下签名:

public ClientResponse(int status, 
                      InBoundHeaders headers, 
                      InputStream entity, 
                      MessageBodyWorkers workers)  
从原始文件中复制
状态
标题
实体
变量非常简单。但是,我看不到任何方法可以获得对
workers
字段的引用


我如何使用Jersey客户端筛选器将响应头从
“text/javascript”
修改为
“application/json”
,以便我的POJO反序列化工作?

我没有回答您真正的问题,但是我想我看到了如果你想在你的过滤器中创建一个新的响应,你可以得到workers实例

您需要的“workers”对象似乎是单例对象。如果可以获得com.sun.jersey.api.client.client实例,则可以检索workers对象。在我的例子中,Jersey客户端代码位于一个单元测试中,它是JerseyTest的子类。JerseyTest定义了一个方法“client()”,该方法返回客户机对象。我添加了以下测试代码(不完全正确,但很接近):

然后我在ClientResponse的构造函数中设置了一个断点(这是Jersey返回的原始ClientResponse。我没有尝试克隆它,因为我的测试不需要克隆它)。传递给构造函数的worker是同一个实例。因此,即使无法从响应对象获取workers对象,也应该可以从其他位置获取它。

提供了创建新的
ClientResponse
对象并返回它所需的细节。由于一些我还没有找到的原因,创建一个新的
InboundHeaders
,将所有现有的头添加到其中,然后修改有问题的单个头仍然会失败,出现
不支持操作异常
。因此,为了重新写入头,我们迭代原始头并以迭代方式构建正确的集:

final Client client = Client.create(clientConfig);
client.addFilter(new ClientFilter()
{
  @Override
  public ClientResponse handle(ClientRequest cr) throws ClientHandlerException
  {
    final ClientResponse response = getNext().handle(cr); 
    final InBoundHeaders headers = new InBoundHeaders();

    for (String header : response.getHeaders().keySet())
    {
      if (header.equals(HttpHeaders.CONTENT_TYPE))
      {
        headers.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); 
      }
      else
      {
        headers.put(header, headers.get(header));
      }
    }

    return new ClientResponse(response.getStatus(),
                              headers,
                              response.getEntityInputStream(),
                              client.getMessageBodyWorkers());
  }
}

在Jersey 2中,向ClientConfig注册一个实现,以便操作传入响应的HTTP头

例如,在Jersey 2.3.1中,这似乎可以很好地处理HTTP头:

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.client.ClientRequestContext;

import org.glassfish.jersey.client.ClientConfig;

/* Ensure that there is an "application/xml" Content-Type header on
 * successful responses without a content type header. */
@Provider
public static class EnsureXmlContentTypeFilter implements ClientResponseFilter {
    @Override
    public void filter(ClientRequestContext requestContext,
                       ClientResponseContext responseContext) {
        if (200 == responseContext.getStatus() &&
            null == responseContext.getHeaderString(HttpHeaders.CONTENT_TYPE)) {

            responseContext.getHeaders().add(
                HttpHeaders.CONTENT_TYPE, "application/xml"
            );

        }
    }
}

private final ClientConfig config = new ClientConfig()
    // Registering this filter adds a "Content-Type: application/xml" 
    // header to each response that lacks Content-Type headers.
    .register(EnsureXmlContentTypeFilter.class)
;
private final Client client = ClientBuilder.newClient(config);
关于过滤器和拦截器的Jersey文档并不完美,但它确实有一些指向相关类的javadocs的链接:


我从一个服务获取XML响应,该服务使用XML内容进行响应,但缺少“content-Type:application/XML”头。可能更好的方法是注册MessageBodyReaders,但在我使用该服务的API时,上述方法会起作用。

如果您想重复使用现有的筛选器实例,只需在
客户端
而不是
客户端配置
上注册即可

:

新泽西州(泽西-2.3):


这不是问题所在,但它可能会帮助您进行迁移。

在Jersey 2中,您应该使用
ClientResponseFilter
。然后您可以调用
responseContext.getHeaders().putSingle(…)

在Java 8下,您可以使用lambda执行此操作:

client.register((ClientResponseFilter) (requestContext, responseContext) ->
                responseContext.getHeaders().putSingle("Content-Type", "application/json"));

这是一个可行的解决方法:。它并没有解决标题问题,但它只允许POJO在一个额外的步骤中反序列化。这似乎完成了跟踪。更多详细信息包含在中。如何在jersey 2.0中做到这一点?@agksmehx:这在
1.14
中起作用。我不知道在
2.0
中发生了什么变化。如果你发现了,请发布一个答案,如果你问了一个新的问题,请链接到新的问题。jersey 2.0中有一个实体拦截器或消息正文拦截器可以完成这个任务-过滤器不起作用。我没有确切的代码,因此无法发布更正式的答案。我刚刚在Jersey 1.1x中使用了上述方法,将传入内容类型为text/html的服务器的响应更改为application/json。非常感谢,@GregKopff。有几件事我必须更改:1)if(header.equalsIgnoreCase(…)和2)else分支是headers.put(header,response.getHeaders().get(header)[否则它们都会以null结尾]。
     import com.sun.jersey.api.client.Client;
     import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;

     Client client = new Client();
     client.addFilter(new HTTPBasicAuthFilter(username, password));
     import javax.ws.rs.client.Client;
     import javax.ws.rs.client.ClientBuilder;
     import org.glassfish.jersey.client.filter.HttpBasicAuthFilter;

     Client client = ClientBuilder.newClient();
     client.register(new HttpBasicAuthFilter(username, password));
client.register((ClientResponseFilter) (requestContext, responseContext) ->
                responseContext.getHeaders().putSingle("Content-Type", "application/json"));