|
|
|
@ -16,9 +16,11 @@
|
|
|
|
|
|
|
|
|
|
package org.springframework.boot.web.embedded.netty;
|
|
|
|
|
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.function.BiPredicate;
|
|
|
|
|
|
|
|
|
|
import io.netty.handler.codec.http.HttpHeaderNames;
|
|
|
|
|
import io.netty.handler.codec.http.HttpHeaders;
|
|
|
|
|
import reactor.ipc.netty.http.server.HttpServerOptions;
|
|
|
|
|
import reactor.ipc.netty.http.server.HttpServerRequest;
|
|
|
|
|
import reactor.ipc.netty.http.server.HttpServerResponse;
|
|
|
|
@ -26,11 +28,14 @@ import reactor.ipc.netty.http.server.HttpServerResponse;
|
|
|
|
|
import org.springframework.boot.web.server.Compression;
|
|
|
|
|
import org.springframework.util.MimeType;
|
|
|
|
|
import org.springframework.util.MimeTypeUtils;
|
|
|
|
|
import org.springframework.util.ObjectUtils;
|
|
|
|
|
import org.springframework.util.StringUtils;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Configure the HTTP compression on an Reactor Netty request/response handler.
|
|
|
|
|
*
|
|
|
|
|
* @author Stephane Maldini
|
|
|
|
|
* @author Phillip Webb
|
|
|
|
|
*/
|
|
|
|
|
final class CompressionCustomizer implements NettyServerCustomizer {
|
|
|
|
|
|
|
|
|
@ -45,71 +50,46 @@ final class CompressionCustomizer implements NettyServerCustomizer {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
CompressionPredicate mimeTypes = getMimeTypesPredicate(
|
|
|
|
|
this.compression.getMimeTypes());
|
|
|
|
|
CompressionPredicate excludedUserAgents = getExcludedUserAgentsPredicate(
|
|
|
|
|
this.compression.getExcludedUserAgents());
|
|
|
|
|
builder.compression(mimeTypes.and(excludedUserAgents));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
private CompressionPredicate getMimeTypesPredicate(String[] mimeTypes) {
|
|
|
|
|
if (ObjectUtils.isEmpty(mimeTypes)) {
|
|
|
|
|
return CompressionPredicate.ALWAYS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean test(HttpServerRequest request, HttpServerResponse response) {
|
|
|
|
|
for (String excludedAgent : this.excludedAgents) {
|
|
|
|
|
if (request.requestHeaders()
|
|
|
|
|
.contains(HttpHeaderNames.USER_AGENT, excludedAgent, true)) {
|
|
|
|
|
return (request, response) -> {
|
|
|
|
|
String contentType = response.responseHeaders()
|
|
|
|
|
.get(HttpHeaderNames.CONTENT_TYPE);
|
|
|
|
|
if (StringUtils.isEmpty(contentType)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
MimeType contentMimeType = MimeTypeUtils.parseMimeType(contentType);
|
|
|
|
|
return Arrays.stream(mimeTypes).map(MimeTypeUtils::parseMimeType)
|
|
|
|
|
.anyMatch((candidate) -> candidate.isCompatibleWith(contentMimeType));
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
private CompressionPredicate getExcludedUserAgentsPredicate(
|
|
|
|
|
String[] excludedUserAgents) {
|
|
|
|
|
if (ObjectUtils.isEmpty(excludedUserAgents)) {
|
|
|
|
|
return CompressionPredicate.ALWAYS;
|
|
|
|
|
}
|
|
|
|
|
return (request, response) -> {
|
|
|
|
|
HttpHeaders headers = request.requestHeaders();
|
|
|
|
|
return Arrays.stream(excludedUserAgents).noneMatch((candidate) -> headers
|
|
|
|
|
.contains(HttpHeaderNames.USER_AGENT, candidate, true));
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class CompressibleMimeTypePredicate
|
|
|
|
|
implements BiPredicate<HttpServerRequest, HttpServerResponse> {
|
|
|
|
|
private interface CompressionPredicate
|
|
|
|
|
extends BiPredicate<HttpServerRequest, HttpServerResponse> {
|
|
|
|
|
|
|
|
|
|
private final MimeType[] mimeTypes;
|
|
|
|
|
static final CompressionPredicate ALWAYS = (request, response) -> true;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|