Skip to content

Commit 4b0cbd4

Browse files
committed
HTTPCLIENT-2422: Restore lazy content decompression
Defer construction of the decompression stream until the response content is actually read.
1 parent d989173 commit 4b0cbd4

2 files changed

Lines changed: 90 additions & 3 deletions

File tree

httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/DecompressingEntity.java

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727

2828
package org.apache.hc.client5.http.entity.compress;
2929

30+
import java.io.FilterInputStream;
3031
import java.io.IOException;
3132
import java.io.InputStream;
3233
import java.io.OutputStream;
34+
3335
import org.apache.hc.core5.http.HttpEntity;
3436
import org.apache.hc.core5.http.io.entity.HttpEntityWrapper;
3537
import org.apache.hc.core5.io.IOFunction;
@@ -48,19 +50,23 @@ public DecompressingEntity(
4850
this.decoder = Args.notNull(decoder, "Stream decoder");
4951
}
5052

53+
private InputStream getDecompressingStream() throws IOException {
54+
return new LazyDecompressingInputStream(super.getContent(), decoder);
55+
}
56+
5157
/**
5258
* Returns the cached <em>decoded</em> stream, creating it once if necessary.
5359
*/
5460
@Override
5561
public InputStream getContent() throws IOException {
5662
if (!isStreaming()) {
57-
return decoder.apply(super.getContent());
63+
return getDecompressingStream();
5864
}
5965

6066
InputStream local = cached;
6167
if (local == null) {
6268
if (cached == null) {
63-
cached = decoder.apply(super.getContent());
69+
cached = getDecompressingStream();
6470
}
6571
local = cached;
6672
}
@@ -97,4 +103,61 @@ public void writeTo(final OutputStream out) throws IOException {
97103
}
98104
}
99105
}
100-
}
106+
107+
private static final class LazyDecompressingInputStream extends FilterInputStream {
108+
109+
private final IOFunction<InputStream, InputStream> decoder;
110+
private InputStream decompressedStream;
111+
112+
private LazyDecompressingInputStream(
113+
final InputStream inputStream,
114+
final IOFunction<InputStream, InputStream> decoder) {
115+
super(inputStream);
116+
this.decoder = decoder;
117+
}
118+
119+
private InputStream getDecompressedStream() throws IOException {
120+
if (decompressedStream == null) {
121+
decompressedStream = decoder.apply(in);
122+
}
123+
return decompressedStream;
124+
}
125+
126+
@Override
127+
public int read() throws IOException {
128+
return getDecompressedStream().read();
129+
}
130+
131+
@Override
132+
public int read(final byte[] b) throws IOException {
133+
return getDecompressedStream().read(b);
134+
}
135+
136+
@Override
137+
public int read(final byte[] b, final int off, final int len) throws IOException {
138+
return getDecompressedStream().read(b, off, len);
139+
}
140+
141+
@Override
142+
public long skip(final long n) throws IOException {
143+
return getDecompressedStream().skip(n);
144+
}
145+
146+
@Override
147+
public int available() throws IOException {
148+
return getDecompressedStream().available();
149+
}
150+
151+
@Override
152+
public void close() throws IOException {
153+
final InputStream local = decompressedStream;
154+
if (local != null) {
155+
local.close();
156+
} else {
157+
super.close();
158+
}
159+
}
160+
161+
}
162+
163+
}

httpclient5/src/test/java/org/apache/hc/client5/http/entity/TestDecompressingEntity.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@
2929

3030
import java.io.ByteArrayInputStream;
3131
import java.io.ByteArrayOutputStream;
32+
import java.io.IOException;
3233
import java.io.InputStream;
3334
import java.nio.charset.StandardCharsets;
3435
import java.util.zip.CRC32;
3536
import java.util.zip.CheckedInputStream;
3637
import java.util.zip.Checksum;
38+
import java.util.zip.GZIPInputStream;
3739

3840
import org.apache.hc.core5.http.ContentType;
3941
import org.apache.hc.core5.http.HttpEntity;
@@ -108,6 +110,28 @@ void testWriteToStream() throws Exception {
108110
}
109111
}
110112

113+
@Test
114+
void testMalformedGzipContentCanBeClosedWithoutReading() throws Exception {
115+
final ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
116+
final InputStreamEntity wrapped = new InputStreamEntity(in, -1, ContentType.APPLICATION_OCTET_STREAM);
117+
final HttpEntity entity = new org.apache.hc.client5.http.entity.compress.DecompressingEntity(
118+
wrapped, GZIPInputStream::new);
119+
120+
Assertions.assertDoesNotThrow(() -> entity.getContent().close());
121+
}
122+
123+
@Test
124+
void testMalformedGzipContentFailsOnRead() throws Exception {
125+
final ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
126+
final InputStreamEntity wrapped = new InputStreamEntity(in, -1, ContentType.APPLICATION_OCTET_STREAM);
127+
final HttpEntity entity = new org.apache.hc.client5.http.entity.compress.DecompressingEntity(
128+
wrapped, GZIPInputStream::new);
129+
130+
try (final InputStream content = entity.getContent()) {
131+
Assertions.assertThrows(IOException.class, content::read);
132+
}
133+
}
134+
111135
static class ChecksumEntity extends org.apache.hc.client5.http.entity.compress.DecompressingEntity {
112136

113137
public ChecksumEntity(final HttpEntity wrapped, final Checksum checksum) {

0 commit comments

Comments
 (0)