Wrapping BodySubscriber<InputStream> in GZIPInputStream leads to hang
Asked Answered
D

2

5

I'm using the new java.net.http classes to handle asynchronous HTTP request+response exchanges, and I'm trying to find a way to have the BodySubscriber handle different encoding types such as gzip.

However, mapping a BodySubsriber<InputStream> so that the underlying stream is wrapped by a GZIPInputStream (when "Content-Encoding: gzip" is found in the response header) leads to a hang. No exceptions, just a total cessation of activity.

The code which maps the BodySubscriber looks like this:

private HttpResponse.BodySubscriber<InputStream> gzippedBodySubscriber(
        HttpResponse.ResponseInfo responseInfo) {
    return HttpResponse.BodySubscribers.mapping(
            HttpResponse.BodySubscribers.ofInputStream(),
            this::decodeGzipStream);
}

private InputStream decodeGzipStream(InputStream gzippedStream) {
    System.out.println("Entered decodeGzipStream method.");
    try {
        InputStream decodedStream = new GZIPInputStream(gzippedStream);
        System.out.println(
                "Created GZIPInputStream to handle response body stream.");
        return decodedStream;
    } catch (IOException ex) {
        System.out.println("IOException occurred while trying to create GZIPInputStream.");
        throw new UncheckedIOException(ex);
    }
}

Receiving an HTTP response which has "gzip" encoding leads to the console showing just this:

Entered EncodedBodyHandler.apply method.
Entered decodeGzipStream method.

Nothing more is seen, so the line after the call to the GZIPInputStream constructor is never executed.

Does anyone know why this attempt to wrap the InputStream from a BodySubscriber<InputStream> in a GZIPInputStream is hanging?

Note: the equivalent method for unencoded (raw text) HTTP response bodies contains simply a call to BodySubscribers.ofInputStream() with no mapping, and this allows the response to be received and displayed without problem.

Discretional answered 19/11, 2018 at 16:39 Comment(0)
S
5

EDIT: JDK-8217264 is fixed since JDK13


This is indeed a bug. I have logged JDK-8217264. I can suggest two work-arounds:

Workaround one

Do not use BodySubscribers.mapping - but transform the InputStream into a GZIPInputStream after getting the HttpResponse's body:

GZIPInputStream gzin = new GZIPInputStream(resp.getBody());

Workaround two

Have the mapping function return a Supplier<InputStream> instead, taking care not to create the GZIPInputStream until Supplier::get is called

static final class ISS implements Supplier<InputStream> {
    final InputStream in;
    GZIPInputStream gz;
    ISS(InputStream in) {
        this.in = in;
    }
    public synchronized InputStream get() {
        if (gz == null) {
            try {
                gz = new GZIPInputStream(in);
            } catch (IOException t) {
                throw new UncheckedIOException(t);
            }
        }
        return gz;
    }
}
Setula answered 16/1, 2019 at 16:25 Comment(3)
Note: If someone knows how to format the code snippet above I'd be grateful. clicking on the {} button on the editor doesn't seem to work.Setula
Seems that numbered list content doesn't play nice with code samples, so I've replaced your numbered list with headed sections. (Feel free to edit the headers to give them more descriptive names.) By the way, in the end I went with the new GZIPInputStream(response.getBody()) workaround, as shown in my answer to another question.Discretional
Ah... Thanks for the info! I'll remember that for next time :-)Setula
C
1

Encountered the exact same problem. I tried the example in the Javadoc of the BodySubscribers.mapping method. Same behavior, the application hangs without any errors.

Could be a bug, because this is an official example from the Javadoc.

  public static <W> BodySubscriber<W> asJSON(Class<W> targetType) {
     BodySubscriber<InputStream> upstream = BodySubscribers.ofInputStream();

     BodySubscriber<W> downstream = BodySubscribers.mapping(
           upstream,
           (InputStream is) -> {
               try (InputStream stream = is) {
                   ObjectMapper objectMapper = new ObjectMapper();
                   return objectMapper.readValue(stream, targetType);
               } catch (IOException e) {
                   throw new UncheckedIOException(e);
               }
           });
    return downstream;
 } }
Chilpancingo answered 11/1, 2019 at 18:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.