com.google.api.client.http.GzipSupport Maven / Gradle / Ivy
Show all versions of google-http-client Show documentation
package com.google.api.client.http;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
final class GzipSupport {
private GzipSupport() {}
static GZIPInputStream newGzipInputStream(InputStream in) throws IOException {
return new GZIPInputStream(new OptimisticAvailabilityInputStream(in));
}
/**
* When {@link GZIPInputStream} completes processing an individual member it will call {@link
* InputStream#available()} to determine if there is more stream to try and process. If the call
* to {@code available()} returns 0 {@code GZIPInputStream} will determine it has processed the
* entirety of the underlying stream. This is spurious, as {@link InputStream#available()} is
* allowed to return 0 if it would require blocking in order for more bytes to be available. When
* {@code GZIPInputStream} is reading from a {@code Transfer-Encoding: chunked} response, if the
* chunk boundary happens to align closely enough to the member boundary {@code GZIPInputStream}
* won't consume the whole response.
*
* This class, provides an optimistic "estimate" (in actuality, a lie) of the number of {@code
* available()} bytes in the underlying stream. It does this by tracking the last number of bytes
* read. If the last number of bytes read is grater than -1, we return {@link Integer#MAX_VALUE}
* to any call of {@link #available()}.
*
*
We're breaking the contract of available() in that we're lying about how much data we have
* accessible without blocking, however in the case where we're weaving {@link GZIPInputStream}
* into response processing we already know there are going to be blocking calls to read before
* the stream is exhausted.
*
*
This scenario isn't unique to processing of chunked responses, and can be replicated
* reliably using a {@link java.io.SequenceInputStream} with two underlying {@link
* java.io.ByteArrayInputStream}. See the corresponding test class for a reproduction.
*
*
The need for this class has been verified for the following JVMs:
*
*
* -
*
* openjdk version "1.8.0_292"
* OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_292-b10)
* OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.292-b10, mixed mode)
*
* -
*
* openjdk version "11.0.14.1" 2022-02-08
* OpenJDK Runtime Environment Temurin-11.0.14.1+1 (build 11.0.14.1+1)
* OpenJDK 64-Bit Server VM Temurin-11.0.14.1+1 (build 11.0.14.1+1, mixed mode)
*
* -
*
* openjdk version "17" 2021-09-14
* OpenJDK Runtime Environment Temurin-17+35 (build 17+35)
* OpenJDK 64-Bit Server VM Temurin-17+35 (build 17+35, mixed mode, sharing)
*
*
*/
private static final class OptimisticAvailabilityInputStream extends FilterInputStream {
private int lastRead = 0;
OptimisticAvailabilityInputStream(InputStream delegate) {
super(delegate);
}
@Override
public int available() throws IOException {
return lastRead > -1 ? Integer.MAX_VALUE : 0;
}
@Override
public int read() throws IOException {
return lastRead = super.read();
}
@Override
public int read(byte[] b) throws IOException {
return lastRead = super.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return lastRead = super.read(b, off, len);
}
}
}