diff --git a/components/proxy/pom.xml b/components/proxy/pom.xml
index c0d9462f8..78cf7ecbc 100644
--- a/components/proxy/pom.xml
+++ b/components/proxy/pom.xml
@@ -57,6 +57,31 @@
linux-aarch_64
+
+ com.aayushatharva.brotli4j
+ brotli4j
+
+
+
+ com.aayushatharva.brotli4j
+ native-linux-x86_64
+
+
+
+ com.aayushatharva.brotli4j
+ native-linux-aarch64
+
+
+
+ com.aayushatharva.brotli4j
+ native-osx-x86_64
+
+
+
+ com.aayushatharva.brotli4j
+ native-osx-aarch64
+
+
com.google.guava
guava
diff --git a/components/proxy/src/main/java/com/hotels/styx/proxy/HttpCompressor.java b/components/proxy/src/main/java/com/hotels/styx/proxy/HttpCompressor.java
index b81b25d89..8549b3b9d 100644
--- a/components/proxy/src/main/java/com/hotels/styx/proxy/HttpCompressor.java
+++ b/components/proxy/src/main/java/com/hotels/styx/proxy/HttpCompressor.java
@@ -15,6 +15,7 @@
*/
package com.hotels.styx.proxy;
+import io.netty.handler.codec.compression.CompressionOptions;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponse;
@@ -53,6 +54,14 @@ public class HttpCompressor extends HttpContentCompressor {
"application/json");
+ public HttpCompressor() {
+ this(0);
+ }
+
+ public HttpCompressor(int contentSizeThreshold) {
+ super(contentSizeThreshold, (CompressionOptions[]) null);
+ }
+
private boolean shouldCompress(String contentType) {
return ENCODING_TYPES.contains(contentType != null ? contentType.toLowerCase() : "");
}
diff --git a/components/proxy/src/test/java/com/hotels/styx/proxy/BrotliTest.java b/components/proxy/src/test/java/com/hotels/styx/proxy/BrotliTest.java
new file mode 100644
index 000000000..db305257a
--- /dev/null
+++ b/components/proxy/src/test/java/com/hotels/styx/proxy/BrotliTest.java
@@ -0,0 +1,18 @@
+package com.hotels.styx.proxy;
+
+import io.netty.handler.codec.compression.Brotli;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+public class BrotliTest {
+ @Test
+ @EnabledOnOs(value = { OS.LINUX, OS.MAC })
+ public void brotliCompressionIsAvailable() throws Throwable {
+ Brotli.ensureAvailability();
+ assertTrue(Brotli.isAvailable());
+ }
+}
diff --git a/components/proxy/src/test/java/com/hotels/styx/proxy/HttpCompressorTest.java b/components/proxy/src/test/java/com/hotels/styx/proxy/HttpCompressorTest.java
new file mode 100644
index 000000000..c51b7941c
--- /dev/null
+++ b/components/proxy/src/test/java/com/hotels/styx/proxy/HttpCompressorTest.java
@@ -0,0 +1,82 @@
+/*
+ Copyright (C) 2013-2024 Expedia Inc.
+
+ Licensed 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.
+ */
+package com.hotels.styx.proxy;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.HttpContentEncoder;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+
+
+public class HttpCompressorTest {
+ private HttpCompressor compressor;
+ private ChannelHandlerContext ctx;
+
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ compressor = new HttpCompressor();
+ ctx = mock(ChannelHandlerContext.class, RETURNS_DEEP_STUBS);
+ compressor.handlerAdded(ctx);
+ }
+
+
+ @ParameterizedTest
+ @MethodSource("responseEncodingParameters")
+ public void validateResponseEncoding(final String acceptEncoding, final String contentType, final String expectedEncoding) throws Exception {
+ HttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
+ httpResponse.headers().add("Content-Type", contentType);
+ HttpContentEncoder.Result result = compressor.beginEncode(httpResponse, acceptEncoding);
+ if (expectedEncoding == null) {
+ assertNull(result);
+ } else {
+ assertEquals(result.targetContentEncoding(), expectedEncoding);
+ assertNotNull(result.contentEncoder());
+ }
+ }
+
+ private static Stream responseEncodingParameters() {
+ // Note: "br" is brotli compression
+ return Stream.of(
+ Arguments.of("br", "application/json", "br"),
+ Arguments.of("br", "image/jpeg", null),
+ Arguments.of("gzip", "application/json", "gzip"),
+ Arguments.of("gzip", "image/jpeg", null),
+ Arguments.of("br, gzip", "application/json", "br"),
+ Arguments.of("gzip, br", "application/json", "br"),
+ Arguments.of("", "application/json", null),
+ Arguments.of("", "image/jpeg", null),
+ Arguments.of("br, garbage", "application/json", "br"),
+ Arguments.of("gzip, garbage", "application/json", "gzip"),
+ Arguments.of("garbage", "application/json", null),
+ Arguments.of("?", "application/json", null),
+ Arguments.of(",", "application/json", null)
+ );
+ }
+}
diff --git a/pom.xml b/pom.xml
index 0bb6e35ec..06a8fb385 100644
--- a/pom.xml
+++ b/pom.xml
@@ -110,6 +110,7 @@
3.1.12
1.12.3
4.1.107.Final
+ 1.16.0
4.12.0
3.0.5
1.0.4
@@ -291,6 +292,36 @@
${guava.version}
+
+ com.aayushatharva.brotli4j
+ brotli4j
+ ${brotli4j.version}
+
+
+
+ com.aayushatharva.brotli4j
+ native-linux-x86_64
+ ${brotli4j.version}
+
+
+
+ com.aayushatharva.brotli4j
+ native-linux-aarch64
+ ${brotli4j.version}
+
+
+
+ com.aayushatharva.brotli4j
+ native-osx-x86_64
+ ${brotli4j.version}
+
+
+
+ com.aayushatharva.brotli4j
+ native-osx-aarch64
+ ${brotli4j.version}
+
+
net.bytebuddy
byte-buddy