Unable to override response header using Jersey client filter

I am trying to use the Jersey client API to use a third-party REST service. I plan to use automatic POJO deserialization to transition from JSON responses to Java objects.

Unfortunately, the third-party service returns responses using the "text/javascript" content type. My Jersey client does not understand that this should be considered as a JSON object and cannot deserialize the object.

I wrote a simple Jersey server application to make sure that by changing the content type from "text/javascript" to "application/json" , that deserialization works,

Armed with this information, I decided to use the Jersey filter client to change the response headers. The code comes from a comment by the author of this question . In fact, the question seems to be exactly the same as mine, but the respondent incorrectly answered the question and shows how to change the request headers (not the response headers). The original author was able to use the answer to create his solution, but it seems that his claimed solution does not work.

Filter Code:

 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 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.

Then I tried to copy all the headers into a new collection, but I see no way to set the header map back to the response.

Finally, I thought that maybe I could create a new ClientResponse containing my changed headers. However, the constructor for ClientResponse has this signature:

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

It is trivial to copy the status , headers and entity variables from the original. However, I see no way to get a link to the workers field.

How can I use the Jersey filter client to change the response header from "text/javascript" to "application/json" so that my My POJO deserialization works?

+4
source share
5 answers

I have no answer to your real question, but I think I see how you can get this instance of workers if you want to try to create a new answer in my filter.

The workers object you need is solitary. If you can get an instance of com.sun.jersey.api.client.Client, you can get a work object. In my case, the Jersey client code is in the unit test, which subclasses the JerseyTest. JerseyTest defines a "client ()" method that returns a Client object. I added the following test code (well, not really, but close):

  MessageBodyWorkers workers = client().getMessageBodyWorkers(); 

Then I set a breakpoint in the ClientResponse constructor (this is the original ClientResponse response returned by Jersey. I did not try to clone it because I did not need it for my test). The workers referred to the constructor were the same example. That way, even if you cannot get the object from the response object, you can get it elsewhere.

+3
source

In Jersey 2, register a ClientResponseFilter implementation with ClientConfig to manipulate the HTTP headers of incoming responses.

For example, this is similar to working with Jersey 2.3.1 to control the HTTP header:

 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's filter and interceptor documentation is not perfect, but it does have some javadocs links for the respective classes: https://jersey.java.net/documentation/latest/filters-and-interceptors.html

I get XML responses from a service that responds with XML content but does not have a Content-Type: application / xml header. Probably the best approach would be to register MessageBodyReaders, but the above approach works while I play with this API.

+7
source

Guido's answer provides the insight needed to create a new ClientResponse object and return it. For reasons that I have not bothered to track down by creating a new InboundHeaders , adding all the existing headers to it, and then modifying the single title in question, it still fails with UnsupportedOperationException . Thus, to rewrite 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()); } } 
+3
source

In Jersey 2, you must use ClientResponseFilter . Then you can just call responseContext.getHeaders().putSingle(...) .

In Java 8, you can do this with lambda:

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

If you want to reuse an existing filter instance, simply register it with Client , not ClientConfig .

The old way (Jersey-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):

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

This is not a good solution , but it can help you complete the migration.

0
source

Source: https://habr.com/ru/post/1437917/


All Articles