diff --git a/src/main/java/io/github/nstdio/http/ext/BodyHandlers.java b/src/main/java/io/github/nstdio/http/ext/BodyHandlers.java
index 0386829..bc71ea4 100644
--- a/src/main/java/io/github/nstdio/http/ext/BodyHandlers.java
+++ b/src/main/java/io/github/nstdio/http/ext/BodyHandlers.java
@@ -22,6 +22,8 @@
*/
package io.github.nstdio.http.ext;
+import io.github.nstdio.http.ext.DecompressingBodyHandler.Options;
+
import java.io.InputStream;
import java.net.http.HttpResponse.BodyHandler;
@@ -29,14 +31,66 @@
* Implementations of {@code BodyHandler}'s.
*/
public final class BodyHandlers {
- private BodyHandlers() {
- }
+ private BodyHandlers() {
+ }
+
+ /**
+ * Wraps response body {@code InputStream} in on-the-fly decompressing {@code InputStream} in accordance with
+ * Content-Encoding header semantics.
+ *
+ * @return The decompressing body handler.
+ */
+ public static BodyHandler ofDecompressing() {
+ return new DecompressingBodyHandlerBuilder().build();
+ }
+
+ /**
+ * Creates new {@code DecompressingBodyHandlerBuilder} instance.
+ *
+ * @return The builder for decompressing body handler.
+ */
+ public static DecompressingBodyHandlerBuilder decompressingBuilder() {
+ return new DecompressingBodyHandlerBuilder();
+ }
+
+ /**
+ * The builder for decompressing body handler.
+ */
+ public static final class DecompressingBodyHandlerBuilder {
+ private boolean failOnUnsupportedDirectives = true;
+ private boolean failOnUnknownDirectives = true;
+
+ /**
+ * Sets whether throw exception when compression directive not supported or not.
+ *
+ * @param failOnUnsupportedDirectives Whether throw exception when compression directive not supported or not
+ * @return this for fluent chaining.
+ */
+ public DecompressingBodyHandlerBuilder failOnUnsupportedDirectives(boolean failOnUnsupportedDirectives) {
+ this.failOnUnsupportedDirectives = failOnUnsupportedDirectives;
+ return this;
+ }
+
+ /**
+ * Sets whether throw exception when unknown compression directive encountered or not.
+ *
+ * @param failOnUnknownDirectives Whether throw exception when unknown compression directive encountered or not
+ * @return this for fluent chaining.
+ */
+ public DecompressingBodyHandlerBuilder failOnUnknownDirectives(boolean failOnUnknownDirectives) {
+ this.failOnUnknownDirectives = failOnUnknownDirectives;
+ return this;
+ }
- /**
- * @return The decompressing body handler.
- */
- public static BodyHandler ofDecompressing() {
- return new DecompressingBodyHandler();
- }
+ /**
+ * Creates the new decompressing body handler.
+ *
+ * @return The builder for decompressing body handler.
+ */
+ public BodyHandler build() {
+ var config = new Options(failOnUnsupportedDirectives, failOnUnknownDirectives);
+ return new DecompressingBodyHandler(config);
+ }
+ }
}
diff --git a/src/main/java/io/github/nstdio/http/ext/DecompressingBodyHandler.java b/src/main/java/io/github/nstdio/http/ext/DecompressingBodyHandler.java
index 345ff73..c0d117e 100644
--- a/src/main/java/io/github/nstdio/http/ext/DecompressingBodyHandler.java
+++ b/src/main/java/io/github/nstdio/http/ext/DecompressingBodyHandler.java
@@ -22,9 +22,6 @@
*/
package io.github.nstdio.http.ext;
-import static java.util.function.Predicate.not;
-import static java.util.stream.Collectors.toList;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
@@ -37,57 +34,82 @@
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
+import static java.util.function.Predicate.not;
+import static java.util.stream.Collectors.toList;
+
final class DecompressingBodyHandler implements BodyHandler {
- private static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
- private static final Pattern COMMA_PATTERN = Pattern.compile(",", Pattern.LITERAL);
- private static final String UNSUPPORTED_DIRECTIVE = "Compression directive '%s' is not supported";
- private static final String UNKNOWN_DIRECTIVE = "Unknown compression directive '%s'";
+ private static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
+ private static final Pattern COMMA_PATTERN = Pattern.compile(",", Pattern.LITERAL);
+ private static final String UNSUPPORTED_DIRECTIVE = "Compression directive '%s' is not supported";
+ private static final String UNKNOWN_DIRECTIVE = "Unknown compression directive '%s'";
- // Visible for testing
- static Function decompressionFn(String directive) {
- switch (directive) {
- case "x-gzip":
- case "gzip":
- return in -> {
- try {
- return new GZIPInputStream(in);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- };
- case "deflate":
- return InflaterInputStream::new;
- case "compress":
- case "br":
- throw new UnsupportedOperationException(String.format(UNSUPPORTED_DIRECTIVE, directive));
- default:
- throw new IllegalArgumentException(String.format(UNKNOWN_DIRECTIVE, directive));
+ private final Options options;
+
+ DecompressingBodyHandler(Options config) {
+ this.options = config;
}
- }
- @Override
- public BodySubscriber apply(ResponseInfo responseInfo) {
- var encodingOpt = responseInfo
- .headers()
- .firstValue(HEADER_CONTENT_ENCODING);
+ // Visible for testing
+ Function decompressionFn(String directive) {
+ switch (directive) {
+ case "x-gzip":
+ case "gzip":
+ return in -> {
+ try {
+ return new GZIPInputStream(in);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ };
+ case "deflate":
+ return InflaterInputStream::new;
+ case "compress":
+ case "br":
+ if (options.failOnUnsupportedDirectives) {
+ throw new UnsupportedOperationException(String.format(UNSUPPORTED_DIRECTIVE, directive));
+ }
+ return Function.identity();
+ default:
+ if (options.failOnUnknownDirectives) {
+ throw new IllegalArgumentException(String.format(UNKNOWN_DIRECTIVE, directive));
+ }
- if (encodingOpt.isEmpty()) {
- return BodySubscribers.ofInputStream();
+ return Function.identity();
+ }
}
- var encodings = COMMA_PATTERN
- .splitAsStream(encodingOpt.get())
- .map(String::trim)
- .filter(not(String::isEmpty))
- .collect(toList());
+ @Override
+ public BodySubscriber apply(ResponseInfo responseInfo) {
+ var encodingOpt = responseInfo
+ .headers()
+ .firstValue(HEADER_CONTENT_ENCODING);
+
+ if (encodingOpt.isEmpty()) {
+ return BodySubscribers.ofInputStream();
+ }
+
+ var encodings = COMMA_PATTERN
+ .splitAsStream(encodingOpt.get())
+ .map(String::trim)
+ .filter(not(String::isEmpty))
+ .collect(toList());
- return encodings
- .stream()
- .map(DecompressingBodyHandler::decompressionFn)
- .reduce(Function::andThen)
- .>map(DecompressingBodySubscriber::new)
- .orElseGet(BodySubscribers::ofInputStream);
- }
+ return encodings
+ .stream()
+ .map(this::decompressionFn)
+ .reduce(Function::andThen)
+ .>map(DecompressingBodySubscriber::new)
+ .orElseGet(BodySubscribers::ofInputStream);
+ }
+
+ static class Options {
+ private final boolean failOnUnsupportedDirectives;
+ private final boolean failOnUnknownDirectives;
+ Options(boolean failOnUnsupportedDirectives, boolean failOnUnknownDirectives) {
+ this.failOnUnsupportedDirectives = failOnUnsupportedDirectives;
+ this.failOnUnknownDirectives = failOnUnknownDirectives;
+ }
+ }
}
diff --git a/src/test/java/io/github/nstdio/http/ext/DecompressingBodyHandlerTest.java b/src/test/java/io/github/nstdio/http/ext/DecompressingBodyHandlerTest.java
index 2645b5d..ed85945 100644
--- a/src/test/java/io/github/nstdio/http/ext/DecompressingBodyHandlerTest.java
+++ b/src/test/java/io/github/nstdio/http/ext/DecompressingBodyHandlerTest.java
@@ -22,65 +22,116 @@
*/
package io.github.nstdio.http.ext;
-import static io.github.nstdio.http.ext.Compression.deflate;
-import static io.github.nstdio.http.ext.Compression.gzip;
-import static io.github.nstdio.http.ext.DecompressingBodyHandler.decompressionFn;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-
+import io.github.nstdio.http.ext.DecompressingBodyHandler.Options;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.ByteArrayInputStream;
+import java.io.InputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
+import static io.github.nstdio.http.ext.Compression.deflate;
+import static io.github.nstdio.http.ext.Compression.gzip;
+import static org.assertj.core.api.Assertions.*;
+
class DecompressingBodyHandlerTest {
- @ParameterizedTest
- @ValueSource(strings = {"gzip", "x-gzip"})
- void shouldReturnGzipInputStream(String directive) {
- var gzipContent = new ByteArrayInputStream(gzip("abc"));
-
- //when
- var fn = decompressionFn(directive);
- var inputStream = fn.apply(gzipContent);
-
- //then
- assertThat(inputStream)
- .isInstanceOf(GZIPInputStream.class);
- }
-
- @Test
- void shouldReturnDeflateInputStream() {
- var deflateContent = new ByteArrayInputStream(deflate("abc"));
-
- //when
- var fn = decompressionFn("deflate");
- var inputStream = fn.apply(deflateContent);
-
- //then
- assertThat(inputStream)
- .isInstanceOf(InflaterInputStream.class);
- }
-
- @ParameterizedTest
- @ValueSource(strings = {"compress", "br"})
- void shouldThrowUnsupportedOperationException(String directive) {
- //when + then
- assertThatExceptionOfType(UnsupportedOperationException.class)
- .isThrownBy(() -> decompressionFn(directive))
- .withMessage("Compression directive '%s' is not supported", directive);
- }
-
- @ParameterizedTest
- @ValueSource(strings = {"", "abc", "gz", "a"})
- void shouldThrowIllegalArgumentException(String directive) {
- //when + then
- assertThatIllegalArgumentException()
- .isThrownBy(() -> decompressionFn(directive))
- .withMessage("Unknown compression directive '%s'", directive);
- }
+ private DecompressingBodyHandler handler;
+
+ @BeforeEach
+ void setUp() {
+ handler = new DecompressingBodyHandler(new Options(true, true));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"gzip", "x-gzip"})
+ void shouldReturnGzipInputStream(String directive) {
+ var gzipContent = new ByteArrayInputStream(gzip("abc"));
+
+ //when
+ var fn = handler.decompressionFn(directive);
+ var inputStream = fn.apply(gzipContent);
+
+ //then
+ assertThat(inputStream)
+ .isInstanceOf(GZIPInputStream.class);
+ }
+
+ @Test
+ void shouldReturnDeflateInputStream() {
+ var deflateContent = new ByteArrayInputStream(deflate("abc"));
+
+ //when
+ var fn = handler.decompressionFn("deflate");
+ var inputStream = fn.apply(deflateContent);
+
+ //then
+ assertThat(inputStream)
+ .isInstanceOf(InflaterInputStream.class);
+ }
+
+
+ @Nested
+ class FailureControlOptions {
+ @ParameterizedTest
+ @ValueSource(strings = {"compress", "br"})
+ void shouldThrowUnsupportedOperationException(String directive) {
+ //given
+ var options = new Options(true, true);
+ var handler = new DecompressingBodyHandler(options);
+
+ //when + then
+ assertThatExceptionOfType(UnsupportedOperationException.class)
+ .isThrownBy(() -> handler.decompressionFn(directive))
+ .withMessage("Compression directive '%s' is not supported", directive);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", "abc", "gz", "a"})
+ void shouldThrowIllegalArgumentException(String directive) {
+ //when + then
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> handler.decompressionFn(directive))
+ .withMessage("Unknown compression directive '%s'", directive);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"compress", "br"})
+ @DisplayName("Should not throw exception when 'failOnUnsupportedDirectives' is 'false'")
+ void shouldNotThrowUnsupportedOperationException(String directive) {
+ //given
+ var options = new Options(false, true);
+ var handler = new DecompressingBodyHandler(options);
+ var in = InputStream.nullInputStream();
+
+ //when
+ var fn = handler.decompressionFn(directive);
+ var actual = fn.apply(in);
+
+ //then
+ assertThat(actual).isSameAs(in);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", "abc", "gz", "a"})
+ @DisplayName("Should not throw exception when 'failOnUnknownDirectives' is 'false'")
+ void shouldNotIllegalArgumentException(String directive) {
+ //given
+ var options = new Options(true, false);
+ var handler = new DecompressingBodyHandler(options);
+ var in = InputStream.nullInputStream();
+
+ //when
+ var fn = handler.decompressionFn(directive);
+ var actual = fn.apply(in);
+
+ //then
+ assertThat(actual).isSameAs(in);
+ }
+ }
}