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

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


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

commit c828eeced84b0e3db7f0f945d7dc9672105cafb2
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  | 54 ++++++++++++++--------
 .../apache/coyote/http11/TestHttp11Processor.java  | 30 +++++++++---
 webapps/docs/changelog.xml                         | 10 ++++
 webapps/docs/config/http.xml                       | 12 +++++
 6 files changed, 120 insertions(+), 29 deletions(-)

diff --git a/java/org/apache/coyote/CompressionConfig.java 
b/java/org/apache/coyote/CompressionConfig.java
index abe3a0dce6..32827d8243 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;
@@ -47,6 +48,35 @@ public class CompressionConfig {
             
"text/javascript,application/javascript,application/json,application/xml";
     private String[] compressibleMimeTypes = null;
     private int compressionMinSize = 2048;
+    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;
+    }
 
 
     /**
@@ -215,9 +245,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 02ac4e589e..ba599cd5b4 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
@@ -402,6 +402,15 @@ public abstract class AbstractHttp11Protocol<S> extends 
AbstractProtocol<S> {
     }
 
 
+    public String getNoCompressionEncodings() {
+        return compressionConfig.getNoCompressionEncodings();
+    }
+
+    public void setNoCompressionEncodings(String encodings) {
+        compressionConfig.setNoCompressionEncodings(encodings);
+    }
+
+
     public boolean useCompression(Request request, Response response) {
         return compressionConfig.useCompression(request, response);
     }
diff --git a/test/org/apache/coyote/TestCompressionConfig.java 
b/test/org/apache/coyote/TestCompressionConfig.java
index ae16c02f8f..be336ec7e6 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,29 +34,30 @@ public class TestCompressionConfig {
     public static Collection<Object[]> parameters() {
         List<Object[]> parameterSets = new ArrayList<>();
 
-        parameterSets.add(new Object[] { new String[] {  },              null, 
Boolean.FALSE, Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "xgzip" },       null, 
Boolean.FALSE, Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "<>gzip" },      null, 
Boolean.FALSE, Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "<>", "gzip" },  null, 
Boolean.TRUE,  Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] {}, null, Boolean.FALSE, 
Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "xgzip" }, null, 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, null, 
Boolean.TRUE, Boolean.FALSE });
 
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", 
Boolean.FALSE, Boolean.FALSE });
 
-        parameterSets.add(new Object[] { new String[] {  },              null, 
Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "xgzip" },       null, 
Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "<>gzip" },      null, 
Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "<>", "gzip" },  null, 
Boolean.TRUE,  Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] {}, null, Boolean.FALSE, 
Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "xgzip" }, null, 
Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, 
Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE });
 
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", 
Boolean.TRUE, Boolean.TRUE });
 
-        parameterSets.add(new Object[] { new String[] { "foobar;foo=bar, 
gzip;bla=\"quoted\"" }, "XX", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "foobar;foo=bar, 
gzip;bla=\"quoted\"" }, "XX", Boolean.TRUE,
+                Boolean.TRUE });
 
         return parameterSets;
     }
@@ -110,4 +112,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 a8faab3f5b..31a3b3f69f 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 57dd59ed8d..4ad4eacf28 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -105,6 +105,16 @@
   issues do not "pop up" wrt. others).
 -->
 <section name="Tomcat 10.1.53 (schultz)" rtext="in development">
+  <subsection name="Coyote">
+    <changelog>
+      <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">
     <changelog>
       <fix>
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index dcbc076034..b43915c4dc 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -674,6 +674,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="noCompressionUserAgents" required="false">
       <p>The value is a regular expression (using <code>java.util.regex</code>)
       matching the <code>user-agent</code> header of HTTP clients for which


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

Reply via email to