garydgregory commented on code in PR #580:
URL: 
https://github.com/apache/httpcomponents-client/pull/580#discussion_r1810552783


##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingFactory.java:
##########
@@ -0,0 +1,277 @@
+/*
+ * ====================================================================
+ * 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.InputStream;
+import java.io.OutputStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import 
org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
+import org.apache.commons.compress.compressors.deflate.DeflateParameters;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.util.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory class for managing compression and decompression of HTTP entities 
using different compression formats.
+ * <p>
+ * This factory uses a cache to optimize access to available input and output 
stream providers for compression formats.
+ * It also allows the use of aliases (e.g., "gzip" and "x-gzip") and 
automatically formats the compression names
+ * to ensure consistency.
+ * </p>
+ *
+ * <p>
+ * Supported compression formats include gzip, deflate, and other available 
formats provided by the
+ * {@link CompressorStreamFactory}.
+ * </p>
+ *
+ * <p>
+ * This class is thread-safe and uses {@link AtomicReference} to cache the 
available input and output stream providers.
+ * </p>
+ *
+ * @since 5.5
+ */
+public class CompressingFactory {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CompressingFactory.class);
+    /**
+     * Singleton instance of the factory.
+     */
+    public static final CompressingFactory INSTANCE = new CompressingFactory();
+
+    private final CompressorStreamFactory compressorStreamFactory = new 
CompressorStreamFactory();
+    private final AtomicReference<Set<String>> inputProvidersCache = new 
AtomicReference<>();
+    private final AtomicReference<Set<String>> outputProvidersCache = new 
AtomicReference<>();
+    private final Map<String, String> formattedNameCache = new 
ConcurrentHashMap<>();
+
+    /**
+     * Returns a set of available input stream compression providers.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableInputProviders() {
+        return inputProvidersCache.updateAndGet(existing -> existing != null ? 
existing : fetchAvailableInputProviders());
+    }
+
+    /**
+     * Returns a set of available output stream compression providers.
+     *
+     * @return a set of available output stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableOutputProviders() {
+        return outputProvidersCache.updateAndGet(existing -> existing != null 
? existing : fetchAvailableOutputProviders());
+    }
+
+    /**
+     * Returns the formatted name of the provided compression format.
+     * <p>
+     * If the provided name matches an alias (e.g., "gzip" or "x-gzip"), the 
method will return the standard name.
+     * </p>
+     *
+     * @param name the compression format name.
+     * @return the formatted name, or the original name if no alias is found.
+     * @throws IllegalArgumentException if the name is null or empty.
+     */
+    public String getFormattedName(final String name) {
+        if (name == null || name.isEmpty()) {
+            LOG.warn("Compression name is null or empty");

Review Comment:
   This API should not do any logging, especially at such a high level. This is 
just a string-to-string conversion API, it has no business rasing warnings IMO.



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingFactory.java:
##########
@@ -0,0 +1,277 @@
+/*
+ * ====================================================================
+ * 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.InputStream;
+import java.io.OutputStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import 
org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
+import org.apache.commons.compress.compressors.deflate.DeflateParameters;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.util.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory class for managing compression and decompression of HTTP entities 
using different compression formats.
+ * <p>
+ * This factory uses a cache to optimize access to available input and output 
stream providers for compression formats.
+ * It also allows the use of aliases (e.g., "gzip" and "x-gzip") and 
automatically formats the compression names
+ * to ensure consistency.
+ * </p>
+ *
+ * <p>
+ * Supported compression formats include gzip, deflate, and other available 
formats provided by the
+ * {@link CompressorStreamFactory}.
+ * </p>
+ *
+ * <p>
+ * This class is thread-safe and uses {@link AtomicReference} to cache the 
available input and output stream providers.
+ * </p>
+ *
+ * @since 5.5
+ */
+public class CompressingFactory {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CompressingFactory.class);
+    /**
+     * Singleton instance of the factory.
+     */
+    public static final CompressingFactory INSTANCE = new CompressingFactory();
+
+    private final CompressorStreamFactory compressorStreamFactory = new 
CompressorStreamFactory();
+    private final AtomicReference<Set<String>> inputProvidersCache = new 
AtomicReference<>();
+    private final AtomicReference<Set<String>> outputProvidersCache = new 
AtomicReference<>();
+    private final Map<String, String> formattedNameCache = new 
ConcurrentHashMap<>();
+
+    /**
+     * Returns a set of available input stream compression providers.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableInputProviders() {
+        return inputProvidersCache.updateAndGet(existing -> existing != null ? 
existing : fetchAvailableInputProviders());
+    }
+
+    /**
+     * Returns a set of available output stream compression providers.
+     *
+     * @return a set of available output stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableOutputProviders() {
+        return outputProvidersCache.updateAndGet(existing -> existing != null 
? existing : fetchAvailableOutputProviders());
+    }
+
+    /**
+     * Returns the formatted name of the provided compression format.
+     * <p>
+     * If the provided name matches an alias (e.g., "gzip" or "x-gzip"), the 
method will return the standard name.
+     * </p>
+     *
+     * @param name the compression format name.
+     * @return the formatted name, or the original name if no alias is found.
+     * @throws IllegalArgumentException if the name is null or empty.
+     */
+    public String getFormattedName(final String name) {
+        if (name == null || name.isEmpty()) {
+            LOG.warn("Compression name is null or empty");
+            return null;
+        }
+        final String lowerCaseName = name.toLowerCase(Locale.ROOT);
+        return formattedNameCache.computeIfAbsent(lowerCaseName, key -> {
+            if ("gzip".equals(key) || "x-gzip".equals(key)) {
+                return "gz";
+            } else if ("compress".equals(key)) {
+                return "z";
+            }
+            return key;
+        });
+    }
+
+    /**
+     * Creates an input stream for the specified compression format and 
decompresses the provided input stream.
+     * <p>
+     * This method uses the specified compression name to decompress the input 
stream and supports the "noWrap" option
+     * for deflate streams.
+     * </p>
+     *
+     * @param name        the compression format.
+     * @param inputStream the input stream to decompress.
+     * @param noWrap      if true, disables the zlib header and trailer for 
deflate streams.
+     * @return the decompressed input stream, or the original input stream if 
the format is not supported.
+     */
+    public InputStream getCompressorInputStream(final String name, final 
InputStream inputStream, final boolean noWrap) throws CompressorException {
+        Args.notNull(inputStream, "InputStream");
+        Args.notNull(name, "name");
+        final String formattedName = getFormattedName(name);
+        return isSupported(formattedName, false)
+                ? createCompressorInputStream(formattedName, inputStream, 
noWrap)
+                : inputStream;
+    }
+
+    /**
+     * Creates an output stream for the specified compression format and 
compresses the provided output stream.
+     *
+     * @param name         the compression format.
+     * @param outputStream the output stream to compress.
+     * @return the compressed output stream, or the original output stream if 
the format is not supported.
+     */
+    public OutputStream getCompressorOutputStream(final String name, final 
OutputStream outputStream) throws CompressorException {
+        final String formattedName = getFormattedName(name);
+        return isSupported(formattedName, true)
+                ? createCompressorOutputStream(formattedName, outputStream)
+                : outputStream;
+    }
+
+    /**
+     * Decompresses the provided HTTP entity using the specified compression 
format.
+     *
+     * @param entity          the HTTP entity to decompress.
+     * @param contentEncoding the compression format.
+     * @return a decompressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity decompressEntity(final HttpEntity entity, final String 
contentEncoding) {
+        return decompressEntity(entity, contentEncoding, false);
+    }
+
+    /**
+     * Decompresses the provided HTTP entity using the specified compression 
format with the option for deflate streams.
+     *
+     * @param entity          the HTTP entity to decompress.
+     * @param contentEncoding the compression format.
+     * @param noWrap          if true, disables the zlib header and trailer 
for deflate streams.
+     * @return a decompressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity decompressEntity(final HttpEntity entity, final String 
contentEncoding, final boolean noWrap) {
+        Args.notNull(entity, "Entity");
+        Args.notNull(contentEncoding, "Content Encoding");
+        if (!isSupported(contentEncoding, false)) {
+            LOG.warn("Unsupported decompression type: {}", contentEncoding);
+            return null;
+        }
+        return new DecompressingEntity(entity, contentEncoding, noWrap);
+    }
+
+    /**
+     * Compresses the provided HTTP entity using the specified compression 
format.
+     *
+     * @param entity          the HTTP entity to compress.
+     * @param contentEncoding the compression format.
+     * @return a compressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity compressEntity(final HttpEntity entity, final String 
contentEncoding) {
+        Args.notNull(entity, "Entity");
+        Args.notNull(contentEncoding, "Content Encoding");
+        if (!isSupported(contentEncoding, true)) {
+            LOG.warn("Unsupported compression type: {}", contentEncoding);
+            return null;
+        }
+        return new CompressingEntity(entity, contentEncoding);
+    }
+
+    /**
+     * Fetches the available input stream compression providers from Commons 
Compress.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    private Set<String> fetchAvailableInputProviders() {
+        final Set<String> inputNames = 
compressorStreamFactory.getInputStreamCompressorNames();
+        return inputNames.stream()
+                .map(String::toLowerCase)
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Fetches the available output stream compression providers from Commons 
Compress.
+     *
+     * @return a set of available output stream compression providers in 
lowercase.
+     */
+    private Set<String> fetchAvailableOutputProviders() {

Review Comment:
   The "fetch" method name prefix is unusual and surprising to me, I would 
follow the "get" naming convention.



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingFactory.java:
##########
@@ -0,0 +1,277 @@
+/*
+ * ====================================================================
+ * 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.InputStream;
+import java.io.OutputStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import 
org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
+import org.apache.commons.compress.compressors.deflate.DeflateParameters;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.util.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory class for managing compression and decompression of HTTP entities 
using different compression formats.
+ * <p>
+ * This factory uses a cache to optimize access to available input and output 
stream providers for compression formats.
+ * It also allows the use of aliases (e.g., "gzip" and "x-gzip") and 
automatically formats the compression names
+ * to ensure consistency.
+ * </p>
+ *
+ * <p>
+ * Supported compression formats include gzip, deflate, and other available 
formats provided by the
+ * {@link CompressorStreamFactory}.
+ * </p>
+ *
+ * <p>
+ * This class is thread-safe and uses {@link AtomicReference} to cache the 
available input and output stream providers.
+ * </p>
+ *
+ * @since 5.5
+ */
+public class CompressingFactory {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CompressingFactory.class);
+    /**
+     * Singleton instance of the factory.
+     */
+    public static final CompressingFactory INSTANCE = new CompressingFactory();
+
+    private final CompressorStreamFactory compressorStreamFactory = new 
CompressorStreamFactory();
+    private final AtomicReference<Set<String>> inputProvidersCache = new 
AtomicReference<>();
+    private final AtomicReference<Set<String>> outputProvidersCache = new 
AtomicReference<>();
+    private final Map<String, String> formattedNameCache = new 
ConcurrentHashMap<>();
+
+    /**
+     * Returns a set of available input stream compression providers.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableInputProviders() {
+        return inputProvidersCache.updateAndGet(existing -> existing != null ? 
existing : fetchAvailableInputProviders());
+    }
+
+    /**
+     * Returns a set of available output stream compression providers.
+     *
+     * @return a set of available output stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableOutputProviders() {
+        return outputProvidersCache.updateAndGet(existing -> existing != null 
? existing : fetchAvailableOutputProviders());
+    }
+
+    /**
+     * Returns the formatted name of the provided compression format.
+     * <p>
+     * If the provided name matches an alias (e.g., "gzip" or "x-gzip"), the 
method will return the standard name.
+     * </p>
+     *
+     * @param name the compression format name.
+     * @return the formatted name, or the original name if no alias is found.
+     * @throws IllegalArgumentException if the name is null or empty.
+     */
+    public String getFormattedName(final String name) {
+        if (name == null || name.isEmpty()) {
+            LOG.warn("Compression name is null or empty");
+            return null;
+        }
+        final String lowerCaseName = name.toLowerCase(Locale.ROOT);
+        return formattedNameCache.computeIfAbsent(lowerCaseName, key -> {
+            if ("gzip".equals(key) || "x-gzip".equals(key)) {
+                return "gz";
+            } else if ("compress".equals(key)) {
+                return "z";
+            }
+            return key;
+        });
+    }
+
+    /**
+     * Creates an input stream for the specified compression format and 
decompresses the provided input stream.
+     * <p>
+     * This method uses the specified compression name to decompress the input 
stream and supports the "noWrap" option
+     * for deflate streams.
+     * </p>
+     *
+     * @param name        the compression format.
+     * @param inputStream the input stream to decompress.
+     * @param noWrap      if true, disables the zlib header and trailer for 
deflate streams.
+     * @return the decompressed input stream, or the original input stream if 
the format is not supported.
+     */
+    public InputStream getCompressorInputStream(final String name, final 
InputStream inputStream, final boolean noWrap) throws CompressorException {
+        Args.notNull(inputStream, "InputStream");
+        Args.notNull(name, "name");
+        final String formattedName = getFormattedName(name);
+        return isSupported(formattedName, false)
+                ? createCompressorInputStream(formattedName, inputStream, 
noWrap)
+                : inputStream;
+    }
+
+    /**
+     * Creates an output stream for the specified compression format and 
compresses the provided output stream.
+     *
+     * @param name         the compression format.
+     * @param outputStream the output stream to compress.
+     * @return the compressed output stream, or the original output stream if 
the format is not supported.
+     */
+    public OutputStream getCompressorOutputStream(final String name, final 
OutputStream outputStream) throws CompressorException {
+        final String formattedName = getFormattedName(name);
+        return isSupported(formattedName, true)
+                ? createCompressorOutputStream(formattedName, outputStream)
+                : outputStream;
+    }
+
+    /**
+     * Decompresses the provided HTTP entity using the specified compression 
format.
+     *
+     * @param entity          the HTTP entity to decompress.
+     * @param contentEncoding the compression format.
+     * @return a decompressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity decompressEntity(final HttpEntity entity, final String 
contentEncoding) {
+        return decompressEntity(entity, contentEncoding, false);
+    }
+
+    /**
+     * Decompresses the provided HTTP entity using the specified compression 
format with the option for deflate streams.
+     *
+     * @param entity          the HTTP entity to decompress.
+     * @param contentEncoding the compression format.
+     * @param noWrap          if true, disables the zlib header and trailer 
for deflate streams.
+     * @return a decompressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity decompressEntity(final HttpEntity entity, final String 
contentEncoding, final boolean noWrap) {
+        Args.notNull(entity, "Entity");
+        Args.notNull(contentEncoding, "Content Encoding");
+        if (!isSupported(contentEncoding, false)) {
+            LOG.warn("Unsupported decompression type: {}", contentEncoding);

Review Comment:
   This class should not do any logging IMO, especially warnings, it should 
throw an illegal argument exception or let the call site deal with a null 
result.
   



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingFactory.java:
##########
@@ -0,0 +1,277 @@
+/*
+ * ====================================================================
+ * 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.InputStream;
+import java.io.OutputStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import 
org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
+import org.apache.commons.compress.compressors.deflate.DeflateParameters;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.util.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory class for managing compression and decompression of HTTP entities 
using different compression formats.
+ * <p>
+ * This factory uses a cache to optimize access to available input and output 
stream providers for compression formats.
+ * It also allows the use of aliases (e.g., "gzip" and "x-gzip") and 
automatically formats the compression names
+ * to ensure consistency.
+ * </p>
+ *
+ * <p>
+ * Supported compression formats include gzip, deflate, and other available 
formats provided by the
+ * {@link CompressorStreamFactory}.
+ * </p>
+ *
+ * <p>
+ * This class is thread-safe and uses {@link AtomicReference} to cache the 
available input and output stream providers.
+ * </p>
+ *
+ * @since 5.5
+ */
+public class CompressingFactory {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CompressingFactory.class);
+    /**
+     * Singleton instance of the factory.
+     */
+    public static final CompressingFactory INSTANCE = new CompressingFactory();
+
+    private final CompressorStreamFactory compressorStreamFactory = new 
CompressorStreamFactory();
+    private final AtomicReference<Set<String>> inputProvidersCache = new 
AtomicReference<>();
+    private final AtomicReference<Set<String>> outputProvidersCache = new 
AtomicReference<>();
+    private final Map<String, String> formattedNameCache = new 
ConcurrentHashMap<>();
+
+    /**
+     * Returns a set of available input stream compression providers.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableInputProviders() {
+        return inputProvidersCache.updateAndGet(existing -> existing != null ? 
existing : fetchAvailableInputProviders());
+    }
+
+    /**
+     * Returns a set of available output stream compression providers.
+     *
+     * @return a set of available output stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableOutputProviders() {
+        return outputProvidersCache.updateAndGet(existing -> existing != null 
? existing : fetchAvailableOutputProviders());
+    }
+
+    /**
+     * Returns the formatted name of the provided compression format.

Review Comment:
   I don't understand what this Javadoc means. What's a "formatted" name? Maybe 
the API is misnamed? Is the job of the method to map from a content type value 
to a Commons Compress key? Is it something else? I think the comment should at 
least make it clear, which should guide us to a better API name.



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingEntity.java:
##########
@@ -0,0 +1,127 @@
+/*
+ * ====================================================================
+ * 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.io.OutputStream;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.entity.HttpEntityWrapper;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * An {@link HttpEntity} wrapper that applies compression to the content 
before writing it to
+ * an output stream. This class supports various compression algorithms based 
on the
+ * specified content encoding.
+ *
+ * <p>Compression is performed using {@link CompressingFactory}, which returns 
a corresponding
+ * {@link OutputStream} for the requested compression type. This class does 
not support
+ * reading the content directly through {@link #getContent()} as the content 
is always compressed
+ * during write operations.</p>
+ *
+ * @since 5.5
+ */
+public class CompressingEntity extends HttpEntityWrapper {
+
+    /**
+     * The content encoding type, e.g., "gzip", "deflate", etc.
+     */
+    private final String contentEncoding;
+
+    /**
+     * Creates a new {@link CompressingEntity} that compresses the wrapped 
entity's content
+     * using the specified content encoding.
+     *
+     * @param entity          the {@link HttpEntity} to wrap and compress; 
must not be {@code null}.
+     * @param contentEncoding the content encoding to use for compression, 
e.g., "gzip".
+     */
+    public CompressingEntity(final HttpEntity entity, final String 
contentEncoding) {
+        super(entity);
+        this.contentEncoding = Args.notNull(contentEncoding, "Content 
encoding");
+    }
+
+    /**
+     * Returns the content encoding used for compression.
+     *
+     * @return the content encoding (e.g., "gzip", "deflate").
+     */
+    @Override
+    public String getContentEncoding() {
+        return contentEncoding;
+    }
+
+    /**
+     * Returns whether the entity is chunked. This is determined by the 
wrapped entity.
+     *
+     * @return {@code true} if the entity is chunked, {@code false} otherwise.
+     */
+    @Override
+    public boolean isChunked() {
+        return super.isChunked();
+    }
+
+    /**
+     * This method is unsupported because the content is meant to be 
compressed during the
+     * {@link #writeTo(OutputStream)} operation.
+     *
+     * @throws UnsupportedOperationException always, as this method is not 
supported.
+     */
+    @Override
+    public InputStream getContent() throws IOException {
+        throw new UnsupportedOperationException("Reading content is not 
supported for CompressingEntity");
+    }
+
+    /**
+     * Writes the compressed content to the provided {@link OutputStream}. 
Compression is performed
+     * using the content encoding provided during entity construction.
+     *
+     * @param outStream the {@link OutputStream} to which the compressed 
content will be written; must not be {@code null}.
+     * @throws IOException                   if an I/O error occurs during 
compression or writing.
+     * @throws UnsupportedOperationException if the specified compression type 
is not supported.
+     */
+    @Override
+    public void writeTo(final OutputStream outStream) throws IOException {
+        Args.notNull(outStream, "Output stream");
+        // Get the compressor based on the specified content encoding
+        final OutputStream compressorStream;
+        try {
+            compressorStream = 
CompressingFactory.INSTANCE.getCompressorOutputStream(contentEncoding, 
outStream);
+        } catch (final CompressorException e) {
+            throw new IOException("Error initializing decompression stream", 
e);

Review Comment:
   Don't you mean "Error initializing compression stream"?



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingFactory.java:
##########
@@ -0,0 +1,277 @@
+/*
+ * ====================================================================
+ * 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.InputStream;
+import java.io.OutputStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import 
org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
+import org.apache.commons.compress.compressors.deflate.DeflateParameters;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.util.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory class for managing compression and decompression of HTTP entities 
using different compression formats.
+ * <p>
+ * This factory uses a cache to optimize access to available input and output 
stream providers for compression formats.
+ * It also allows the use of aliases (e.g., "gzip" and "x-gzip") and 
automatically formats the compression names
+ * to ensure consistency.
+ * </p>
+ *
+ * <p>
+ * Supported compression formats include gzip, deflate, and other available 
formats provided by the
+ * {@link CompressorStreamFactory}.
+ * </p>
+ *
+ * <p>
+ * This class is thread-safe and uses {@link AtomicReference} to cache the 
available input and output stream providers.
+ * </p>
+ *
+ * @since 5.5
+ */
+public class CompressingFactory {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CompressingFactory.class);
+    /**
+     * Singleton instance of the factory.
+     */
+    public static final CompressingFactory INSTANCE = new CompressingFactory();
+
+    private final CompressorStreamFactory compressorStreamFactory = new 
CompressorStreamFactory();
+    private final AtomicReference<Set<String>> inputProvidersCache = new 
AtomicReference<>();
+    private final AtomicReference<Set<String>> outputProvidersCache = new 
AtomicReference<>();
+    private final Map<String, String> formattedNameCache = new 
ConcurrentHashMap<>();
+
+    /**
+     * Returns a set of available input stream compression providers.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableInputProviders() {
+        return inputProvidersCache.updateAndGet(existing -> existing != null ? 
existing : fetchAvailableInputProviders());
+    }
+
+    /**
+     * Returns a set of available output stream compression providers.
+     *
+     * @return a set of available output stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableOutputProviders() {
+        return outputProvidersCache.updateAndGet(existing -> existing != null 
? existing : fetchAvailableOutputProviders());
+    }
+
+    /**
+     * Returns the formatted name of the provided compression format.
+     * <p>
+     * If the provided name matches an alias (e.g., "gzip" or "x-gzip"), the 
method will return the standard name.
+     * </p>
+     *
+     * @param name the compression format name.
+     * @return the formatted name, or the original name if no alias is found.
+     * @throws IllegalArgumentException if the name is null or empty.
+     */
+    public String getFormattedName(final String name) {
+        if (name == null || name.isEmpty()) {
+            LOG.warn("Compression name is null or empty");
+            return null;
+        }
+        final String lowerCaseName = name.toLowerCase(Locale.ROOT);
+        return formattedNameCache.computeIfAbsent(lowerCaseName, key -> {
+            if ("gzip".equals(key) || "x-gzip".equals(key)) {
+                return "gz";
+            } else if ("compress".equals(key)) {
+                return "z";
+            }
+            return key;
+        });
+    }
+
+    /**
+     * Creates an input stream for the specified compression format and 
decompresses the provided input stream.
+     * <p>
+     * This method uses the specified compression name to decompress the input 
stream and supports the "noWrap" option
+     * for deflate streams.
+     * </p>
+     *
+     * @param name        the compression format.
+     * @param inputStream the input stream to decompress.
+     * @param noWrap      if true, disables the zlib header and trailer for 
deflate streams.
+     * @return the decompressed input stream, or the original input stream if 
the format is not supported.
+     */
+    public InputStream getCompressorInputStream(final String name, final 
InputStream inputStream, final boolean noWrap) throws CompressorException {
+        Args.notNull(inputStream, "InputStream");
+        Args.notNull(name, "name");
+        final String formattedName = getFormattedName(name);
+        return isSupported(formattedName, false)
+                ? createCompressorInputStream(formattedName, inputStream, 
noWrap)
+                : inputStream;

Review Comment:
   The Javadoc and implementation don't match:
   - Javadoc states decompression is performed
   - The code doesn't decompress if the format isn't supported.
   
   Either the Javadoc needs updating, or, more likely, we should throw an 
exception indicating the operation is not supported for that format type name.
   
   I must say I find the API and Javadoc confusing, we are creating a 
"Compressor" input stream in order to "decompress" with a "compression" format! 
What?
   
   - Maybe calling the API `getDecompressorInputStream` would help because it 
"decompresses". I expect a "Compressor" to compress, not the other way around.
   - Maybe using the term "format type name" instead "compression format" would 
remove the need for the reader to constantly parse "compress" vs. "decompress" 
in the text. 
   - Maybe only using one term "decompress" or "compress" for one method 
(Javadoc and code) would alleviate the issue.



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingFactory.java:
##########
@@ -0,0 +1,277 @@
+/*
+ * ====================================================================
+ * 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.InputStream;
+import java.io.OutputStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import 
org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
+import org.apache.commons.compress.compressors.deflate.DeflateParameters;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.util.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory class for managing compression and decompression of HTTP entities 
using different compression formats.
+ * <p>
+ * This factory uses a cache to optimize access to available input and output 
stream providers for compression formats.
+ * It also allows the use of aliases (e.g., "gzip" and "x-gzip") and 
automatically formats the compression names
+ * to ensure consistency.
+ * </p>
+ *
+ * <p>
+ * Supported compression formats include gzip, deflate, and other available 
formats provided by the
+ * {@link CompressorStreamFactory}.
+ * </p>
+ *
+ * <p>
+ * This class is thread-safe and uses {@link AtomicReference} to cache the 
available input and output stream providers.
+ * </p>
+ *
+ * @since 5.5
+ */
+public class CompressingFactory {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CompressingFactory.class);
+    /**
+     * Singleton instance of the factory.
+     */
+    public static final CompressingFactory INSTANCE = new CompressingFactory();
+
+    private final CompressorStreamFactory compressorStreamFactory = new 
CompressorStreamFactory();
+    private final AtomicReference<Set<String>> inputProvidersCache = new 
AtomicReference<>();
+    private final AtomicReference<Set<String>> outputProvidersCache = new 
AtomicReference<>();
+    private final Map<String, String> formattedNameCache = new 
ConcurrentHashMap<>();
+
+    /**
+     * Returns a set of available input stream compression providers.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableInputProviders() {
+        return inputProvidersCache.updateAndGet(existing -> existing != null ? 
existing : fetchAvailableInputProviders());
+    }
+
+    /**
+     * Returns a set of available output stream compression providers.
+     *
+     * @return a set of available output stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableOutputProviders() {
+        return outputProvidersCache.updateAndGet(existing -> existing != null 
? existing : fetchAvailableOutputProviders());
+    }
+
+    /**
+     * Returns the formatted name of the provided compression format.
+     * <p>
+     * If the provided name matches an alias (e.g., "gzip" or "x-gzip"), the 
method will return the standard name.
+     * </p>
+     *
+     * @param name the compression format name.
+     * @return the formatted name, or the original name if no alias is found.
+     * @throws IllegalArgumentException if the name is null or empty.
+     */
+    public String getFormattedName(final String name) {
+        if (name == null || name.isEmpty()) {
+            LOG.warn("Compression name is null or empty");
+            return null;
+        }
+        final String lowerCaseName = name.toLowerCase(Locale.ROOT);
+        return formattedNameCache.computeIfAbsent(lowerCaseName, key -> {
+            if ("gzip".equals(key) || "x-gzip".equals(key)) {
+                return "gz";
+            } else if ("compress".equals(key)) {
+                return "z";
+            }
+            return key;
+        });
+    }
+
+    /**
+     * Creates an input stream for the specified compression format and 
decompresses the provided input stream.
+     * <p>
+     * This method uses the specified compression name to decompress the input 
stream and supports the "noWrap" option
+     * for deflate streams.
+     * </p>
+     *
+     * @param name        the compression format.
+     * @param inputStream the input stream to decompress.
+     * @param noWrap      if true, disables the zlib header and trailer for 
deflate streams.
+     * @return the decompressed input stream, or the original input stream if 
the format is not supported.
+     */
+    public InputStream getCompressorInputStream(final String name, final 
InputStream inputStream, final boolean noWrap) throws CompressorException {
+        Args.notNull(inputStream, "InputStream");
+        Args.notNull(name, "name");
+        final String formattedName = getFormattedName(name);
+        return isSupported(formattedName, false)
+                ? createCompressorInputStream(formattedName, inputStream, 
noWrap)
+                : inputStream;
+    }
+
+    /**
+     * Creates an output stream for the specified compression format and 
compresses the provided output stream.
+     *
+     * @param name         the compression format.
+     * @param outputStream the output stream to compress.
+     * @return the compressed output stream, or the original output stream if 
the format is not supported.
+     */
+    public OutputStream getCompressorOutputStream(final String name, final 
OutputStream outputStream) throws CompressorException {
+        final String formattedName = getFormattedName(name);
+        return isSupported(formattedName, true)
+                ? createCompressorOutputStream(formattedName, outputStream)
+                : outputStream;
+    }
+
+    /**
+     * Decompresses the provided HTTP entity using the specified compression 
format.
+     *
+     * @param entity          the HTTP entity to decompress.
+     * @param contentEncoding the compression format.
+     * @return a decompressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity decompressEntity(final HttpEntity entity, final String 
contentEncoding) {
+        return decompressEntity(entity, contentEncoding, false);
+    }
+
+    /**
+     * Decompresses the provided HTTP entity using the specified compression 
format with the option for deflate streams.
+     *
+     * @param entity          the HTTP entity to decompress.
+     * @param contentEncoding the compression format.
+     * @param noWrap          if true, disables the zlib header and trailer 
for deflate streams.
+     * @return a decompressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity decompressEntity(final HttpEntity entity, final String 
contentEncoding, final boolean noWrap) {
+        Args.notNull(entity, "Entity");
+        Args.notNull(contentEncoding, "Content Encoding");
+        if (!isSupported(contentEncoding, false)) {
+            LOG.warn("Unsupported decompression type: {}", contentEncoding);
+            return null;
+        }
+        return new DecompressingEntity(entity, contentEncoding, noWrap);
+    }
+
+    /**
+     * Compresses the provided HTTP entity using the specified compression 
format.
+     *
+     * @param entity          the HTTP entity to compress.
+     * @param contentEncoding the compression format.
+     * @return a compressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity compressEntity(final HttpEntity entity, final String 
contentEncoding) {
+        Args.notNull(entity, "Entity");
+        Args.notNull(contentEncoding, "Content Encoding");
+        if (!isSupported(contentEncoding, true)) {
+            LOG.warn("Unsupported compression type: {}", contentEncoding);
+            return null;
+        }
+        return new CompressingEntity(entity, contentEncoding);
+    }
+
+    /**
+     * Fetches the available input stream compression providers from Commons 
Compress.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    private Set<String> fetchAvailableInputProviders() {
+        final Set<String> inputNames = 
compressorStreamFactory.getInputStreamCompressorNames();
+        return inputNames.stream()
+                .map(String::toLowerCase)

Review Comment:
   This might have some odd results depending on the locale context 
(https://garygregory.wordpress.com/2015/11/03/java-lowercase-conversion-turkey/)
 if you do not use a Locale like the PR does above using ROOT.



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/DecompressingEntity.java:
##########
@@ -38,7 +38,9 @@
  * Common base class for decompressing {@link HttpEntity} implementations.
  *
  * @since 4.4
+ * @deprecated

Review Comment:
   The Javadoc tag is missing its comment to redirect reader.



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/DecompressingEntity.java:
##########
@@ -0,0 +1,149 @@
+/*
+ * ====================================================================
+ * 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.io.OutputStream;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.entity.HttpEntityWrapper;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * An {@link HttpEntity} wrapper that decompresses the content of the wrapped 
entity.
+ * This class supports different compression types and can handle both standard
+ * compression (e.g., gzip, deflate) and variations that require a custom 
handling (e.g., noWrap).
+ *
+ * <p>Decompression is performed using a {@link LazyDecompressingInputStream} 
that
+ * applies decompression lazily when content is requested.</p>
+ *
+ * <p>
+ * Note: This class uses lazy initialization for certain fields, making it 
<b>not thread-safe</b>.
+ * If multiple threads access an instance of this class concurrently, they 
must synchronize on the instance
+ * to ensure correct behavior.
+ * </p>
+ *
+ * @since 5.5
+ */
+@Contract(threading = ThreadingBehavior.UNSAFE)
+public class DecompressingEntity extends HttpEntityWrapper {
+
+    /**
+     * The content input stream, initialized lazily during the first read.
+     */
+    private InputStream content;
+
+    /**
+     * The compression type used for decompression (e.g., gzip, deflate).
+     */
+    private final String compressionType;
+
+    /**
+     * The flag indicating if decompression should skip certain headers 
(noWrap).
+     */
+    private final boolean noWrap;
+
+    /**
+     * Constructs a new {@link DecompressingEntity} with the specified 
compression type and noWrap setting.
+     *
+     * @param wrapped         the non-null {@link HttpEntity} to be wrapped.
+     * @param compressionType the compression type (e.g., "gzip", "deflate").
+     * @param noWrap          whether to decompress without headers for 
certain compression formats.
+     */
+    public DecompressingEntity(final HttpEntity wrapped, final String 
compressionType, final boolean noWrap) {
+        super(wrapped);
+        this.compressionType = compressionType;
+        this.noWrap = noWrap;
+    }
+
+    /**
+     * Constructs a new {@link DecompressingEntity} with the specified 
compression type, defaulting to no noWrap handling.
+     *
+     * @param wrapped         the non-null {@link HttpEntity} to be wrapped.
+     * @param compressionType the compression type (e.g., "gzip", "deflate").
+     */
+    public DecompressingEntity(final HttpEntity wrapped, final String 
compressionType) {
+        this(wrapped, compressionType, false);
+    }
+
+    /**
+     * Initializes and returns a stream for decompression.
+     * The decompression is applied lazily on the wrapped entity's content.
+     *
+     * @return a lazily initialized {@link InputStream} that decompresses the 
content.
+     * @throws IOException if an error occurs during decompression.
+     */
+    private InputStream getDecompressingStream() throws IOException {
+        return new LazyDecompressingInputStream(super.getContent(), 
compressionType, noWrap);
+    }
+
+    /**
+     * Returns the decompressed content stream. If the entity is streaming,
+     * the same {@link InputStream} is returned on subsequent calls.
+     *
+     * @return the decompressed {@link InputStream}.
+     * @throws IOException if an error occurs during decompression.
+     */
+    @Override
+    public InputStream getContent() throws IOException {

Review Comment:
   As I commented elsewhere in this PR, the thread safety or lack thereof needs 
to be documented if not addressed.



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingFactory.java:
##########
@@ -0,0 +1,277 @@
+/*
+ * ====================================================================
+ * 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.InputStream;
+import java.io.OutputStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import 
org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
+import org.apache.commons.compress.compressors.deflate.DeflateParameters;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.util.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory class for managing compression and decompression of HTTP entities 
using different compression formats.
+ * <p>
+ * This factory uses a cache to optimize access to available input and output 
stream providers for compression formats.
+ * It also allows the use of aliases (e.g., "gzip" and "x-gzip") and 
automatically formats the compression names
+ * to ensure consistency.
+ * </p>
+ *
+ * <p>
+ * Supported compression formats include gzip, deflate, and other available 
formats provided by the
+ * {@link CompressorStreamFactory}.
+ * </p>
+ *
+ * <p>
+ * This class is thread-safe and uses {@link AtomicReference} to cache the 
available input and output stream providers.
+ * </p>
+ *
+ * @since 5.5
+ */
+public class CompressingFactory {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CompressingFactory.class);
+    /**
+     * Singleton instance of the factory.
+     */
+    public static final CompressingFactory INSTANCE = new CompressingFactory();
+
+    private final CompressorStreamFactory compressorStreamFactory = new 
CompressorStreamFactory();
+    private final AtomicReference<Set<String>> inputProvidersCache = new 
AtomicReference<>();
+    private final AtomicReference<Set<String>> outputProvidersCache = new 
AtomicReference<>();
+    private final Map<String, String> formattedNameCache = new 
ConcurrentHashMap<>();
+
+    /**
+     * Returns a set of available input stream compression providers.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableInputProviders() {
+        return inputProvidersCache.updateAndGet(existing -> existing != null ? 
existing : fetchAvailableInputProviders());
+    }
+
+    /**
+     * Returns a set of available output stream compression providers.
+     *
+     * @return a set of available output stream compression providers in 
lowercase.
+     */
+    public Set<String> getAvailableOutputProviders() {
+        return outputProvidersCache.updateAndGet(existing -> existing != null 
? existing : fetchAvailableOutputProviders());
+    }
+
+    /**
+     * Returns the formatted name of the provided compression format.
+     * <p>
+     * If the provided name matches an alias (e.g., "gzip" or "x-gzip"), the 
method will return the standard name.
+     * </p>
+     *
+     * @param name the compression format name.
+     * @return the formatted name, or the original name if no alias is found.
+     * @throws IllegalArgumentException if the name is null or empty.
+     */
+    public String getFormattedName(final String name) {
+        if (name == null || name.isEmpty()) {
+            LOG.warn("Compression name is null or empty");
+            return null;
+        }
+        final String lowerCaseName = name.toLowerCase(Locale.ROOT);
+        return formattedNameCache.computeIfAbsent(lowerCaseName, key -> {
+            if ("gzip".equals(key) || "x-gzip".equals(key)) {
+                return "gz";
+            } else if ("compress".equals(key)) {
+                return "z";
+            }
+            return key;
+        });
+    }
+
+    /**
+     * Creates an input stream for the specified compression format and 
decompresses the provided input stream.
+     * <p>
+     * This method uses the specified compression name to decompress the input 
stream and supports the "noWrap" option
+     * for deflate streams.
+     * </p>
+     *
+     * @param name        the compression format.
+     * @param inputStream the input stream to decompress.
+     * @param noWrap      if true, disables the zlib header and trailer for 
deflate streams.
+     * @return the decompressed input stream, or the original input stream if 
the format is not supported.
+     */
+    public InputStream getCompressorInputStream(final String name, final 
InputStream inputStream, final boolean noWrap) throws CompressorException {
+        Args.notNull(inputStream, "InputStream");
+        Args.notNull(name, "name");
+        final String formattedName = getFormattedName(name);
+        return isSupported(formattedName, false)
+                ? createCompressorInputStream(formattedName, inputStream, 
noWrap)
+                : inputStream;
+    }
+
+    /**
+     * Creates an output stream for the specified compression format and 
compresses the provided output stream.
+     *
+     * @param name         the compression format.
+     * @param outputStream the output stream to compress.
+     * @return the compressed output stream, or the original output stream if 
the format is not supported.
+     */
+    public OutputStream getCompressorOutputStream(final String name, final 
OutputStream outputStream) throws CompressorException {
+        final String formattedName = getFormattedName(name);
+        return isSupported(formattedName, true)
+                ? createCompressorOutputStream(formattedName, outputStream)
+                : outputStream;
+    }
+
+    /**
+     * Decompresses the provided HTTP entity using the specified compression 
format.
+     *
+     * @param entity          the HTTP entity to decompress.
+     * @param contentEncoding the compression format.
+     * @return a decompressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity decompressEntity(final HttpEntity entity, final String 
contentEncoding) {
+        return decompressEntity(entity, contentEncoding, false);
+    }
+
+    /**
+     * Decompresses the provided HTTP entity using the specified compression 
format with the option for deflate streams.
+     *
+     * @param entity          the HTTP entity to decompress.
+     * @param contentEncoding the compression format.
+     * @param noWrap          if true, disables the zlib header and trailer 
for deflate streams.
+     * @return a decompressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity decompressEntity(final HttpEntity entity, final String 
contentEncoding, final boolean noWrap) {
+        Args.notNull(entity, "Entity");
+        Args.notNull(contentEncoding, "Content Encoding");
+        if (!isSupported(contentEncoding, false)) {
+            LOG.warn("Unsupported decompression type: {}", contentEncoding);
+            return null;
+        }
+        return new DecompressingEntity(entity, contentEncoding, noWrap);
+    }
+
+    /**
+     * Compresses the provided HTTP entity using the specified compression 
format.
+     *
+     * @param entity          the HTTP entity to compress.
+     * @param contentEncoding the compression format.
+     * @return a compressed {@link HttpEntity}, or {@code null} if the 
compression format is unsupported.
+     */
+    public HttpEntity compressEntity(final HttpEntity entity, final String 
contentEncoding) {
+        Args.notNull(entity, "Entity");
+        Args.notNull(contentEncoding, "Content Encoding");
+        if (!isSupported(contentEncoding, true)) {
+            LOG.warn("Unsupported compression type: {}", contentEncoding);
+            return null;
+        }
+        return new CompressingEntity(entity, contentEncoding);
+    }
+
+    /**
+     * Fetches the available input stream compression providers from Commons 
Compress.
+     *
+     * @return a set of available input stream compression providers in 
lowercase.
+     */
+    private Set<String> fetchAvailableInputProviders() {
+        final Set<String> inputNames = 
compressorStreamFactory.getInputStreamCompressorNames();
+        return inputNames.stream()
+                .map(String::toLowerCase)
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Fetches the available output stream compression providers from Commons 
Compress.
+     *
+     * @return a set of available output stream compression providers in 
lowercase.
+     */
+    private Set<String> fetchAvailableOutputProviders() {
+        final Set<String> outputNames = 
compressorStreamFactory.getOutputStreamCompressorNames();
+        return outputNames.stream()
+                .map(String::toLowerCase)

Review Comment:
   See above.



##########
httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingEntity.java:
##########
@@ -0,0 +1,127 @@
+/*
+ * ====================================================================
+ * 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.io.OutputStream;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.entity.HttpEntityWrapper;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * An {@link HttpEntity} wrapper that applies compression to the content 
before writing it to
+ * an output stream. This class supports various compression algorithms based 
on the
+ * specified content encoding.
+ *
+ * <p>Compression is performed using {@link CompressingFactory}, which returns 
a corresponding
+ * {@link OutputStream} for the requested compression type. This class does 
not support
+ * reading the content directly through {@link #getContent()} as the content 
is always compressed
+ * during write operations.</p>
+ *
+ * @since 5.5
+ */
+public class CompressingEntity extends HttpEntityWrapper {
+
+    /**
+     * The content encoding type, e.g., "gzip", "deflate", etc.
+     */
+    private final String contentEncoding;
+
+    /**
+     * Creates a new {@link CompressingEntity} that compresses the wrapped 
entity's content
+     * using the specified content encoding.
+     *
+     * @param entity          the {@link HttpEntity} to wrap and compress; 
must not be {@code null}.
+     * @param contentEncoding the content encoding to use for compression, 
e.g., "gzip".
+     */
+    public CompressingEntity(final HttpEntity entity, final String 
contentEncoding) {
+        super(entity);
+        this.contentEncoding = Args.notNull(contentEncoding, "Content 
encoding");
+    }
+
+    /**
+     * Returns the content encoding used for compression.
+     *
+     * @return the content encoding (e.g., "gzip", "deflate").
+     */
+    @Override
+    public String getContentEncoding() {
+        return contentEncoding;
+    }
+
+    /**
+     * Returns whether the entity is chunked. This is determined by the 
wrapped entity.
+     *
+     * @return {@code true} if the entity is chunked, {@code false} otherwise.
+     */
+    @Override
+    public boolean isChunked() {
+        return super.isChunked();
+    }
+
+    /**
+     * This method is unsupported because the content is meant to be 
compressed during the
+     * {@link #writeTo(OutputStream)} operation.
+     *
+     * @throws UnsupportedOperationException always, as this method is not 
supported.
+     */
+    @Override
+    public InputStream getContent() throws IOException {
+        throw new UnsupportedOperationException("Reading content is not 
supported for CompressingEntity");
+    }
+
+    /**
+     * Writes the compressed content to the provided {@link OutputStream}. 
Compression is performed
+     * using the content encoding provided during entity construction.
+     *
+     * @param outStream the {@link OutputStream} to which the compressed 
content will be written; must not be {@code null}.
+     * @throws IOException                   if an I/O error occurs during 
compression or writing.
+     * @throws UnsupportedOperationException if the specified compression type 
is not supported.
+     */
+    @Override
+    public void writeTo(final OutputStream outStream) throws IOException {
+        Args.notNull(outStream, "Output stream");
+        // Get the compressor based on the specified content encoding
+        final OutputStream compressorStream;
+        try {
+            compressorStream = 
CompressingFactory.INSTANCE.getCompressorOutputStream(contentEncoding, 
outStream);
+        } catch (final CompressorException e) {
+            throw new IOException("Error initializing decompression stream", 
e);
+        }
+        if (compressorStream != null) {
+            // Write compressed data
+            super.writeTo(compressorStream);
+            // Close the compressor stream after writing
+            compressorStream.close();

Review Comment:
   Could this method be rewritten to use a try-with-resources block?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscr...@hc.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@hc.apache.org
For additional commands, e-mail: dev-h...@hc.apache.org

Reply via email to