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