简体   繁体   English

无法使用Jersey客户端过滤器覆盖响应标头

[英]Unable to override a response header using a Jersey client filter

I am trying to use the Jersey client API to consume a third-party REST service. 我正在尝试使用Jersey客户端API来使用第三方REST服务。 I plan to use the automatic POJO deserialisation to go from JSON responses to Java objects. 我计划使用自动POJO反序列化从JSON响应转到Java对象。

Unfortunately, the third party service returns the responses using the content type "text/javascript" . 不幸的是,第三方服务使用内容类型"text/javascript"返回响应。 My Jersey client fails to understand that this should be considered as a JSON object and fails to deserialise the object. 我的Jersey客户端无法理解这应该被视为JSON对象并且无法反序列化该对象。

I wrote a simple Jersey server application to verify that by changing the content type from "text/javascript" to "application/json" that the deserialisation works. 我写了一个简单的Jersey服务器应用程序,通过将内容类型从"text/javascript"更改为"application/json"来验证反序列化的工作原理。

Armed with this information, I set about to use a Jersey client filter to modify the response headers. 有了这些信息,我开始使用Jersey客户端过滤器来修改响应头。 The code comes from a comment by the author of this question . 该代码来自该问题的作者的评论。 In fact, the question appears to be exactly the same as mine - however the answerer mis-answered the question and shows how to modify the request headers (rather than the response headers). 事实上,问题似乎与我的完全相同 - 但是回答者错误地回答了问题,并展示了如何修改请求标头(而不是响应标头)。 The original author was able to use the answer to create his solution, but, it seems his stated solution fails to work. 原作者能够使用答案来创建他的解决方案,但是,似乎他所声明的解决方案无法工作。

The filter code is: 过滤器代码是:

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

When executed however, an UnsupportedOperationException is raised: 但是,执行时会引发UnsupportedOperationException

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)

The returned headers appear to be wrapped in an unmodifiable collection. 返回的标题似乎包含在不可修改的集合中。

I then attempted to copy all of the headers to a new collection, but there is no way that I can see to set a map of headers back into the response. 然后我尝试将所有标头复制到新集合中,但是我无法看到将标头映射设置回响应。

Finally, I thought perhaps I can create a new ClientResponse containing my amended headers. 最后,我想也许我可以创建一个包含我修改过的标题的新ClientResponse However, the constructor for ClientResponse has this signature: 但是, ClientResponse的构造函数具有以下签名:

public ClientResponse(int status, 
                      InBoundHeaders headers, 
                      InputStream entity, 
                      MessageBodyWorkers workers)  

It is trivial to copy the status , headers and entity variables from the original. 从原始headers复制statusheadersentity变量是微不足道的。 However, I can see no way of getting a reference to the workers field. 但是,我看不到获得对workers字段的引用的方法。

How can I use a Jersey client filter to modify the response header from "text/javascript" to "application/json" so that my POJO deserialisation will work? 如何使用Jersey客户端过滤器将响应头从"text/javascript""application/json"以便我的POJO反序列化可以工作?

In Jersey 2, register an implementation of a ClientResponseFilter with the ClientConfig in order to manipulate the HTTP headers of incoming responses. 在Jersey 2中,使用ClientConfig注册ClientResponseFilter的实现,以便操纵传入响应的HTTP头。

For example, this seems to work well with Jersey 2.3.1 for manipulating HTTP header: 例如,这似乎与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);

The Jersey documentation on Filters and Interceptors isn't perfect, but it does have some links to the javadocs for the relevant classes: https://jersey.java.net/documentation/latest/filters-and-interceptors.html 关于过滤器和拦截器的Jersey文档并不完美,但它确实有一些链接到相关类的javadoc: https//jersey.java.net/documentation/latest/filters-and-interceptors.html

I am getting XML responses from a service which responds with XML content, but lacks a "Content-Type: application/xml" header. 我从一个响应XML内容的服务获得XML响应,但缺少“Content-Type:application / xml”标头。 Probably a better approach would be to register MessageBodyReaders, but the above approach works while I'm playing around with that service's API. 可能更好的方法是注册MessageBodyReaders,但上面的方法在我使用该服务的API时有效。

I don't have an answer to your real question, but I think I see how you can get that workers instance if you want to try to create a new response in your filter. 我没有回答你的真实问题,但我想如果你想尝试在你的过滤器中创建一个新的响应,我会看到你如何获得这个工人实例。

The "workers" object that you need appears to be a singleton. 您需要的“工人”对象似乎是单身人士。 If you can get hold of your com.sun.jersey.api.client.Client instance, you can retrieve the workers object. 如果您可以获取com.sun.jersey.api.client.Client实例,则可以检索workers对象。 In my case, the Jersey client code is in a unit test which subclassed JerseyTest. 在我的例子中,Jersey客户端代码在一个单元测试中,其子类是JerseyTest。 JerseyTest defines a method "client()" which returns the Client object. JerseyTest定义了一个返回Client对象的方法“client()”。 I added the following test code (well not exactly but close): 我添加了以下测试代码(不完全但非常接近):

  MessageBodyWorkers workers = client().getMessageBodyWorkers();

Then I set a breakpoint in the constructor of ClientResponse (this is the original ClientResponse returned by Jersey. I have not attempted to clone it because I don't need to for my test). 然后我在ClientResponse的构造函数中设置了一个断点(这是Jersey返回的原始ClientResponse。我没有尝试克隆它,因为我不需要进行测试)。 The workers passed to the constructor was the same instance. 传递给构造函数的worker是同一个实例。 So, even though you can not get the workers object from the response object, you should be able to get it elsewhere. 因此,即使您无法从响应对象获取workers对象,您也应该能够在其他地方获取它。

Guido's answer provides the insight required to create a new ClientResponse object and return it instead. Guido的答案提供了创建新ClientResponse对象并返回它所需的洞察力。 For reasons that I've not yet bothered to track down, creating a new InboundHeaders , adding all the existing headers to it, and then modifying the single header in question still fails with an UnsupportedOperationException . 由于我还没有打算追查的原因,创建新的InboundHeadersInboundHeaders添加所有现有标头,然后修改有问题的单个标头仍然失败并出现UnsupportedOperationException As such, to re-write the headers, we iterate over the original headers and build the correct set iteratively: 因此,为了重新编写标头,我们迭代原始标头并迭代地构建正确的集合:

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

In Jersey 2, you should use a ClientResponseFilter . 在Jersey 2中,您应该使用ClientResponseFilter Then you can just call responseContext.getHeaders().putSingle(...) . 然后你可以调用responseContext.getHeaders().putSingle(...)

Under Java 8 you can do it with a lambda: 在Java 8下,您可以使用lambda:

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

If you want to re-use an existing filter instance , just register it on the Client instead of on the ClientConfig . 如果要重新使用现有过滤器实例 ,只需在Client而不是ClientConfig上注册它。

Old way (Jersey-1.9) : 老方式(泽西-1.9)

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

New way (Jersey-2.3): 新方式(Jersey-2.3):

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

That's not the best solution , but it may help you to migrate. 这不是最好的解决方案 ,但它可以帮助您迁移。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM