This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
     new 9fed0d19f8 Add an HTTP configuration setting, noCompressionEncodings
9fed0d19f8 is described below

commit 9fed0d19f812bb781872a812dec0e780c6a31e17
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Feb 6 18:55:30 2026 +0000

    Add an HTTP configuration setting, noCompressionEncodings
    
    This can be used to control which content encodings will not be
    compressed when compression is enabled.
    Based on pull request #914 by Long9725.
---
 java/org/apache/coyote/CompressionConfig.java      | 34 +++++++++-
 .../coyote/http11/AbstractHttp11Protocol.java      |  9 +++
 test/org/apache/coyote/TestCompressionConfig.java  | 77 ++++++++++++++--------
 .../apache/coyote/http11/TestHttp11Processor.java  | 30 +++++++--
 webapps/docs/changelog.xml                         |  6 ++
 webapps/docs/config/http.xml                       | 12 ++++
 6 files changed, 129 insertions(+), 39 deletions(-)

diff --git a/java/org/apache/coyote/CompressionConfig.java 
b/java/org/apache/coyote/CompressionConfig.java
index 4d2c8f410a..5d8d3133ef 100644
--- a/java/org/apache/coyote/CompressionConfig.java
+++ b/java/org/apache/coyote/CompressionConfig.java
@@ -19,6 +19,7 @@ package org.apache.coyote;
 import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
@@ -48,6 +49,35 @@ public class CompressionConfig {
     private String[] compressibleMimeTypes = null;
     private int compressionMinSize = 2048;
     private boolean noCompressionStrongETag = true;
+    private Set<String> noCompressionEncodings =
+            new HashSet<>(Arrays.asList("br", "compress", "dcb", "dcz", 
"deflate", "gzip", "pack200-gzip", "zstd"));
+
+
+    public String getNoCompressionEncodings() {
+        return String.join(",", noCompressionEncodings);
+    }
+
+
+    /**
+     * Set the list of content encodings that indicate already-compressed 
content.
+     * When content is already encoded with one of these encodings, 
compression will not be applied
+     * to prevent double compression.
+     *
+     * @param encodings Comma-separated list of encoding names (e.g., 
"gzip,br.dflate")
+     */
+    public void setNoCompressionEncodings(String encodings) {
+        Set<String> newEncodings = new HashSet<>();
+        if (encodings != null && !encodings.isEmpty()) {
+            StringTokenizer tokens = new StringTokenizer(encodings, ",");
+            while (tokens.hasMoreTokens()) {
+                String token = tokens.nextToken().trim();
+                if(!token.isEmpty()) {
+                    newEncodings.add(token);
+                }
+            }
+        }
+        this.noCompressionEncodings = newEncodings;
+    }
 
 
     /**
@@ -242,9 +272,7 @@ public class CompressionConfig {
             if (tokens.contains("identity")) {
                 // If identity, do not do content modifications
                 useContentEncoding = false;
-            } else if (tokens.contains("br") || tokens.contains("compress") || 
tokens.contains("dcb") ||
-                    tokens.contains("dcz") || tokens.contains("deflate") || 
tokens.contains("gzip") ||
-                    tokens.contains("pack200-gzip") || 
tokens.contains("zstd")) {
+            } else if 
(noCompressionEncodings.stream().anyMatch(tokens::contains)) {
                 // Content should not be compressed twice
                 return false;
             }
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java 
b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
index 1f1c210525..e02257f614 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
@@ -427,6 +427,15 @@ public abstract class AbstractHttp11Protocol<S> extends 
AbstractProtocol<S> {
     }
 
 
+    public String getNoCompressionEncodings() {
+        return compressionConfig.getNoCompressionEncodings();
+    }
+
+    public void setNoCompressionEncodings(String encodings) {
+        compressionConfig.setNoCompressionEncodings(encodings);
+    }
+
+
     @Deprecated
     public boolean getNoCompressionStrongETag() {
         return compressionConfig.getNoCompressionStrongETag();
diff --git a/test/org/apache/coyote/TestCompressionConfig.java 
b/test/org/apache/coyote/TestCompressionConfig.java
index 86a6f702d9..9f807abb13 100644
--- a/test/org/apache/coyote/TestCompressionConfig.java
+++ b/test/org/apache/coyote/TestCompressionConfig.java
@@ -17,6 +17,7 @@
 package org.apache.coyote;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
@@ -33,35 +34,39 @@ public class TestCompressionConfig {
     public static Collection<Object[]> parameters() {
         List<Object[]> parameterSets = new ArrayList<>();
 
-        parameterSets.add(new Object[] { new String[] {  },              null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "xgzip" },       null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "<>gzip" },      null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "<>", "gzip" },  null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
-
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.TRUE,  Boolean.FALSE, Boolean.FALSE });
-
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.FALSE, Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.FALSE, Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.FALSE, Boolean.TRUE,  Boolean.FALSE });
-
-        parameterSets.add(new Object[] { new String[] {  },              null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "xgzip" },       null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "<>gzip" },      null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "<>", "gzip" },  null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
-
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.TRUE,  Boolean.FALSE, Boolean.TRUE });
-
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.FALSE, Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.FALSE, Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.FALSE, Boolean.TRUE,  Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] {}, null, Boolean.TRUE, 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "xgzip" }, null, 
Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, 
Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+        parameterSets
+                .add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+        parameterSets
+                .add(new Object[] { new String[] { "<>", "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", 
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", 
Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.FALSE, Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", 
Boolean.FALSE, Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", 
Boolean.FALSE, Boolean.TRUE, Boolean.FALSE });
+
+        parameterSets.add(new Object[] { new String[] {}, null, Boolean.TRUE, 
Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "xgzip" }, null, 
Boolean.TRUE, Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, 
Boolean.TRUE, Boolean.FALSE, Boolean.TRUE });
+        parameterSets
+                .add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE, Boolean.TRUE });
+        parameterSets
+                .add(new Object[] { new String[] { "<>", "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE, Boolean.TRUE });
+
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", 
Boolean.TRUE, Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", 
Boolean.TRUE, Boolean.FALSE, Boolean.TRUE });
+
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.FALSE, Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", 
Boolean.FALSE, Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", 
Boolean.FALSE, Boolean.TRUE, Boolean.TRUE });
 
         return parameterSets;
     }
@@ -120,4 +125,18 @@ public class TestCompressionConfig {
             }
         }
     }
+
+
+    @Test
+    public void testNoCompressionEncodings() {
+        CompressionConfig config = new CompressionConfig();
+        String encodings = config.getNoCompressionEncodings();
+        Assert.assertTrue(Arrays.asList("br", "compress", "dcb", "dcz", 
"deflate", "gzip", "pack200-gzip", "zstd")
+                .stream().anyMatch(encodings::contains));
+
+        config.setNoCompressionEncodings("br");
+        String newEncodings = config.getNoCompressionEncodings();
+        Assert.assertTrue(newEncodings.contains("br"));
+        Assert.assertFalse(newEncodings.contains("gzip"));
+    }
 }
diff --git a/test/org/apache/coyote/http11/TestHttp11Processor.java 
b/test/org/apache/coyote/http11/TestHttp11Processor.java
index 3547eb0c3f..dadfacb275 100644
--- a/test/org/apache/coyote/http11/TestHttp11Processor.java
+++ b/test/org/apache/coyote/http11/TestHttp11Processor.java
@@ -32,6 +32,7 @@ import java.net.SocketAddress;
 import java.nio.CharBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -421,7 +422,7 @@ public class TestHttp11Processor extends TomcatBaseTest {
         tomcat.start();
 
         ByteChunk responseBody = new ByteChunk();
-        Map<String, List<String>> responseHeaders = new HashMap<>();
+        Map<String,List<String>> responseHeaders = new HashMap<>();
         int rc = getUrl("http://localhost:"; + getPort() + "/test", 
responseBody, responseHeaders);
 
         Assert.assertEquals(HttpServletResponse.SC_OK, rc);
@@ -445,7 +446,7 @@ public class TestHttp11Processor extends TomcatBaseTest {
         tomcat.start();
 
         ByteChunk responseBody = new ByteChunk();
-        Map<String, List<String>> responseHeaders = new HashMap<>();
+        Map<String,List<String>> responseHeaders = new HashMap<>();
         int rc = getUrl("http://localhost:"; + getPort() + "/test", 
responseBody, responseHeaders);
 
         Assert.assertEquals(HttpServletResponse.SC_OK, rc);
@@ -854,11 +855,11 @@ public class TestHttp11Processor extends TomcatBaseTest {
         tomcat.start();
 
         ByteChunk getBody = new ByteChunk();
-        Map<String, List<String>> getHeaders = new HashMap<>();
+        Map<String,List<String>> getHeaders = new HashMap<>();
         int getStatus = getUrl("http://localhost:"; + getPort() + "/test", 
getBody, getHeaders);
 
         ByteChunk headBody = new ByteChunk();
-        Map<String, List<String>> headHeaders = new HashMap<>();
+        Map<String,List<String>> headHeaders = new HashMap<>();
         int headStatus = getUrl("http://localhost:"; + getPort() + "/test", 
headBody, headHeaders);
 
         Assert.assertEquals(HttpServletResponse.SC_OK, getStatus);
@@ -997,7 +998,7 @@ public class TestHttp11Processor extends TomcatBaseTest {
         tomcat.start();
 
         ByteChunk responseBody = new ByteChunk();
-        Map<String, List<String>> responseHeaders = new HashMap<>();
+        Map<String,List<String>> responseHeaders = new HashMap<>();
         int rc = getUrl("http://localhost:"; + getPort() + "/test", 
responseBody, responseHeaders);
 
         Assert.assertEquals(HttpServletResponse.SC_RESET_CONTENT, rc);
@@ -2149,7 +2150,6 @@ public class TestHttp11Processor extends TomcatBaseTest {
     }
 
 
-
     private static class EarlyHintsServlet extends HttpServlet {
 
         private static final long serialVersionUID = 1L;
@@ -2165,6 +2165,7 @@ public class TestHttp11Processor extends TomcatBaseTest {
             this.useSendError = useSendError;
             this.errorString = errorString;
         }
+
         @Override
         protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
             resp.addHeader("Link", "</style.css>; rel=preload; as=style");
@@ -2185,4 +2186,19 @@ public class TestHttp11Processor extends TomcatBaseTest {
             resp.getWriter().write("OK");
         }
     }
-}
+
+
+    @Test
+    public void testNoCompressionEncodings() {
+        Http11NioProtocol protocol = new Http11NioProtocol();
+        String encodings = protocol.getNoCompressionEncodings();
+        Assert.assertTrue(Arrays.asList("br", "compress", "dcb", "dcz", 
"deflate", "gzip", "pack200-gzip", "zstd")
+                .stream().anyMatch(encodings::contains));
+
+        protocol.setNoCompressionEncodings("br");
+
+        String newEncodings = protocol.getNoCompressionEncodings();
+        Assert.assertTrue(newEncodings.contains("br"));
+        Assert.assertFalse(newEncodings.contains("gzip"));
+    }
+}
\ No newline at end of file
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 1fdfe5daaf..48b8a26931 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -111,6 +111,12 @@
         <bug>69938</bug>: Avoid changing the closed state of TLS channel when
         resetting it after close. (remm)
       </fix>
+      <add>
+        Add an HTTP configuration setting, <code>noCompressionEncodings</code>,
+        that can be used to control which content encodings will not be
+        compressed when compression is enabled. Based on pull request
+        <pr>914</pr> by Long9725. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Jasper">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index d7d5a02360..c8a2093d7c 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -682,6 +682,18 @@
       used.</p>
     </attribute>
 
+    <attribute name="noCompressionEncodings" requried="false">
+      <p>A comma-separated list of content encodings that indicate
+      already-compressed content. When the response already has a
+      <code>Content-Encoding</code> header with one of these values, 
compression
+      will not be applied to prevent double compression. This attribute is only
+      used if <strong>compression</strong> is set to <code>on</code> or
+      <code>force</code>.</p>
+      <p>If not specified, the default values is
+      <code>br,compress,dcb,dcz,deflate,gzip,pack2000-gzip,zstd</code>, which
+      includes all commonly used compression algorithms.</p>
+    </attribute>
+
     <attribute name="noCompressionStrongETag" required="false">
       <p>This flag configures whether resources with a strong ETag will be
       considered for compression. If <code>true</code>, resources with a strong


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to