Skip to content

Commit 638759b

Browse files
committed
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.
1 parent 93f5c41 commit 638759b

File tree

6 files changed

+116
-29
lines changed

6 files changed

+116
-29
lines changed

java/org/apache/coyote/CompressionConfig.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.io.StringReader;
2121
import java.util.ArrayList;
22+
import java.util.Arrays;
2223
import java.util.Enumeration;
2324
import java.util.HashSet;
2425
import java.util.List;
@@ -47,6 +48,35 @@ public class CompressionConfig {
4748
"text/javascript,application/javascript,application/json,application/xml";
4849
private String[] compressibleMimeTypes = null;
4950
private int compressionMinSize = 2048;
51+
private Set<String> noCompressionEncodings =
52+
new HashSet<>(Arrays.asList("br", "compress", "dcb", "dcz", "deflate", "gzip", "pack200-gzip", "zstd"));
53+
54+
55+
public String getNoCompressionEncodings() {
56+
return String.join(",", noCompressionEncodings);
57+
}
58+
59+
60+
/**
61+
* Set the list of content encodings that indicate already-compressed content.
62+
* When content is already encoded with one of these encodings, compression will not be applied
63+
* to prevent double compression.
64+
*
65+
* @param encodings Comma-separated list of encoding names (e.g., "gzip,br.dflate")
66+
*/
67+
public void setNoCompressionEncodings(String encodings) {
68+
Set<String> newEncodings = new HashSet<>();
69+
if (encodings != null && !encodings.isEmpty()) {
70+
StringTokenizer tokens = new StringTokenizer(encodings, ",");
71+
while (tokens.hasMoreTokens()) {
72+
String token = tokens.nextToken().trim();
73+
if(!token.isEmpty()) {
74+
newEncodings.add(token);
75+
}
76+
}
77+
}
78+
this.noCompressionEncodings = newEncodings;
79+
}
5080

5181

5282
/**
@@ -210,9 +240,7 @@ public boolean useCompression(Request request, Response response) {
210240
if (tokens.contains("identity")) {
211241
// If identity, do not do content modifications
212242
useContentEncoding = false;
213-
} else if (tokens.contains("br") || tokens.contains("compress") || tokens.contains("dcb") ||
214-
tokens.contains("dcz") || tokens.contains("deflate") || tokens.contains("gzip") ||
215-
tokens.contains("pack200-gzip") || tokens.contains("zstd")) {
243+
} else if (noCompressionEncodings.stream().anyMatch(tokens::contains)) {
216244
// Content should not be compressed twice
217245
return false;
218246
}

java/org/apache/coyote/http11/AbstractHttp11Protocol.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,15 @@ public void setCompressionMinSize(int compressionMinSize) {
340340
}
341341

342342

343+
public String getNoCompressionEncodings() {
344+
return compressionConfig.getNoCompressionEncodings();
345+
}
346+
347+
public void setNoCompressionEncodings(String encodings) {
348+
compressionConfig.setNoCompressionEncodings(encodings);
349+
}
350+
351+
343352
public boolean useCompression(Request request, Response response) {
344353
return compressionConfig.useCompression(request, response);
345354
}

test/org/apache/coyote/TestCompressionConfig.java

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.coyote;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.List;
2223

@@ -33,29 +34,30 @@ public class TestCompressionConfig {
3334
public static Collection<Object[]> parameters() {
3435
List<Object[]> parameterSets = new ArrayList<>();
3536

36-
parameterSets.add(new Object[] { new String[] { }, null, Boolean.FALSE, Boolean.FALSE });
37-
parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.FALSE });
38-
parameterSets.add(new Object[] { new String[] { "xgzip" }, null, Boolean.FALSE, Boolean.FALSE });
39-
parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, Boolean.FALSE, Boolean.FALSE });
40-
parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, Boolean.TRUE, Boolean.FALSE });
41-
parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, null, Boolean.TRUE, Boolean.FALSE });
37+
parameterSets.add(new Object[] { new String[] {}, null, Boolean.FALSE, Boolean.FALSE });
38+
parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.FALSE });
39+
parameterSets.add(new Object[] { new String[] { "xgzip" }, null, Boolean.FALSE, Boolean.FALSE });
40+
parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, Boolean.FALSE, Boolean.FALSE });
41+
parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, Boolean.TRUE, Boolean.FALSE });
42+
parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, null, Boolean.TRUE, Boolean.FALSE });
4243

43-
parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.FALSE });
44-
parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", Boolean.TRUE, Boolean.FALSE });
45-
parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", Boolean.FALSE, Boolean.FALSE });
44+
parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.FALSE });
45+
parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", Boolean.TRUE, Boolean.FALSE });
46+
parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", Boolean.FALSE, Boolean.FALSE });
4647

47-
parameterSets.add(new Object[] { new String[] { }, null, Boolean.FALSE, Boolean.TRUE });
48-
parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.TRUE });
49-
parameterSets.add(new Object[] { new String[] { "xgzip" }, null, Boolean.FALSE, Boolean.TRUE });
50-
parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, Boolean.FALSE, Boolean.TRUE });
51-
parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, Boolean.TRUE, Boolean.TRUE });
52-
parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, null, Boolean.TRUE, Boolean.TRUE });
48+
parameterSets.add(new Object[] { new String[] {}, null, Boolean.FALSE, Boolean.TRUE });
49+
parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.TRUE });
50+
parameterSets.add(new Object[] { new String[] { "xgzip" }, null, Boolean.FALSE, Boolean.TRUE });
51+
parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, Boolean.FALSE, Boolean.TRUE });
52+
parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, Boolean.TRUE, Boolean.TRUE });
53+
parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, null, Boolean.TRUE, Boolean.TRUE });
5354

54-
parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.TRUE });
55-
parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", Boolean.TRUE, Boolean.TRUE });
56-
parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", Boolean.TRUE, Boolean.TRUE });
55+
parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.TRUE });
56+
parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", Boolean.TRUE, Boolean.TRUE });
57+
parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", Boolean.TRUE, Boolean.TRUE });
5758

58-
parameterSets.add(new Object[] { new String[] { "foobar;foo=bar, gzip;bla=\"quoted\"" }, "XX", Boolean.TRUE, Boolean.TRUE });
59+
parameterSets.add(new Object[] { new String[] { "foobar;foo=bar, gzip;bla=\"quoted\"" }, "XX", Boolean.TRUE,
60+
Boolean.TRUE });
5961

6062
return parameterSets;
6163
}
@@ -110,4 +112,18 @@ public void testUseCompression() throws Exception {
110112
}
111113
}
112114
}
115+
116+
117+
@Test
118+
public void testNoCompressionEncodings() {
119+
CompressionConfig config = new CompressionConfig();
120+
String encodings = config.getNoCompressionEncodings();
121+
Assert.assertTrue(Arrays.asList("br", "compress", "dcb", "dcz", "deflate", "gzip", "pack200-gzip", "zstd")
122+
.stream().anyMatch(encodings::contains));
123+
124+
config.setNoCompressionEncodings("br");
125+
String newEncodings = config.getNoCompressionEncodings();
126+
Assert.assertTrue(newEncodings.contains("br"));
127+
Assert.assertFalse(newEncodings.contains("gzip"));
128+
}
113129
}

test/org/apache/coyote/http11/TestHttp11Processor.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.nio.CharBuffer;
3333
import java.nio.charset.StandardCharsets;
3434
import java.util.ArrayList;
35+
import java.util.Arrays;
3536
import java.util.HashMap;
3637
import java.util.List;
3738
import java.util.Map;
@@ -421,7 +422,7 @@ public void testChunking11NoContentLength() throws Exception {
421422
tomcat.start();
422423

423424
ByteChunk responseBody = new ByteChunk();
424-
Map<String, List<String>> responseHeaders = new HashMap<>();
425+
Map<String,List<String>> responseHeaders = new HashMap<>();
425426
int rc = getUrl("http://localhost:" + getPort() + "/test", responseBody, responseHeaders);
426427

427428
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
@@ -445,7 +446,7 @@ public void testNoChunking11NoContentLengthConnectionClose() throws Exception {
445446
tomcat.start();
446447

447448
ByteChunk responseBody = new ByteChunk();
448-
Map<String, List<String>> responseHeaders = new HashMap<>();
449+
Map<String,List<String>> responseHeaders = new HashMap<>();
449450
int rc = getUrl("http://localhost:" + getPort() + "/test", responseBody, responseHeaders);
450451

451452
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
@@ -854,11 +855,11 @@ public void testBug59310() throws Exception {
854855
tomcat.start();
855856

856857
ByteChunk getBody = new ByteChunk();
857-
Map<String, List<String>> getHeaders = new HashMap<>();
858+
Map<String,List<String>> getHeaders = new HashMap<>();
858859
int getStatus = getUrl("http://localhost:" + getPort() + "/test", getBody, getHeaders);
859860

860861
ByteChunk headBody = new ByteChunk();
861-
Map<String, List<String>> headHeaders = new HashMap<>();
862+
Map<String,List<String>> headHeaders = new HashMap<>();
862863
int headStatus = getUrl("http://localhost:" + getPort() + "/test", headBody, headHeaders);
863864

864865
Assert.assertEquals(HttpServletResponse.SC_OK, getStatus);
@@ -997,7 +998,7 @@ public void testBug61086() throws Exception {
997998
tomcat.start();
998999

9991000
ByteChunk responseBody = new ByteChunk();
1000-
Map<String, List<String>> responseHeaders = new HashMap<>();
1001+
Map<String,List<String>> responseHeaders = new HashMap<>();
10011002
int rc = getUrl("http://localhost:" + getPort() + "/test", responseBody, responseHeaders);
10021003

10031004
Assert.assertEquals(HttpServletResponse.SC_RESET_CONTENT, rc);
@@ -2149,7 +2150,6 @@ public void testEarlyHintsSendErrorWithMessage() throws Exception {
21492150
}
21502151

21512152

2152-
21532153
private static class EarlyHintsServlet extends HttpServlet {
21542154

21552155
private static final long serialVersionUID = 1L;
@@ -2165,6 +2165,7 @@ private static class EarlyHintsServlet extends HttpServlet {
21652165
this.useSendError = useSendError;
21662166
this.errorString = errorString;
21672167
}
2168+
21682169
@Override
21692170
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
21702171
resp.addHeader("Link", "</style.css>; rel=preload; as=style");
@@ -2185,4 +2186,19 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
21852186
resp.getWriter().write("OK");
21862187
}
21872188
}
2188-
}
2189+
2190+
2191+
@Test
2192+
public void testNoCompressionEncodings() {
2193+
Http11NioProtocol protocol = new Http11NioProtocol();
2194+
String encodings = protocol.getNoCompressionEncodings();
2195+
Assert.assertTrue(Arrays.asList("br", "compress", "dcb", "dcz", "deflate", "gzip", "pack200-gzip", "zstd")
2196+
.stream().anyMatch(encodings::contains));
2197+
2198+
protocol.setNoCompressionEncodings("br");
2199+
2200+
String newEncodings = protocol.getNoCompressionEncodings();
2201+
Assert.assertTrue(newEncodings.contains("br"));
2202+
Assert.assertFalse(newEncodings.contains("gzip"));
2203+
}
2204+
}

webapps/docs/changelog.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@
227227
<bug>69938</bug>: Avoid changing the closed state of TLS channel when
228228
resetting it after close. (remm)
229229
</fix>
230+
<add>
231+
Add an HTTP configuration setting, <code>noCompressionEncodings</code>,
232+
that can be used to control which content encodings will not be
233+
compressed when compression is enabled. Based on pull request
234+
<pr>914</pr> by Long9725. (markt)
235+
</add>
230236
</changelog>
231237
</subsection>
232238
<subsection name="Jasper">

webapps/docs/config/http.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,18 @@
651651
used.</p>
652652
</attribute>
653653

654+
<attribute name="noCompressionEncodings" requried="false">
655+
<p>A comma-separated list of content encodings that indicate
656+
already-compressed content. When the response already has a
657+
<code>Content-Encoding</code> header with one of these values, compression
658+
will not be applied to prevent double compression. This attribute is only
659+
used if <strong>compression</strong> is set to <code>on</code> or
660+
<code>force</code>.</p>
661+
<p>If not specified, the default values is
662+
<code>br,compress,dcb,dcz,deflate,gzip,pack2000-gzip,zstd</code>, which
663+
includes all commonly used compression algorithms.</p>
664+
</attribute>
665+
654666
<attribute name="noCompressionUserAgents" required="false">
655667
<p>The value is a regular expression (using <code>java.util.regex</code>)
656668
matching the <code>user-agent</code> header of HTTP clients for which

0 commit comments

Comments
 (0)