Merge pull request #12269 from smaldini:addNettyCompressionOptions

* pr/12269:
  Polish
  Add Netty Compression support
pull/12284/head
Brian Clozel 7 years ago
commit 52b40ee4ad

@ -163,7 +163,7 @@ content into your application. Rather, pick only the properties that you need.
server.compression.enabled=false # Whether response compression is enabled.
server.compression.excluded-user-agents= # List of user-agents to exclude from compression.
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript # Comma-separated list of MIME types that should be compressed.
server.compression.min-response-size=2048 # Minimum response size that is required for compression to be performed.
server.compression.min-response-size=2048 # Minimum "Content-Length" value that is required for compression to be performed.
server.connection-timeout= # Time that connectors wait for another HTTP request before closing the connection. When not set, the connector's container-specific default is used. Use a value of -1 to indicate no (that is, an infinite) timeout.
server.error.include-exception=false # Include the "exception" attribute.
server.error.include-stacktrace=never # When to include a "stacktrace" attribute.

@ -0,0 +1,115 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* 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 org.springframework.boot.web.embedded.netty;
import java.util.function.BiPredicate;
import io.netty.handler.codec.http.HttpHeaderNames;
import reactor.ipc.netty.http.server.HttpServerOptions;
import reactor.ipc.netty.http.server.HttpServerRequest;
import reactor.ipc.netty.http.server.HttpServerResponse;
import org.springframework.boot.web.server.Compression;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* Configure the HTTP compression on an Reactor Netty request/response handler.
*
* @author Stephane Maldini
*/
final class CompressionCustomizer implements NettyServerCustomizer {
private final Compression compression;
CompressionCustomizer(Compression compression) {
this.compression = compression;
}
@Override
public void customize(HttpServerOptions.Builder builder) {
if (this.compression.getMinResponseSize() >= 0) {
builder.compression(this.compression.getMinResponseSize());
}
BiPredicate<HttpServerRequest, HttpServerResponse> compressPredicate = null;
if (this.compression.getMimeTypes() != null &&
this.compression.getMimeTypes().length > 0) {
compressPredicate = new CompressibleMimeTypePredicate(this.compression.getMimeTypes());
}
if (this.compression.getExcludedUserAgents() != null &&
this.compression.getExcludedUserAgents().length > 0) {
BiPredicate<HttpServerRequest, HttpServerResponse> agentCompressPredicate =
new CompressibleAgentPredicate(this.compression.getExcludedUserAgents());
compressPredicate = compressPredicate == null ?
agentCompressPredicate :
compressPredicate.and(agentCompressPredicate);
}
if (compressPredicate != null) {
builder.compression(compressPredicate);
}
}
private static class CompressibleAgentPredicate
implements BiPredicate<HttpServerRequest, HttpServerResponse> {
private final String[] excludedAgents;
CompressibleAgentPredicate(String[] excludedAgents) {
this.excludedAgents = new String[excludedAgents.length];
System.arraycopy(excludedAgents, 0, this.excludedAgents, 0, excludedAgents.length);
}
@Override
public boolean test(HttpServerRequest request, HttpServerResponse response) {
for (String excludedAgent : this.excludedAgents) {
if (request.requestHeaders()
.contains(HttpHeaderNames.USER_AGENT, excludedAgent, true)) {
return false;
}
}
return true;
}
}
private static class CompressibleMimeTypePredicate
implements BiPredicate<HttpServerRequest, HttpServerResponse> {
private final MimeType[] mimeTypes;
CompressibleMimeTypePredicate(String[] mimeTypes) {
this.mimeTypes = new MimeType[mimeTypes.length];
for (int i = 0; i < mimeTypes.length; i++) {
this.mimeTypes[i] = MimeTypeUtils.parseMimeType(mimeTypes[i]);
}
}
@Override
public boolean test(HttpServerRequest request, HttpServerResponse response) {
String contentType = response.responseHeaders()
.get(HttpHeaderNames.CONTENT_TYPE);
if (contentType != null) {
for (MimeType mimeType : this.mimeTypes) {
if (mimeType.isCompatibleWith(MimeTypeUtils.parseMimeType(contentType))) {
return true;
}
}
}
return false;
}
}
}

@ -106,8 +106,10 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
getSsl(), getSslStoreProvider());
sslServerCustomizer.customize(options);
}
if (getCompression() != null) {
options.compression(getCompression().getEnabled());
if (getCompression() != null && getCompression().getEnabled()) {
CompressionCustomizer compressionCustomizer = new CompressionCustomizer(
getCompression());
compressionCustomizer.customize(options);
}
applyCustomizers(options);
}).build();

@ -43,7 +43,7 @@ public class Compression {
private String[] excludedUserAgents = null;
/**
* Minimum response size that is required for compression to be performed.
* Minimum "Content-Length" value that is required for compression to be performed.
*/
private int minResponseSize = 2048;

@ -46,7 +46,6 @@ import reactor.ipc.netty.http.client.HttpClientOptions;
import reactor.test.StepVerifier;
import org.springframework.boot.testsupport.rule.OutputCapture;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerFactory;
import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.Ssl;
@ -258,7 +257,7 @@ public abstract class AbstractReactiveWebServerFactoryTests {
}
@Test
public void compressionOfResponseToGetRequest() throws Exception {
public void compressionOfResponseToGetRequest() {
WebClient client = prepareCompressionTest();
ResponseEntity<Void> response = client.get().exchange()
.flatMap((res) -> res.toEntity(Void.class)).block();
@ -266,7 +265,7 @@ public abstract class AbstractReactiveWebServerFactoryTests {
}
@Test
public void compressionOfResponseToPostRequest() throws Exception {
public void compressionOfResponseToPostRequest() {
WebClient client = prepareCompressionTest();
ResponseEntity<Void> response = client.post().exchange()
.flatMap((res) -> res.toEntity(Void.class)).block();
@ -274,11 +273,10 @@ public abstract class AbstractReactiveWebServerFactoryTests {
}
@Test
public void noCompressionForMimeType() throws Exception {
Assumptions.assumeThat(getFactory())
.isNotInstanceOf(NettyReactiveWebServerFactory.class);
public void noCompressionForSmallResponse() {
Compression compression = new Compression();
compression.setMimeTypes(new String[] { "application/json" });
compression.setEnabled(true);
compression.setMinResponseSize(3001);
WebClient client = prepareCompressionTest(compression);
ResponseEntity<Void> response = client.get().exchange()
.flatMap((res) -> res.toEntity(Void.class)).block();
@ -286,12 +284,20 @@ public abstract class AbstractReactiveWebServerFactoryTests {
}
@Test
public void noCompressionForUserAgent() throws Exception {
Assumptions.assumeThat(getFactory())
.isNotInstanceOf(NettyReactiveWebServerFactory.class);
public void noCompressionForMimeType() {
Compression compression = new Compression();
compression.setMimeTypes(new String[] {"application/json"});
WebClient client = prepareCompressionTest(compression);
ResponseEntity<Void> response = client.get().exchange()
.flatMap((res) -> res.toEntity(Void.class)).block();
assertResponseIsNotCompressed(response);
}
@Test
public void noCompressionForUserAgent() {
Compression compression = new Compression();
compression.setEnabled(true);
compression.setExcludedUserAgents(new String[] { "testUserAgent" });
compression.setExcludedUserAgents(new String[] {"testUserAgent"});
WebClient client = prepareCompressionTest(compression);
ResponseEntity<Void> response = client.get().header("User-Agent", "testUserAgent")
.exchange().flatMap((res) -> res.toEntity(Void.class)).block();
@ -342,7 +348,7 @@ public abstract class AbstractReactiveWebServerFactoryTests {
extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpResponse) {
HttpResponse response = (HttpResponse) msg;
boolean compressed = response.headers()
@ -375,6 +381,7 @@ public abstract class AbstractReactiveWebServerFactoryTests {
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
response.setStatusCode(HttpStatus.OK);
response.getHeaders().setContentType(this.mediaType);
response.getHeaders().setContentLength(this.bytes.readableByteCount());
return response.writeWith(Mono.just(this.bytes));
}

Loading…
Cancel
Save