Skip to content

Commit a265ce5

Browse files
committed
feature(proxy): support brotli compression
1 parent 01036ea commit a265ce5

File tree

5 files changed

+167
-0
lines changed

5 files changed

+167
-0
lines changed

components/proxy/pom.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@
5757
<classifier>linux-aarch_64</classifier>
5858
</dependency>
5959

60+
<dependency>
61+
<groupId>com.aayushatharva.brotli4j</groupId>
62+
<artifactId>brotli4j</artifactId>
63+
</dependency>
64+
65+
<dependency>
66+
<groupId>com.aayushatharva.brotli4j</groupId>
67+
<artifactId>native-linux-x86_64</artifactId>
68+
</dependency>
69+
70+
<dependency>
71+
<groupId>com.aayushatharva.brotli4j</groupId>
72+
<artifactId>native-linux-aarch64</artifactId>
73+
</dependency>
74+
75+
<dependency>
76+
<groupId>com.aayushatharva.brotli4j</groupId>
77+
<artifactId>native-osx-x86_64</artifactId>
78+
</dependency>
79+
80+
<dependency>
81+
<groupId>com.aayushatharva.brotli4j</groupId>
82+
<artifactId>native-osx-aarch64</artifactId>
83+
</dependency>
84+
6085
<dependency>
6186
<groupId>com.google.guava</groupId>
6287
<artifactId>guava</artifactId>

components/proxy/src/main/java/com/hotels/styx/proxy/HttpCompressor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.hotels.styx.proxy;
1717

18+
import io.netty.handler.codec.compression.CompressionOptions;
1819
import io.netty.handler.codec.http.HttpContentCompressor;
1920
import io.netty.handler.codec.http.HttpHeaderNames;
2021
import io.netty.handler.codec.http.HttpResponse;
@@ -53,6 +54,14 @@ public class HttpCompressor extends HttpContentCompressor {
5354
"application/json");
5455

5556

57+
public HttpCompressor() {
58+
this(0);
59+
}
60+
61+
public HttpCompressor(int contentSizeThreshold) {
62+
super(contentSizeThreshold, (CompressionOptions[]) null);
63+
}
64+
5665
private boolean shouldCompress(String contentType) {
5766
return ENCODING_TYPES.contains(contentType != null ? contentType.toLowerCase() : "");
5867
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.hotels.styx.proxy;
2+
3+
import io.netty.handler.codec.compression.Brotli;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.condition.EnabledOnOs;
6+
import org.junit.jupiter.api.condition.OS;
7+
8+
import static org.junit.jupiter.api.Assertions.assertTrue;
9+
10+
11+
public class BrotliTest {
12+
@Test
13+
@EnabledOnOs(value = { OS.LINUX, OS.MAC })
14+
public void brotliCompressionIsAvailable() throws Throwable {
15+
Brotli.ensureAvailability();
16+
assertTrue(Brotli.isAvailable());
17+
}
18+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
Copyright (C) 2013-2024 Expedia Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package com.hotels.styx.proxy;
17+
18+
import io.netty.channel.ChannelHandlerContext;
19+
import io.netty.handler.codec.http.DefaultFullHttpResponse;
20+
import io.netty.handler.codec.http.HttpContentEncoder;
21+
import io.netty.handler.codec.http.HttpResponse;
22+
import io.netty.handler.codec.http.HttpResponseStatus;
23+
import io.netty.handler.codec.http.HttpVersion;
24+
import java.util.stream.Stream;
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.Arguments;
28+
import org.junit.jupiter.params.provider.MethodSource;
29+
30+
import static org.junit.jupiter.api.Assertions.assertEquals;
31+
import static org.junit.jupiter.api.Assertions.assertNotNull;
32+
import static org.junit.jupiter.api.Assertions.assertNull;
33+
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
34+
import static org.mockito.Mockito.mock;
35+
36+
37+
public class HttpCompressorTest {
38+
private static final String COMPRESSIBLE_MIME_TYPE = "application/json";
39+
private static final String NOT_COMPRESSIBLE_MIME_TYPE = "image/jpeg";
40+
private HttpCompressor compressor;
41+
private ChannelHandlerContext ctx;
42+
43+
44+
@BeforeEach
45+
public void setUp() throws Exception {
46+
compressor = new HttpCompressor();
47+
ctx = mock(ChannelHandlerContext.class, RETURNS_DEEP_STUBS);
48+
compressor.handlerAdded(ctx);
49+
}
50+
51+
52+
@ParameterizedTest
53+
@MethodSource("responseEncodingParameters")
54+
public void validateResponseEncoding(final String acceptEncoding, final String contentType, final String expectedEncoding) throws Exception {
55+
HttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
56+
httpResponse.headers().add("Content-Type", contentType);
57+
HttpContentEncoder.Result result = compressor.beginEncode(httpResponse, acceptEncoding);
58+
if (expectedEncoding == null) {
59+
assertNull(result);
60+
} else {
61+
assertEquals(result.targetContentEncoding(), expectedEncoding);
62+
assertNotNull(result.contentEncoder());
63+
}
64+
}
65+
66+
private static Stream<Arguments> responseEncodingParameters() {
67+
// Note: "br" is brotli compression
68+
return Stream.of(
69+
Arguments.of("br", COMPRESSIBLE_MIME_TYPE, "br"),
70+
Arguments.of("br", NOT_COMPRESSIBLE_MIME_TYPE, null),
71+
Arguments.of("gzip", COMPRESSIBLE_MIME_TYPE, "gzip"),
72+
Arguments.of("gzip", NOT_COMPRESSIBLE_MIME_TYPE, null),
73+
Arguments.of("br, gzip", COMPRESSIBLE_MIME_TYPE, "br"),
74+
Arguments.of("gzip, br", COMPRESSIBLE_MIME_TYPE, "br"),
75+
Arguments.of("", COMPRESSIBLE_MIME_TYPE, null),
76+
Arguments.of("", NOT_COMPRESSIBLE_MIME_TYPE, null),
77+
Arguments.of("br, garbage", COMPRESSIBLE_MIME_TYPE, "br"),
78+
Arguments.of("gzip, garbage", COMPRESSIBLE_MIME_TYPE, "gzip"),
79+
Arguments.of("garbage", COMPRESSIBLE_MIME_TYPE, null),
80+
Arguments.of("?", COMPRESSIBLE_MIME_TYPE, null),
81+
Arguments.of(",", COMPRESSIBLE_MIME_TYPE, null)
82+
);
83+
}
84+
}

pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
<janino.version>3.1.12</janino.version>
111111
<micrometer.version>1.12.3</micrometer.version>
112112
<netty.version>4.1.107.Final</netty.version>
113+
<brotli4j.version>1.16.0</brotli4j.version>
113114
<okhttp.version>4.12.0</okhttp.version>
114115
<pcollections.version>3.0.5</pcollections.version>
115116
<reactive-streams.version>1.0.4</reactive-streams.version>
@@ -291,6 +292,36 @@
291292
<version>${guava.version}</version>
292293
</dependency>
293294

295+
<dependency>
296+
<groupId>com.aayushatharva.brotli4j</groupId>
297+
<artifactId>brotli4j</artifactId>
298+
<version>${brotli4j.version}</version>
299+
</dependency>
300+
301+
<dependency>
302+
<groupId>com.aayushatharva.brotli4j</groupId>
303+
<artifactId>native-linux-x86_64</artifactId>
304+
<version>${brotli4j.version}</version>
305+
</dependency>
306+
307+
<dependency>
308+
<groupId>com.aayushatharva.brotli4j</groupId>
309+
<artifactId>native-linux-aarch64</artifactId>
310+
<version>${brotli4j.version}</version>
311+
</dependency>
312+
313+
<dependency>
314+
<groupId>com.aayushatharva.brotli4j</groupId>
315+
<artifactId>native-osx-x86_64</artifactId>
316+
<version>${brotli4j.version}</version>
317+
</dependency>
318+
319+
<dependency>
320+
<groupId>com.aayushatharva.brotli4j</groupId>
321+
<artifactId>native-osx-aarch64</artifactId>
322+
<version>${brotli4j.version}</version>
323+
</dependency>
324+
294325
<dependency>
295326
<groupId>net.bytebuddy</groupId>
296327
<artifactId>byte-buddy</artifactId>

0 commit comments

Comments
 (0)