Skip to content

Commit 7a1a2cd

Browse files
committed
ARTEMIS-5833 support compression for HTTP responses
1 parent ed18b53 commit 7a1a2cd

File tree

6 files changed

+108
-2
lines changed

6 files changed

+108
-2
lines changed

artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ public class WebServerDTO extends ComponentDTO {
113113
@XmlAttribute
114114
public Integer maxResponseHeaderSize;
115115

116+
@XmlAttribute
117+
public Boolean compressionEnabled;
118+
119+
@XmlAttribute
120+
public Integer compressionLevel;
121+
116122
public String getPath() {
117123
return path;
118124
}

artemis-web/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@
102102
<groupId>org.eclipse.jetty</groupId>
103103
<artifactId>jetty-alpn-java-server</artifactId>
104104
</dependency>
105+
<dependency>
106+
<groupId>org.eclipse.jetty.compression</groupId>
107+
<artifactId>jetty-compression-server</artifactId>
108+
</dependency>
109+
<!--dependency>
110+
<groupId>org.eclipse.jetty.compression</groupId>
111+
<artifactId>jetty-compression-brotli</artifactId>
112+
</dependency-->
113+
<dependency>
114+
<groupId>org.eclipse.jetty.compression</groupId>
115+
<artifactId>jetty-compression-gzip</artifactId>
116+
</dependency>
105117
<dependency>
106118
<groupId>org.apache.artemis</groupId>
107119
<artifactId>artemis-server</artifactId>

artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
import org.apache.activemq.artemis.utils.PemConfigUtil;
5858
import org.apache.activemq.artemis.utils.sm.SecurityManagerShim;
5959
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
60+
import org.eclipse.jetty.compression.Compression;
61+
import org.eclipse.jetty.compression.EncoderConfig;
62+
import org.eclipse.jetty.compression.gzip.GzipCompression;
63+
import org.eclipse.jetty.compression.gzip.GzipEncoderConfig;
64+
import org.eclipse.jetty.compression.server.CompressionHandler;
6065
import org.eclipse.jetty.ee9.security.DefaultAuthenticatorFactory;
6166
import org.eclipse.jetty.ee9.servlet.FilterHolder;
6267
import org.eclipse.jetty.ee9.webapp.WebAppContext;
@@ -149,6 +154,21 @@ public synchronized void start() throws Exception {
149154
Scheduler scheduler = new ScheduledExecutorScheduler("activemq-web-scheduled", false);
150155
server = new Server(threadPool, scheduler, null);
151156
handlers = new Handler.Sequence();
157+
if (webServerConfig.compressionEnabled != null && webServerConfig.compressionEnabled) {
158+
int compressionLevel = Objects.requireNonNullElse(webServerConfig.compressionLevel, 6);
159+
logger.debug("embedded web server is using GZIP compression level {}", compressionLevel);
160+
EncoderConfig encoderConfig = new GzipEncoderConfig();
161+
encoderConfig.setCompressionLevel(compressionLevel);
162+
Compression compression = new GzipCompression();
163+
compression.setDefaultEncoderConfig(encoderConfig);
164+
CompressionHandler compressionHandler = new CompressionHandler();
165+
compressionHandler.putCompression(compression);
166+
compressionHandler.setHandler(handlers);
167+
server.setHandler(compressionHandler);
168+
} else {
169+
server.setHandler(handlers);
170+
}
171+
152172

153173
HttpConfiguration httpConfiguration = new HttpConfiguration();
154174

@@ -253,8 +273,6 @@ public void requestDestroyed(ServletRequestEvent sre) {
253273

254274
handlers.addHandler(defaultHandler); // this should be last
255275

256-
server.setHandler(handlers);
257-
258276
server.start();
259277

260278
printStatus(bindings);

artemis-web/src/test/java/org/apache/activemq/cli/test/WebServerComponentTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import io.netty.handler.codec.http.HttpClientCodec;
6666
import io.netty.handler.codec.http.HttpContent;
6767
import io.netty.handler.codec.http.HttpHeaderNames;
68+
import io.netty.handler.codec.http.HttpHeaders;
6869
import io.netty.handler.codec.http.HttpMethod;
6970
import io.netty.handler.codec.http.HttpObject;
7071
import io.netty.handler.codec.http.HttpRequest;
@@ -202,6 +203,63 @@ private void internalSimpleServer(boolean useCustomizer) throws Exception {
202203
assertFalse(webServerComponent.isStarted());
203204
}
204205

206+
@Test
207+
public void testCompressionEnabled() throws Exception {
208+
testCompression(true);
209+
}
210+
211+
@Test
212+
public void testCompressionDisabled() throws Exception {
213+
testCompression(false);
214+
}
215+
216+
private void testCompression(boolean compressionEnabled) throws Exception {
217+
final String encoding = "gzip";
218+
219+
BindingDTO bindingDTO = new BindingDTO();
220+
bindingDTO.uri = "http://localhost:0";
221+
WebServerDTO webServerDTO = new WebServerDTO();
222+
webServerDTO.setBindings(Collections.singletonList(bindingDTO));
223+
webServerDTO.path = "webapps";
224+
webServerDTO.webContentEnabled = true;
225+
webServerDTO.compressionEnabled = compressionEnabled;
226+
webServerDTO.compressionLevel = 999;
227+
WebServerComponent webServerComponent = new WebServerComponent();
228+
assertFalse(webServerComponent.isStarted());
229+
webServerComponent.configure(webServerDTO, "src/test/resources/", "src/test/resources/");
230+
testedComponents.add(webServerComponent);
231+
webServerComponent.start();
232+
final int port = webServerComponent.getPort();
233+
// Make the connection attempt.
234+
CountDownLatch latch = new CountDownLatch(1);
235+
final ClientHandler clientHandler = new ClientHandler(latch);
236+
Channel ch = getChannel(port, clientHandler);
237+
238+
// this file is different from the other tests because it has to be above a certain size in order to be compressed
239+
URI uri = new URI("http://localhost/WebServerComponentCompressionTest.txt");
240+
// Prepare the HTTP request.
241+
HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath());
242+
request.headers().set(HttpHeaderNames.HOST, "localhost");
243+
request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, encoding);
244+
245+
// Send the HTTP request.
246+
ch.writeAndFlush(request);
247+
assertTrue(latch.await(5, TimeUnit.SECONDS));
248+
String contentEncoding = clientHandler.headers.get(HttpHeaderNames.CONTENT_ENCODING);
249+
if (compressionEnabled) {
250+
assertEquals(encoding, contentEncoding);
251+
} else {
252+
assertNull(contentEncoding);
253+
}
254+
255+
// Wait for the server to close the connection.
256+
ch.close();
257+
ch.eventLoop().shutdownNow();
258+
assertTrue(webServerComponent.isStarted());
259+
webServerComponent.stop(true);
260+
assertFalse(webServerComponent.isStarted());
261+
}
262+
205263
@Test
206264
public void testThreadPool() throws Exception {
207265
BindingDTO bindingDTO = new BindingDTO();
@@ -1133,6 +1191,7 @@ class ClientHandler extends SimpleChannelInboundHandler<HttpObject> {
11331191
private CountDownLatch latch;
11341192
private StringBuilder body = new StringBuilder();
11351193
private String serverHeader;
1194+
private HttpHeaders headers;
11361195

11371196
ClientHandler(CountDownLatch latch) {
11381197
this.latch = latch;
@@ -1141,6 +1200,7 @@ class ClientHandler extends SimpleChannelInboundHandler<HttpObject> {
11411200
@Override
11421201
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
11431202
if (msg instanceof HttpResponse response) {
1203+
headers = response.headers();
11441204
serverHeader = response.headers().get("Server");
11451205
} else if (msg instanceof HttpContent content) {
11461206
body.append(content.content().toString(CharsetUtil.UTF_8));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0123456789012345678901234567890123456789

docs/user-manual/web-server.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ The location to redirect the requests with the root target.
3434
webContentEnabled::
3535
Whether or not the content included in the web folder of the home and the instance directories is accessible.
3636
Default is `false`.
37+
compressionEnabled::
38+
Whether to compress HTTP responses.
39+
Uses `gzip` encoding for maximum compatibility.
40+
This will impact any client communicating with the embedded web server including the web console and consumers of xref:metrics.adoc#metrics[metrics] (e.g. Prometheus) assuming they set the `Accept-Encoding` header to `gzip` in their HTTP requests.
41+
Default is `false`.
42+
compressionLevel::
43+
The level of compression for HTTP responses.
44+
Only valid if `compressionEnabled` is `true`.
45+
Default is `6`. Must be between `0` and `9` inclusive.
3746
maxThreads::
3847
The maximum number of threads the embedded web server can create to service HTTP requests.
3948
Default is `200`.

0 commit comments

Comments
 (0)