From 2dafdca160b7eec2606ec7b048ec2d12ab15b7b3 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Fri, 16 Aug 2024 14:01:08 -0400 Subject: [PATCH] Input streams returned by DecompressingEntity.getContent() should support mark(int) and reset() if the underlying stream does - Pass calls to the underlying InputStream for mark(int) - Pass calls to the underlying InputStream for reset() - LazyDecompressingInputStream extends FilterInputStream - Reimplement internals fluently - Reuse Closer.close(Closeable) - Tested locally with git master 5.3-beta2-SNAPSHOT --- .../entity/LazyDecompressingInputStream.java | 50 +++++++++++-------- .../http/entity/TestDecompressingEntity.java | 17 +++++++ 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/LazyDecompressingInputStream.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/LazyDecompressingInputStream.java index 88b581853c..84150dbc02 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/LazyDecompressingInputStream.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/LazyDecompressingInputStream.java @@ -26,15 +26,18 @@ */ package org.apache.hc.client5.http.entity; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; + +import org.apache.hc.core5.io.Closer; /** - * Lazy init InputStream wrapper. + * Lazy initializes from an {@link InputStream} wrapper. */ -class LazyDecompressingInputStream extends InputStream { +class LazyDecompressingInputStream extends FilterInputStream { - private final InputStream wrappedStream; private final InputStreamFactory inputStreamFactory; private InputStream wrapperStream; @@ -42,38 +45,35 @@ class LazyDecompressingInputStream extends InputStream { public LazyDecompressingInputStream( final InputStream wrappedStream, final InputStreamFactory inputStreamFactory) { - this.wrappedStream = wrappedStream; + super(wrappedStream); this.inputStreamFactory = inputStreamFactory; } - private void initWrapper() throws IOException { + private InputStream initWrapper() throws IOException { if (wrapperStream == null) { - wrapperStream = inputStreamFactory.create(wrappedStream); + wrapperStream = inputStreamFactory.create(in); } + return wrapperStream; } @Override public int read() throws IOException { - initWrapper(); - return wrapperStream.read(); + return initWrapper().read(); } @Override public int read(final byte[] b) throws IOException { - initWrapper(); - return wrapperStream.read(b); + return initWrapper().read(b); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { - initWrapper(); - return wrapperStream.read(b, off, len); + return initWrapper().read(b, off, len); } @Override public long skip(final long n) throws IOException { - initWrapper(); - return wrapperStream.skip(n); + return initWrapper().skip(n); } @Override @@ -83,19 +83,29 @@ public boolean markSupported() { @Override public int available() throws IOException { - initWrapper(); - return wrapperStream.available(); + return initWrapper().available(); } @Override public void close() throws IOException { try { - if (wrapperStream != null) { - wrapperStream.close(); - } + Closer.close(wrapperStream); } finally { - wrappedStream.close(); + super.close(); + } + } + + @Override + public void mark(final int readlimit) { + try { + initWrapper().mark(readlimit); + } catch (final IOException e) { + throw new UncheckedIOException(e); } } + @Override + public void reset() throws IOException { + initWrapper().reset(); + } } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/TestDecompressingEntity.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/TestDecompressingEntity.java index ecbb27cc2f..a3959d02d6 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/TestDecompressingEntity.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/TestDecompressingEntity.java @@ -76,6 +76,22 @@ void testStreaming() throws Exception { EntityUtils.consume(entity); } + @Test + void testStreamingMarking() throws Exception { + final CRC32 crc32 = new CRC32(); + final ByteArrayInputStream in = new ByteArrayInputStream("1234567890".getBytes(StandardCharsets.US_ASCII)); + final InputStreamEntity wrapped = new InputStreamEntity(in, -1, ContentType.DEFAULT_TEXT); + final ChecksumEntity entity = new ChecksumEntity(wrapped, crc32); + final InputStream in1 = entity.getContent(); + Assertions.assertEquals('1', in1.read()); + Assertions.assertEquals('2', in1.read()); + in1.mark(1); + Assertions.assertEquals('3', in1.read()); + in1.reset(); + Assertions.assertEquals('3', in1.read()); + EntityUtils.consume(entity); + } + @Test void testWriteToStream() throws Exception { final CRC32 crc32 = new CRC32(); @@ -101,3 +117,4 @@ public ChecksumEntity(final HttpEntity wrapped, final Checksum checksum) { } } +