javajax-rsjersey-2.0jersey-client

Jersey Client Error - Unable to Parse Invalid Content-Type Header (need workaround)


I'm using Jersey Client 2.29 I believe.
When handling a request the server responds with Content-Type = application/ (of course that is a bogus value it is supposed to be application/json). jersey blows up because it can't parse the subtype of the media type:

java.util.concurrent.CompletionException: org.glassfish.jersey.message.internal.HeaderValueException: Unable to parse "Content-Type" header value: "application/"
    at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
    at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.glassfish.jersey.message.internal.HeaderValueException: Unable to parse "Content-Type" header value: "application/"
    at org.glassfish.jersey.message.internal.InboundMessageContext.exception(InboundMessageContext.java:314)
    at org.glassfish.jersey.message.internal.InboundMessageContext.singleHeader(InboundMessageContext.java:309)
    at org.glassfish.jersey.message.internal.InboundMessageContext.getMediaType(InboundMessageContext.java:422)
    at com.test.web.ext.filter.LoggingFilter.filter(LoggingFilter.java:94)
    at org.glassfish.jersey.client.ClientFilteringStages$ResponseFilterStage.apply(ClientFilteringStages.java:109)
    at org.glassfish.jersey.client.ClientFilteringStages$ResponseFilterStage.apply(ClientFilteringStages.java:97)
    at org.glassfish.jersey.process.internal.Stages.process(Stages.java:147)
    at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:259)
    at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$1(JerseyInvocation.java:743)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:205)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:390)
    at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:741)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:432)
    at org.glassfish.jersey.client.JerseyCompletionStageRxInvoker.lambda$method$1(JerseyCompletionStageRxInvoker.java:46)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
    ... 3 more
Caused by: javax.ws.rs.ProcessingException: java.lang.IllegalArgumentException: Error parsing media type 'application/'
    at org.glassfish.jersey.message.internal.InboundMessageContext$5.apply(InboundMessageContext.java:428)
    at org.glassfish.jersey.message.internal.InboundMessageContext$5.apply(InboundMessageContext.java:422)
    at org.glassfish.jersey.message.internal.InboundMessageContext.singleHeader(InboundMessageContext.java:307)
    ... 18 more
Caused by: java.lang.IllegalArgumentException: Error parsing media type 'application/'
    at org.glassfish.jersey.message.internal.MediaTypeProvider.fromString(MediaTypeProvider.java:69)
    at org.glassfish.jersey.message.internal.MediaTypeProvider.fromString(MediaTypeProvider.java:37)
    at javax.ws.rs.core.MediaType.valueOf(MediaType.java:196)
    at org.glassfish.jersey.message.internal.InboundMessageContext$5.apply(InboundMessageContext.java:426)
    ... 20 more
Caused by: java.text.ParseException: End of header.
    at org.glassfish.jersey.message.internal.HttpHeaderReaderImpl.getNextCharacter(HttpHeaderReaderImpl.java:155)
    at org.glassfish.jersey.message.internal.HttpHeaderReaderImpl.next(HttpHeaderReaderImpl.java:116)
    at org.glassfish.jersey.message.internal.HttpHeaderReaderImpl.next(HttpHeaderReaderImpl.java:111)
    at org.glassfish.jersey.message.internal.HttpHeaderReader.nextToken(HttpHeaderReader.java:104)
    at org.glassfish.jersey.message.internal.MediaTypeProvider.valueOf(MediaTypeProvider.java:90)
    at org.glassfish.jersey.message.internal.MediaTypeProvider.fromString(MediaTypeProvider.java:67)
    ... 23 more

github-MediaTypeProvider
I have no control of this 3rd party server and I have to be able to process this request.
Is there anyway around this?
Some property, setting, registering a custom media type provider, anything?

P.S. I have successfully used ByteBuddy to redefine this method at runtime and handle the error with some extra special sauce but that is a major hack... I'd love a better option if it exists.

Client Configuration

final Client client = ClientBuilder.newBuilder()
  .property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.CHUNKED)
  .property(ClientProperties.FOLLOW_REDIRECTS, false)
  .sslContext(tls)
  .hostnameVerifier(new NoOpHostnameVerifier())
  .register(new CookiePersistFilter(NARRATIVE)) // @Priority(HEADER_DECORATOR)
  .register(new LoggingFilter(NARRATIVE))       // @Priority(USER)
  .register(MyFilter.class)                     // @Priority(50000)
  .register(JsonReader.class)
  .register(JsonWriter.class)
  .register(HTMLReader.class)
  .register(MultiPartFeature.class)
  .build();

Filters are executed in this order:

  1. MyFilter
  2. LoggingFilter
  3. CookiePersistFilter

Solution

  • As Paul Samsotha pointed out, I can fix this with a ClientResponseFilter using high priority. Here is one way one could do it.

    import javax.annotation.Priority;
    import javax.ws.rs.client.ClientRequestContext;
    import javax.ws.rs.client.ClientResponseContext;
    import javax.ws.rs.client.ClientResponseFilter;
    import javax.ws.rs.core.HttpHeaders;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.ext.Provider;
    
    import com.selinc.winchester.common.narrative.INarrative;
    import com.selinc.winchester.sentinel.Sentinel;
    import com.selinc.winchester.wrestler.ResponsePriorities;
    
    @Provider
    @Priority(25000) // Note the priority ordering is reverse of ClientRequestFilter
    public final class ResponseHeaderWorkaroundFilter implements ClientResponseFilter
    {
      @Override
      public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext)
      {
        try
        {
          // Attempt to read the Content-Type header.
          responseContext.getMediaType();
        }
        catch (Throwable error)
        {
          // On error, set the Content-Type header to some default value.
          responseContext.getHeaders()
            .putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
        }
      }
    }