You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ab...@apache.org on 2025/06/19 16:36:45 UTC
(httpcomponents-client) branch master updated: HTTPCLIENT-1843 Plug Commons-Compress into HttpClient’s automatic (#651)
This is an automated email from the ASF dual-hosted git repository.
abernal pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git
The following commit(s) were added to refs/heads/master by this push:
new d1eb5b8b0 HTTPCLIENT-1843 Plug Commons-Compress into HttpClient’s automatic (#651)
d1eb5b8b0 is described below
commit d1eb5b8b09b32bfe72b364edce378eb21a416167
Author: Arturo Bernal <ab...@apache.org>
AuthorDate: Thu Jun 19 18:36:40 2025 +0200
HTTPCLIENT-1843 Plug Commons-Compress into HttpClient’s automatic (#651)
content-decoding (optional)
* New ContentDecoderRegistry discovers extra codecs (br, zstd, xz, lz4, …)
via Commons-Compress when that jar is on the class-path; otherwise falls
back to the built-ins (gzip, deflate) only.
* No hard dependency added—projects that need the extra algorithms just add
`commons-compress` (and helper jars like google-brotli, zstd-jni, xz-java)
to their pom and HttpClient uses them automatically.
---
.../hc/client5/testing/sync/TestRedirects.java | 4 +-
httpclient5/pom.xml | 5 +
.../http/entity/BrotliInputStreamFactory.java | 11 ++
.../http/entity/DeflateInputStreamFactory.java | 11 ++
.../http/entity/GZIPInputStreamFactory.java | 11 ++
.../hc/client5/http/entity/InputStreamFactory.java | 17 +++
.../compress/CommonsCompressDecoderFactory.java | 117 +++++++++++++++++
.../http/entity/compress/ContentCoding.java | 136 +++++++++++++++++++
.../entity/compress/ContentDecoderRegistry.java | 144 +++++++++++++++++++++
.../http/impl/classic/ContentCompressionExec.java | 58 +++++----
pom.xml | 7 +
11 files changed, 495 insertions(+), 26 deletions(-)
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java
index 8b67437ca..25c9e7f41 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java
@@ -646,8 +646,8 @@ public void handle(final ClassicHttpRequest request,
Assertions.assertEquals(new URIBuilder().setHttpHost(target).setPath("/random/100").build(),
reqWrapper.getUri());
- assertThat(values.poll(), CoreMatchers.equalTo("gzip, x-gzip, deflate"));
- assertThat(values.poll(), CoreMatchers.equalTo("gzip, x-gzip, deflate"));
+ assertThat(values.poll(), CoreMatchers.equalTo("gzip, deflate, lz4-framed, lz4-block, bzip2, pack200, deflate64, x-gzip"));
+ assertThat(values.poll(), CoreMatchers.equalTo("gzip, deflate, lz4-framed, lz4-block, bzip2, pack200, deflate64, x-gzip"));
assertThat(values.poll(), CoreMatchers.nullValue());
}
diff --git a/httpclient5/pom.xml b/httpclient5/pom.xml
index 47777876b..b66f028f0 100644
--- a/httpclient5/pom.xml
+++ b/httpclient5/pom.xml
@@ -108,6 +108,11 @@
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-compress</artifactId>
+ <optional>true</optional>
+ </dependency>
</dependencies>
<build>
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java
index b5eac4fec..68abf0aed 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java
@@ -41,6 +41,17 @@
@Contract(threading = ThreadingBehavior.STATELESS)
public class BrotliInputStreamFactory implements InputStreamFactory {
+ /**
+ * Canonical token for the deflate content-coding.
+ * @since 5.6
+ */
+ public static final String ENCODING = "br";
+
+ @Override
+ public String getContentEncoding() {
+ return ENCODING;
+ }
+
/**
* Default instance of {@link BrotliInputStreamFactory}.
*/
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/DeflateInputStreamFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/DeflateInputStreamFactory.java
index cfd113a76..ab1a285ab 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/DeflateInputStreamFactory.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/DeflateInputStreamFactory.java
@@ -41,6 +41,17 @@
@Contract(threading = ThreadingBehavior.STATELESS)
public class DeflateInputStreamFactory implements InputStreamFactory {
+ /**
+ * Canonical token for the deflate content-coding.
+ * @since 5.6
+ */
+ public static final String ENCODING = "deflate";
+
+ @Override
+ public String getContentEncoding() {
+ return ENCODING;
+ }
+
/**
* Default instance of {@link DeflateInputStreamFactory}.
*/
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/GZIPInputStreamFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/GZIPInputStreamFactory.java
index a03e20dd9..f59712afe 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/GZIPInputStreamFactory.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/GZIPInputStreamFactory.java
@@ -42,6 +42,17 @@
@Contract(threading = ThreadingBehavior.STATELESS)
public class GZIPInputStreamFactory implements InputStreamFactory {
+ /**
+ * Canonical token for the gzip content-coding.
+ * @since 5.6
+ */
+ public static final String ENCODING = "gzip";
+
+ @Override
+ public String getContentEncoding() {
+ return ENCODING;
+ }
+
/**
* Default instance of {@link GZIPInputStreamFactory}.
*/
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/InputStreamFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/InputStreamFactory.java
index a6689435e..b2664648e 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/InputStreamFactory.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/InputStreamFactory.java
@@ -38,4 +38,21 @@ public interface InputStreamFactory {
InputStream create(InputStream inputStream) throws IOException;
+ /**
+ * Returns the canonical {@code Content-Encoding} token handled by this
+ * factory (for example {@code "gzip"}, {@code "deflate"}, {@code "br"}).
+ * <p>
+ * Implementations that do <strong>not</strong> represent a HTTP
+ * content-decoder should simply inherit the default implementation,
+ * which returns an empty string.
+ *
+ * @return the lower-case encoding token, or an empty string when the
+ * factory is not intended for HTTP content-decoding
+ *
+ * @since 5.6
+ */
+ default String getContentEncoding() {
+ return "";
+ }
+
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressDecoderFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressDecoderFactory.java
new file mode 100644
index 000000000..412ba414b
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressDecoderFactory.java
@@ -0,0 +1,117 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.entity.compress;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import org.apache.hc.client5.http.entity.InputStreamFactory;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+
+/**
+ * A factory for creating InputStream instances, utilizing Apache Commons Compress.
+ * This class is compiled with Commons Compress as an optional dependency, loading
+ * only when the library is present at runtime, avoiding mandatory inclusion in
+ * downstream builds.
+ * <p>
+ * <p>
+ * Some encodings require native helper JARs; runtime availability is checked
+ * using a lightweight Class.forName probe to register codecs only when helpers
+ * are present.
+ *
+ * @since 5.6
+ */
+@Internal
+@Contract(threading = ThreadingBehavior.STATELESS)
+final class CommonsCompressDecoderFactory implements InputStreamFactory {
+
+
+ /**
+ * Map of codings that need extra JARs → the fully‐qualified class we test for
+ */
+ private static final Map<ContentCoding, String> REQUIRED_CLASS_NAME;
+
+ static {
+ final Map<ContentCoding, String> m = new EnumMap<>(ContentCoding.class);
+ m.put(ContentCoding.BROTLI, "org.brotli.dec.BrotliInputStream");
+ m.put(ContentCoding.ZSTD, "com.github.luben.zstd.ZstdInputStream");
+ m.put(ContentCoding.XZ, "org.tukaani.xz.XZInputStream");
+ m.put(ContentCoding.LZMA, "org.tukaani.xz.XZInputStream");
+ REQUIRED_CLASS_NAME = Collections.unmodifiableMap(m);
+ }
+
+ private final String encoding;
+
+ CommonsCompressDecoderFactory(final String encoding) {
+ this.encoding = encoding.toLowerCase(Locale.ROOT);
+ }
+
+ @Override
+ public String getContentEncoding() {
+ return encoding;
+ }
+
+ @Override
+ public InputStream create(final InputStream source) throws IOException {
+ try {
+ return new CompressorStreamFactory()
+ .createCompressorInputStream(encoding, source);
+ } catch (final CompressorException | LinkageError ex) {
+ throw new IOException(
+ "Unable to decode Content-Encoding '" + encoding + '\'', ex);
+ }
+ }
+
+
+ static boolean runtimeAvailable(final String token) {
+ final ContentCoding coding = ContentCoding.fromToken(token);
+ if (coding == null) {
+ return true;
+ }
+ final String helper = REQUIRED_CLASS_NAME.get(coding);
+ if (helper == null) {
+ // no extra JAR needed
+ return true;
+ }
+ try {
+ Class.forName(helper, false,
+ CommonsCompressDecoderFactory.class.getClassLoader());
+ return true;
+ } catch (final ClassNotFoundException | LinkageError ex) {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentCoding.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentCoding.java
new file mode 100644
index 000000000..3841a4ed0
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentCoding.java
@@ -0,0 +1,136 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.entity.compress;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Enumeration of the canonical IANA content-coding tokens supported by HttpClient for
+ * HTTP request and response bodies.
+ * <p>
+ * Each constant corresponds to the standard token used in the {@code Content-Encoding}
+ * and {@code Accept-Encoding} headers. Some codings (e.g. Brotli, Zstandard, XZ/LZMA)
+ * may require additional helper libraries at runtime.
+ *
+ * @since 5.6
+ */
+public enum ContentCoding {
+
+ /**
+ * GZIP compression format.
+ */
+ GZIP("gzip"),
+ /**
+ * "deflate" compression format (zlib or raw).
+ */
+ DEFLATE("deflate"),
+ /**
+ * Legacy alias for GZIP.
+ */
+ X_GZIP("x-gzip"),
+
+ // Optional codecs requiring Commons-Compress or native helpers
+ /**
+ * Brotli compression format.
+ */
+ BROTLI("br"),
+ /**
+ * Zstandard compression format.
+ */
+ ZSTD("zstd"),
+ /**
+ * XZ compression format.
+ */
+ XZ("xz"),
+ /**
+ * LZMA compression format.
+ */
+ LZMA("lzma"),
+ /**
+ * Framed LZ4 compression format.
+ */
+ LZ4_FRAMED("lz4-framed"),
+ /**
+ * Block LZ4 compression format.
+ */
+ LZ4_BLOCK("lz4-block"),
+ /**
+ * BZIP2 compression format.
+ */
+ BZIP2("bzip2"),
+ /**
+ * Pack200 compression format.
+ */
+ PACK200("pack200"),
+ /**
+ * Deflate64 compression format.
+ */
+ DEFLATE64("deflate64");
+
+ private static final Map<String, ContentCoding> TOKEN_LOOKUP;
+ static {
+ final Map<String, ContentCoding> map = new HashMap<>(values().length, 1f);
+ for (final ContentCoding contentCoding : values()) {
+ map.put(contentCoding.token, contentCoding);
+ }
+ TOKEN_LOOKUP = Collections.unmodifiableMap(map);
+ }
+
+ private final String token;
+
+ ContentCoding(final String token) {
+ this.token = token;
+ }
+
+ /**
+ * Returns the standard IANA token string for this content-coding.
+ *
+ * @return the lowercase token used in HTTP headers
+ */
+ public String token() {
+ return token;
+ }
+
+ /**
+ * Lookup an enum by its token (case‐insensitive), or {@code null} if none matches.
+ * <p>
+ * This method is backed by a static, pre‐populated map so the lookup is O(1)
+ * instead of O(n).</p>
+ *
+ * @param token the content‐coding token to look up
+ * @return the matching enum constant, or {@code null} if none
+ */
+ public static ContentCoding fromToken(final String token) {
+ return TOKEN_LOOKUP.get(
+ token == null ? null : token.toLowerCase(Locale.ROOT)
+ );
+ }
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentDecoderRegistry.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentDecoderRegistry.java
new file mode 100644
index 000000000..a019f705e
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentDecoderRegistry.java
@@ -0,0 +1,144 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.entity.compress;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.hc.client5.http.entity.BrotliDecompressingEntity;
+import org.apache.hc.client5.http.entity.BrotliInputStreamFactory;
+import org.apache.hc.client5.http.entity.DeflateInputStreamFactory;
+import org.apache.hc.client5.http.entity.GZIPInputStreamFactory;
+import org.apache.hc.client5.http.entity.InputStreamFactory;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+
+/**
+ * Immutable run-time catalogue of {@link InputStreamFactory} instances
+ * capable of <em>decoding</em> HTTP entity bodies.
+ *
+ * <p>The map is populated once during class initialisation:</p>
+ * <ol>
+ * <li>Built-ins: {@code gzip} and {@code deflate} are always present.</li>
+ * <li>If Commons-Compress is on the class-path we register a configurable
+ * list of codecs (br, zstd, xz, …) via
+ * {@link CommonsCompressDecoderFactory} – guarded by a cheap
+ * presence check.</li>
+ * <li>If Commons was absent or could not supply <code>br</code>,
+ * we fall back to the pure native singleton
+ * {@link BrotliInputStreamFactory} (when the <code>org.brotli</code>
+ * decoder JAR is available).</li>
+ * </ol>
+ *
+ * <p>The resulting {@code Map} is wrapped in
+ * {@link Collections#unmodifiableMap(Map)} and published through
+ * {@link #getRegistry()} for safe, lock-free concurrent reads.</p>
+ *
+ * @since 5.6
+ */
+@Internal
+@Contract(threading = ThreadingBehavior.STATELESS)
+public final class ContentDecoderRegistry {
+
+ private static final String CCSF =
+ "org.apache.commons.compress.compressors.CompressorStreamFactory";
+
+
+ private static final Map<ContentCoding, InputStreamFactory> REGISTRY = buildRegistry();
+
+
+ /**
+ * Returns the unmodifiable codec map (key = canonical token, value = factory).
+ */
+ public static Map<ContentCoding, InputStreamFactory> getRegistry() {
+ return REGISTRY;
+ }
+
+
+ private static Map<ContentCoding, InputStreamFactory> buildRegistry() {
+ final LinkedHashMap<ContentCoding, InputStreamFactory> m = new LinkedHashMap<>();
+
+ // 1. Built-ins
+ register(m, ContentCoding.GZIP, new GZIPInputStreamFactory());
+ register(m, ContentCoding.DEFLATE, new DeflateInputStreamFactory());
+
+ // 2. Commons-Compress (optional)
+ if (commonsCompressPresent()) {
+ for (final ContentCoding coding : Arrays.asList(
+ ContentCoding.BROTLI,
+ ContentCoding.ZSTD,
+ ContentCoding.XZ,
+ ContentCoding.LZMA,
+ ContentCoding.LZ4_FRAMED,
+ ContentCoding.LZ4_BLOCK,
+ ContentCoding.BZIP2,
+ ContentCoding.PACK200,
+ ContentCoding.DEFLATE64)) {
+ addCommons(m, coding);
+ }
+ }
+
+ // 3. Native Brotli fallback if Commons did not register it
+ if (!m.containsKey(ContentCoding.BROTLI)
+ && BrotliDecompressingEntity.isAvailable()) {
+ register(m, ContentCoding.BROTLI, new BrotliInputStreamFactory());
+ }
+
+ return Collections.unmodifiableMap(m);
+ }
+
+ private static void register(final Map<ContentCoding, InputStreamFactory> map,
+ final ContentCoding coding,
+ final InputStreamFactory factory) {
+ map.put(coding, factory);
+ }
+
+ private static void addCommons(final Map<ContentCoding, InputStreamFactory> map,
+ final ContentCoding coding) {
+ if (CommonsCompressDecoderFactory.runtimeAvailable(coding.token())) {
+ register(map, coding, new CommonsCompressDecoderFactory(coding.token()));
+ }
+ }
+
+ private static boolean commonsCompressPresent() {
+ try {
+ Class.forName(
+ CCSF, false, ContentDecoderRegistry.class.getClassLoader());
+ return true;
+ } catch (final ClassNotFoundException | LinkageError ex) {
+ return false;
+ }
+ }
+
+ private ContentDecoderRegistry() {
+ }
+}
+
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java
index ac3cd5a55..4b9d7289d 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java
@@ -29,20 +29,20 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.zip.GZIPInputStream;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.client5.http.config.RequestConfig;
-import org.apache.hc.client5.http.entity.BrotliDecompressingEntity;
-import org.apache.hc.client5.http.entity.BrotliInputStreamFactory;
import org.apache.hc.client5.http.entity.DecompressingEntity;
import org.apache.hc.client5.http.entity.DeflateInputStream;
-import org.apache.hc.client5.http.entity.DeflateInputStreamFactory;
-import org.apache.hc.client5.http.entity.GZIPInputStreamFactory;
import org.apache.hc.client5.http.entity.InputStreamFactory;
+import org.apache.hc.client5.http.entity.compress.ContentCoding;
+import org.apache.hc.client5.http.entity.compress.ContentDecoderRegistry;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
@@ -81,36 +81,47 @@ public final class ContentCompressionExec implements ExecChainHandler {
private final Lookup<InputStreamFactory> decoderRegistry;
private final boolean ignoreUnknown;
+ private static final Map<ContentCoding, InputStreamFactory> DECODERS = ContentDecoderRegistry.getRegistry();
+
+ /**
+ * Pre-built list of all supported tokens (plus X-GZIP alias) for
+ * the Accept-Encoding header, to avoid reconstructing it every time.
+ */
+ private static final List<String> DEFAULT_ACCEPT_ENCODINGS;
+ static {
+ final List<String> tmp = new ArrayList<>(DECODERS.size() + 1);
+ for (final ContentCoding coding : DECODERS.keySet()) {
+ tmp.add(coding.token());
+ }
+ // add x-gzip alias if gzip is present
+ if (DECODERS.containsKey(ContentCoding.GZIP)) {
+ tmp.add(ContentCoding.X_GZIP.token());
+ }
+ DEFAULT_ACCEPT_ENCODINGS = Collections.unmodifiableList(tmp);
+ }
+
public ContentCompressionExec(
final List<String> acceptEncoding,
final Lookup<InputStreamFactory> decoderRegistry,
final boolean ignoreUnknown) {
- final boolean brotliSupported = decoderRegistry == null && BrotliDecompressingEntity.isAvailable();
- if (acceptEncoding != null) {
- this.acceptEncoding = MessageSupport.headerOfTokens(HttpHeaders.ACCEPT_ENCODING, acceptEncoding);
- } else {
- final List<String> encodings = new ArrayList<>(4);
- encodings.add("gzip");
- encodings.add("x-gzip");
- encodings.add("deflate");
- if (brotliSupported) {
- encodings.add("br");
- }
- this.acceptEncoding = MessageSupport.headerOfTokens(HttpHeaders.ACCEPT_ENCODING, encodings);
- }
+ final List<String> encodingsHeader = acceptEncoding != null ? acceptEncoding : DEFAULT_ACCEPT_ENCODINGS;
+
+ this.acceptEncoding = MessageSupport.headerOfTokens(HttpHeaders.ACCEPT_ENCODING, encodingsHeader);
+
if (decoderRegistry != null) {
this.decoderRegistry = decoderRegistry;
} else {
- final RegistryBuilder<InputStreamFactory> builder = RegistryBuilder.<InputStreamFactory>create()
- .register("gzip", GZIPInputStreamFactory.getInstance())
- .register("x-gzip", GZIPInputStreamFactory.getInstance())
- .register("deflate", DeflateInputStreamFactory.getInstance());
- if (brotliSupported) {
- builder.register("br", BrotliInputStreamFactory.getInstance());
+ final RegistryBuilder<InputStreamFactory> builder = RegistryBuilder.create();
+ DECODERS.forEach((coding, factory) ->
+ builder.register(coding.token(), factory));
+ // register the x-gzip alias again
+ if (DECODERS.containsKey(ContentCoding.GZIP)) {
+ builder.register(ContentCoding.X_GZIP.token(), DECODERS.get(ContentCoding.GZIP));
}
this.decoderRegistry = builder.build();
}
+
this.ignoreUnknown = ignoreUnknown;
}
@@ -176,5 +187,4 @@ public ClassicHttpResponse execute(
}
return response;
}
-
}
diff --git a/pom.xml b/pom.xml
index 5fd9b3442..7556ea010 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,6 +78,7 @@
<junixsocket.version>2.10.1</junixsocket.version>
<api.comparison.version>5.3</api.comparison.version>
<hc.animal-sniffer.signature.ignores>javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer</hc.animal-sniffer.signature.ignores>
+ <commons.compress.version>1.27.1</commons.compress.version>
</properties>
<dependencyManagement>
@@ -204,6 +205,12 @@
<artifactId>commons-io</artifactId>
<version>2.19.0</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-compress</artifactId>
+ <version>${commons.compress.version}</version>
+ </dependency>
+
</dependencies>
</dependencyManagement>