Apply server.undertow.* config to reactive servers
This commit applies `server.undertow.*` configuration properties to Undertow when configured as a reactive web server. See gh-11500
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.autoconfigure.web.embedded.undertow;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import io.undertow.UndertowOptions;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.cloud.CloudPlatform;
|
||||
import org.springframework.boot.web.embedded.undertow.ConfigurableUndertowWebServerFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Customization for Undertow-specific features common
|
||||
* for both Servlet and Reactive servers.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public final class UndertowCustomizer {
|
||||
|
||||
private UndertowCustomizer() {
|
||||
}
|
||||
|
||||
public static void customizeUndertow(ServerProperties serverProperties,
|
||||
Environment environment, ConfigurableUndertowWebServerFactory factory) {
|
||||
|
||||
ServerProperties.Undertow undertowProperties = serverProperties.getUndertow();
|
||||
ServerProperties.Undertow.Accesslog accesslogProperties = undertowProperties
|
||||
.getAccesslog();
|
||||
if (undertowProperties.getBufferSize() != null) {
|
||||
factory.setBufferSize(undertowProperties.getBufferSize());
|
||||
}
|
||||
if (undertowProperties.getIoThreads() != null) {
|
||||
factory.setIoThreads(undertowProperties.getIoThreads());
|
||||
}
|
||||
if (undertowProperties.getWorkerThreads() != null) {
|
||||
factory.setWorkerThreads(undertowProperties.getWorkerThreads());
|
||||
}
|
||||
if (undertowProperties.getDirectBuffers() != null) {
|
||||
factory.setUseDirectBuffers(undertowProperties.getDirectBuffers());
|
||||
}
|
||||
if (undertowProperties.getAccesslog().getEnabled() != null) {
|
||||
factory.setAccessLogEnabled(accesslogProperties.getEnabled());
|
||||
}
|
||||
factory.setAccessLogDirectory(accesslogProperties.getDir());
|
||||
factory.setAccessLogPattern(accesslogProperties.getPattern());
|
||||
factory.setAccessLogPrefix(accesslogProperties.getPrefix());
|
||||
factory.setAccessLogSuffix(accesslogProperties.getSuffix());
|
||||
factory.setAccessLogRotate(accesslogProperties.isRotate());
|
||||
factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders(serverProperties, environment));
|
||||
if (serverProperties.getMaxHttpHeaderSize() > 0) {
|
||||
customizeMaxHttpHeaderSize(factory,
|
||||
serverProperties.getMaxHttpHeaderSize());
|
||||
}
|
||||
if (undertowProperties.getMaxHttpPostSize() > 0) {
|
||||
customizeMaxHttpPostSize(factory,
|
||||
undertowProperties.getMaxHttpPostSize());
|
||||
}
|
||||
if (serverProperties.getConnectionTimeout() != null) {
|
||||
customizeConnectionTimeout(factory,
|
||||
serverProperties.getConnectionTimeout());
|
||||
}
|
||||
factory.addDeploymentInfoCustomizers((deploymentInfo) -> deploymentInfo
|
||||
.setEagerFilterInit(undertowProperties.isEagerFilterInit()));
|
||||
}
|
||||
|
||||
private static void customizeConnectionTimeout(
|
||||
ConfigurableUndertowWebServerFactory factory, Duration connectionTimeout) {
|
||||
factory.addBuilderCustomizers((builder) -> builder.setSocketOption(
|
||||
UndertowOptions.NO_REQUEST_TIMEOUT,
|
||||
(int) connectionTimeout.toMillis()));
|
||||
}
|
||||
|
||||
private static void customizeMaxHttpHeaderSize(
|
||||
ConfigurableUndertowWebServerFactory factory, int maxHttpHeaderSize) {
|
||||
factory.addBuilderCustomizers((builder) -> builder
|
||||
.setServerOption(UndertowOptions.MAX_HEADER_SIZE, maxHttpHeaderSize));
|
||||
}
|
||||
|
||||
private static void customizeMaxHttpPostSize(
|
||||
ConfigurableUndertowWebServerFactory factory, long maxHttpPostSize) {
|
||||
factory.addBuilderCustomizers((builder) -> builder
|
||||
.setServerOption(UndertowOptions.MAX_ENTITY_SIZE, maxHttpPostSize));
|
||||
}
|
||||
|
||||
private static boolean getOrDeduceUseForwardHeaders(ServerProperties serverProperties,
|
||||
Environment environment) {
|
||||
if (serverProperties.isUseForwardHeaders() != null) {
|
||||
return serverProperties.isUseForwardHeaders();
|
||||
}
|
||||
CloudPlatform platform = CloudPlatform.getActive(environment);
|
||||
return platform != null && platform.isUsingForwardHeaders();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,8 +19,10 @@ package org.springframework.boot.autoconfigure.web.reactive;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.embedded.jetty.JettyCustomizer;
|
||||
import org.springframework.boot.autoconfigure.web.embedded.tomcat.TomcatCustomizer;
|
||||
import org.springframework.boot.autoconfigure.web.embedded.undertow.UndertowCustomizer;
|
||||
import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
@@ -79,6 +81,10 @@ public class DefaultReactiveWebServerFactoryCustomizer implements
|
||||
JettyCustomizer.customizeJetty(this.serverProperties, this.environment,
|
||||
(JettyReactiveWebServerFactory) factory);
|
||||
}
|
||||
if (factory instanceof UndertowReactiveWebServerFactory) {
|
||||
UndertowCustomizer.customizeUndertow(this.serverProperties, this.environment,
|
||||
(UndertowReactiveWebServerFactory) factory);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.web.servlet;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -24,13 +23,11 @@ import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.SessionCookieConfig;
|
||||
|
||||
import io.undertow.UndertowOptions;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties.Session;
|
||||
import org.springframework.boot.autoconfigure.web.embedded.jetty.JettyCustomizer;
|
||||
import org.springframework.boot.autoconfigure.web.embedded.tomcat.TomcatCustomizer;
|
||||
import org.springframework.boot.cloud.CloudPlatform;
|
||||
import org.springframework.boot.autoconfigure.web.embedded.undertow.UndertowCustomizer;
|
||||
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
@@ -129,15 +126,6 @@ public class DefaultServletWebServerFactoryCustomizer
|
||||
this.serverProperties.getServlet().getContextParameters()));
|
||||
}
|
||||
|
||||
private static boolean getOrDeduceUseForwardHeaders(ServerProperties serverProperties,
|
||||
Environment environment) {
|
||||
if (serverProperties.isUseForwardHeaders() != null) {
|
||||
return serverProperties.isUseForwardHeaders();
|
||||
}
|
||||
CloudPlatform platform = CloudPlatform.getActive(environment);
|
||||
return platform != null && platform.isUsingForwardHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ServletContextInitializer} to apply appropriate parts of the {@link Session}
|
||||
* configuration.
|
||||
@@ -231,71 +219,4 @@ public class DefaultServletWebServerFactoryCustomizer
|
||||
}
|
||||
}
|
||||
|
||||
private static class UndertowCustomizer {
|
||||
|
||||
protected static void customizeUndertow(ServerProperties serverProperties,
|
||||
Environment environment, UndertowServletWebServerFactory factory) {
|
||||
|
||||
ServerProperties.Undertow undertowProperties = serverProperties.getUndertow();
|
||||
ServerProperties.Undertow.Accesslog accesslogProperties = undertowProperties
|
||||
.getAccesslog();
|
||||
if (undertowProperties.getBufferSize() != null) {
|
||||
factory.setBufferSize(undertowProperties.getBufferSize());
|
||||
}
|
||||
if (undertowProperties.getIoThreads() != null) {
|
||||
factory.setIoThreads(undertowProperties.getIoThreads());
|
||||
}
|
||||
if (undertowProperties.getWorkerThreads() != null) {
|
||||
factory.setWorkerThreads(undertowProperties.getWorkerThreads());
|
||||
}
|
||||
if (undertowProperties.getDirectBuffers() != null) {
|
||||
factory.setDirectBuffers(undertowProperties.getDirectBuffers());
|
||||
}
|
||||
if (undertowProperties.getAccesslog().getEnabled() != null) {
|
||||
factory.setAccessLogEnabled(accesslogProperties.getEnabled());
|
||||
}
|
||||
factory.setAccessLogDirectory(accesslogProperties.getDir());
|
||||
factory.setAccessLogPattern(accesslogProperties.getPattern());
|
||||
factory.setAccessLogPrefix(accesslogProperties.getPrefix());
|
||||
factory.setAccessLogSuffix(accesslogProperties.getSuffix());
|
||||
factory.setAccessLogRotate(accesslogProperties.isRotate());
|
||||
factory.setUseForwardHeaders(
|
||||
getOrDeduceUseForwardHeaders(serverProperties, environment));
|
||||
if (serverProperties.getMaxHttpHeaderSize() > 0) {
|
||||
customizeMaxHttpHeaderSize(factory,
|
||||
serverProperties.getMaxHttpHeaderSize());
|
||||
}
|
||||
if (undertowProperties.getMaxHttpPostSize() > 0) {
|
||||
customizeMaxHttpPostSize(factory,
|
||||
undertowProperties.getMaxHttpPostSize());
|
||||
}
|
||||
if (serverProperties.getConnectionTimeout() != null) {
|
||||
customizeConnectionTimeout(factory,
|
||||
serverProperties.getConnectionTimeout());
|
||||
}
|
||||
factory.addDeploymentInfoCustomizers((deploymentInfo) -> deploymentInfo
|
||||
.setEagerFilterInit(undertowProperties.isEagerFilterInit()));
|
||||
}
|
||||
|
||||
private static void customizeConnectionTimeout(
|
||||
UndertowServletWebServerFactory factory, Duration connectionTimeout) {
|
||||
factory.addBuilderCustomizers((builder) -> builder.setSocketOption(
|
||||
UndertowOptions.NO_REQUEST_TIMEOUT,
|
||||
(int) connectionTimeout.toMillis()));
|
||||
}
|
||||
|
||||
private static void customizeMaxHttpHeaderSize(
|
||||
UndertowServletWebServerFactory factory, int maxHttpHeaderSize) {
|
||||
factory.addBuilderCustomizers((builder) -> builder
|
||||
.setServerOption(UndertowOptions.MAX_HEADER_SIZE, maxHttpHeaderSize));
|
||||
}
|
||||
|
||||
private static void customizeMaxHttpPostSize(
|
||||
UndertowServletWebServerFactory factory, long maxHttpPostSize) {
|
||||
factory.addBuilderCustomizers((builder) -> builder
|
||||
.setServerOption(UndertowOptions.MAX_ENTITY_SIZE, maxHttpPostSize));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,12 +44,15 @@ import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory
|
||||
import org.springframework.boot.web.embedded.jetty.JettyWebServer;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
|
||||
import org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -434,6 +437,52 @@ public class DefaultReactiveWebServerFactoryCustomizerTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customizeUndertowAccessLog() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("server.undertow.accesslog.enabled", "true");
|
||||
map.put("server.undertow.accesslog.pattern", "foo");
|
||||
map.put("server.undertow.accesslog.prefix", "test_log");
|
||||
map.put("server.undertow.accesslog.suffix", "txt");
|
||||
map.put("server.undertow.accesslog.dir", "test-logs");
|
||||
map.put("server.undertow.accesslog.rotate", "false");
|
||||
bindProperties(map);
|
||||
UndertowReactiveWebServerFactory factory = spy(
|
||||
new UndertowReactiveWebServerFactory());
|
||||
this.customizer.customize(factory);
|
||||
verify(factory).setAccessLogEnabled(true);
|
||||
verify(factory).setAccessLogPattern("foo");
|
||||
verify(factory).setAccessLogPrefix("test_log");
|
||||
verify(factory).setAccessLogSuffix("txt");
|
||||
verify(factory).setAccessLogDirectory(new File("test-logs"));
|
||||
verify(factory).setAccessLogRotate(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setUseForwardHeadersUndertow() {
|
||||
this.properties.setUseForwardHeaders(true);
|
||||
UndertowReactiveWebServerFactory factory = spy(new UndertowReactiveWebServerFactory());
|
||||
this.customizer.customize(factory);
|
||||
verify(factory).setUseForwardHeaders(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deduceUseForwardHeadersUndertow() {
|
||||
this.customizer.setEnvironment(new MockEnvironment().withProperty("DYNO", "-"));
|
||||
UndertowReactiveWebServerFactory factory = spy(
|
||||
new UndertowReactiveWebServerFactory());
|
||||
this.customizer.customize(factory);
|
||||
verify(factory).setUseForwardHeaders(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipNullElementsForUndertow() {
|
||||
UndertowReactiveWebServerFactory factory = mock(
|
||||
UndertowReactiveWebServerFactory.class);
|
||||
this.customizer.customize(factory);
|
||||
verify(factory, never()).setAccessLogEnabled(anyBoolean());
|
||||
}
|
||||
|
||||
private NCSARequestLog getNCSARequestLog(JettyWebServer webServer) {
|
||||
RequestLog requestLog = webServer.getServer().getRequestLog();
|
||||
assertThat(requestLog).isInstanceOf(NCSARequestLog.class);
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
<subpackage name="embedded.tomcat">
|
||||
<allow pkg="org.springframework.boot.web.embedded.tomcat" />
|
||||
</subpackage>
|
||||
<subpackage name="embedded.undertow">
|
||||
<allow pkg="org.springframework.boot.web.embedded.undertow" />
|
||||
</subpackage>
|
||||
<subpackage name="servlet">
|
||||
<allow pkg="javax.servlet" />
|
||||
<allow pkg="org.springframework.boot.web.embedded" />
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.undertow;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
|
||||
/**
|
||||
* Web Server Factory configuration for Undertow-specific features.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.0.0
|
||||
* @see UndertowServletWebServerFactory
|
||||
* @see UndertowReactiveWebServerFactory
|
||||
*/
|
||||
public interface ConfigurableUndertowWebServerFactory {
|
||||
|
||||
/**
|
||||
* Add {@link UndertowBuilderCustomizer}s that should be used to customize the
|
||||
* Undertow {@link Undertow.Builder}.
|
||||
* @param customizers the customizers to add
|
||||
*/
|
||||
void addBuilderCustomizers(UndertowBuilderCustomizer... customizers);
|
||||
|
||||
/**
|
||||
* Add {@link UndertowDeploymentInfoCustomizer}s that should be used to customize the
|
||||
* Undertow {@link DeploymentInfo}.
|
||||
* @param customizers the customizers to add
|
||||
*/
|
||||
void addDeploymentInfoCustomizers(UndertowDeploymentInfoCustomizer... customizers);
|
||||
|
||||
/**
|
||||
* Set the buffer size.
|
||||
* @param bufferSize buffer size
|
||||
*/
|
||||
void setBufferSize(Integer bufferSize);
|
||||
|
||||
/**
|
||||
* Set the number of IO Threads.
|
||||
* @param ioThreads number of IO Threads
|
||||
*/
|
||||
void setIoThreads(Integer ioThreads);
|
||||
|
||||
/**
|
||||
* Set the number of Worker Threads.
|
||||
* @param workerThreads number of Worker Threads
|
||||
*/
|
||||
void setWorkerThreads(Integer workerThreads);
|
||||
|
||||
/**
|
||||
* Set whether direct buffers should be used.
|
||||
* @param useForwardHeaders whether direct buffers should be used
|
||||
*/
|
||||
void setUseDirectBuffers(Boolean useForwardHeaders);
|
||||
|
||||
/**
|
||||
* Set the access log directory.
|
||||
* @param accessLogDirectory access log directory
|
||||
*/
|
||||
void setAccessLogDirectory(File accessLogDirectory);
|
||||
|
||||
/**
|
||||
* Set the access log pattern.
|
||||
* @param accessLogPattern access log pattern
|
||||
*/
|
||||
void setAccessLogPattern(String accessLogPattern);
|
||||
|
||||
/**
|
||||
* Set the access log prefix.
|
||||
* @param accessLogPrefix log prefix
|
||||
*/
|
||||
void setAccessLogPrefix(String accessLogPrefix);
|
||||
|
||||
/**
|
||||
* Set the access log suffix.
|
||||
* @param accessLogSuffix access log suffix
|
||||
*/
|
||||
void setAccessLogSuffix(String accessLogSuffix);
|
||||
|
||||
/**
|
||||
* Set whether access logs are enabled.
|
||||
* @param accessLogEnabled whether access logs are enabled
|
||||
*/
|
||||
void setAccessLogEnabled(boolean accessLogEnabled);
|
||||
|
||||
/**
|
||||
* Set whether access logs rotation is enabled.
|
||||
* @param accessLogRotate whether access logs rotation is enabled
|
||||
*/
|
||||
void setAccessLogRotate(boolean accessLogRotate);
|
||||
|
||||
/**
|
||||
* Set if x-forward-* headers should be processed.
|
||||
* @param useForwardHeaders if x-forward headers should be used
|
||||
*/
|
||||
void setUseForwardHeaders(boolean useForwardHeaders);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.undertow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.undertow.attribute.RequestHeaderAttribute;
|
||||
import io.undertow.predicate.Predicate;
|
||||
import io.undertow.predicate.Predicates;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.server.handlers.encoding.ContentEncodingRepository;
|
||||
import io.undertow.server.handlers.encoding.EncodingHandler;
|
||||
import io.undertow.server.handlers.encoding.GzipEncodingProvider;
|
||||
import io.undertow.util.Headers;
|
||||
import io.undertow.util.HttpString;
|
||||
|
||||
import org.springframework.boot.web.server.Compression;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* Configure the HTTP compression on an Undertow {@link HttpHandler}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class UndertowCompressionConfigurer {
|
||||
|
||||
private UndertowCompressionConfigurer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally wrap the given {@link HttpHandler} for HTTP compression support.
|
||||
* @param compression the HTTP compression configuration
|
||||
* @param httpHandler the HTTP handler to wrap
|
||||
* @return the wrapped HTTP handler if compression is enabled, or the handler itself
|
||||
*/
|
||||
public static HttpHandler configureCompression(Compression compression, HttpHandler httpHandler) {
|
||||
if (compression == null || !compression.getEnabled()) {
|
||||
return httpHandler;
|
||||
}
|
||||
ContentEncodingRepository repository = new ContentEncodingRepository();
|
||||
repository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50,
|
||||
Predicates.and(getCompressionPredicates(compression)));
|
||||
return new EncodingHandler(repository).setNext(httpHandler);
|
||||
}
|
||||
|
||||
private static Predicate[] getCompressionPredicates(Compression compression) {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
predicates.add(new MaxSizePredicate(compression.getMinResponseSize()));
|
||||
predicates.add(new CompressibleMimeTypePredicate(compression.getMimeTypes()));
|
||||
if (compression.getExcludedUserAgents() != null) {
|
||||
for (String agent : compression.getExcludedUserAgents()) {
|
||||
RequestHeaderAttribute agentHeader = new RequestHeaderAttribute(
|
||||
new HttpString(HttpHeaders.USER_AGENT));
|
||||
predicates.add(Predicates.not(Predicates.regex(agentHeader, agent)));
|
||||
}
|
||||
}
|
||||
return predicates.toArray(new Predicate[predicates.size()]);
|
||||
}
|
||||
|
||||
private static class CompressibleMimeTypePredicate implements Predicate {
|
||||
|
||||
private final List<MimeType> mimeTypes;
|
||||
|
||||
CompressibleMimeTypePredicate(String[] mimeTypes) {
|
||||
this.mimeTypes = new ArrayList<>(mimeTypes.length);
|
||||
for (String mimeTypeString : mimeTypes) {
|
||||
this.mimeTypes.add(MimeTypeUtils.parseMimeType(mimeTypeString));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resolve(HttpServerExchange value) {
|
||||
String contentType = value.getResponseHeaders()
|
||||
.getFirst(HttpHeaders.CONTENT_TYPE);
|
||||
if (contentType != null) {
|
||||
for (MimeType mimeType : this.mimeTypes) {
|
||||
if (mimeType
|
||||
.isCompatibleWith(MimeTypeUtils.parseMimeType(contentType))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate that returns true if the Content-Size of a request is above a given value
|
||||
* or is missing.
|
||||
*/
|
||||
private static class MaxSizePredicate implements Predicate {
|
||||
|
||||
private final Predicate maxContentSize;
|
||||
|
||||
MaxSizePredicate(int size) {
|
||||
this.maxContentSize = Predicates.maxContentSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resolve(HttpServerExchange value) {
|
||||
if (value.getResponseHeaders().contains(Headers.CONTENT_LENGTH)) {
|
||||
return this.maxContentSize.resolve(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,18 +16,29 @@
|
||||
|
||||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import io.undertow.Handlers;
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.UndertowOptions;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.handlers.accesslog.AccessLogHandler;
|
||||
import io.undertow.server.handlers.accesslog.AccessLogReceiver;
|
||||
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import org.xnio.OptionMap;
|
||||
import org.xnio.Options;
|
||||
import org.xnio.Xnio;
|
||||
import org.xnio.XnioWorker;
|
||||
|
||||
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -37,7 +48,12 @@ import org.springframework.util.Assert;
|
||||
* @author Brian Clozel
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
|
||||
public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerFactory
|
||||
implements ConfigurableUndertowWebServerFactory {
|
||||
|
||||
private List<UndertowBuilderCustomizer> builderCustomizers = new ArrayList<>();
|
||||
|
||||
private List<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers = new ArrayList<>();
|
||||
|
||||
private Integer bufferSize;
|
||||
|
||||
@@ -47,7 +63,20 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
||||
|
||||
private Boolean directBuffers;
|
||||
|
||||
private List<UndertowBuilderCustomizer> builderCustomizers = new ArrayList<>();
|
||||
private File accessLogDirectory;
|
||||
|
||||
private String accessLogPattern;
|
||||
|
||||
private String accessLogPrefix;
|
||||
|
||||
private String accessLogSuffix;
|
||||
|
||||
private boolean accessLogEnabled = false;
|
||||
|
||||
private boolean accessLogRotate = true;
|
||||
|
||||
private boolean useForwardHeaders;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link UndertowReactiveWebServerFactory} instance.
|
||||
@@ -65,9 +94,9 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebServer getWebServer(HttpHandler httpHandler) {
|
||||
public WebServer getWebServer(org.springframework.http.server.reactive.HttpHandler httpHandler) {
|
||||
Undertow.Builder builder = createBuilder(getPort());
|
||||
UndertowHttpHandlerAdapter handler = new UndertowHttpHandlerAdapter(httpHandler);
|
||||
HttpHandler handler = createUndertowHandler(httpHandler);
|
||||
builder.setHandler(handler);
|
||||
return new UndertowWebServer(builder, getPort() >= 0);
|
||||
}
|
||||
@@ -98,6 +127,50 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
||||
return builder;
|
||||
}
|
||||
|
||||
private HttpHandler createUndertowHandler(org.springframework.http.server.reactive.HttpHandler httpHandler) {
|
||||
HttpHandler handler = new UndertowHttpHandlerAdapter(httpHandler);
|
||||
if (this.useForwardHeaders) {
|
||||
handler = Handlers.proxyPeerAddress(handler);
|
||||
}
|
||||
handler = UndertowCompressionConfigurer.configureCompression(getCompression(), handler);
|
||||
if (isAccessLogEnabled()) {
|
||||
handler = createAccessLogHandler(handler);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private AccessLogHandler createAccessLogHandler(io.undertow.server.HttpHandler handler) {
|
||||
try {
|
||||
createAccessLogDirectoryIfNecessary();
|
||||
String prefix = (this.accessLogPrefix != null ? this.accessLogPrefix
|
||||
: "access_log.");
|
||||
AccessLogReceiver accessLogReceiver = new DefaultAccessLogReceiver(
|
||||
createWorker(), this.accessLogDirectory, prefix, this.accessLogSuffix,
|
||||
this.accessLogRotate);
|
||||
String formatString = (this.accessLogPattern != null) ? this.accessLogPattern
|
||||
: "common";
|
||||
return new AccessLogHandler(handler, accessLogReceiver, formatString,
|
||||
Undertow.class.getClassLoader());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to create AccessLogHandler", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void createAccessLogDirectoryIfNecessary() {
|
||||
Assert.state(this.accessLogDirectory != null, "Access log directory is not set");
|
||||
if (!this.accessLogDirectory.isDirectory() && !this.accessLogDirectory.mkdirs()) {
|
||||
throw new IllegalStateException("Failed to create access log directory '"
|
||||
+ this.accessLogDirectory + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private XnioWorker createWorker() throws IOException {
|
||||
Xnio xnio = Xnio.getInstance(Undertow.class.getClassLoader());
|
||||
return xnio.createWorker(
|
||||
OptionMap.builder().set(Options.THREAD_DAEMON, true).getMap());
|
||||
}
|
||||
|
||||
private void customizeSsl(Undertow.Builder builder) {
|
||||
new SslBuilderCustomizer(getPort(), getAddress(), getSsl(), getSslStoreProvider())
|
||||
.customize(builder);
|
||||
@@ -114,19 +187,94 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
||||
return getAddress().getHostAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@link UndertowDeploymentInfoCustomizer}s that should be applied to the
|
||||
* Undertow {@link DeploymentInfo}. Calling this method will replace any existing
|
||||
* customizers.
|
||||
* @param customizers the customizers to set
|
||||
*/
|
||||
public void setDeploymentInfoCustomizers(
|
||||
Collection<? extends UndertowDeploymentInfoCustomizer> customizers) {
|
||||
Assert.notNull(customizers, "Customizers must not be null");
|
||||
this.deploymentInfoCustomizers = new ArrayList<>(customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mutable collection of the {@link UndertowDeploymentInfoCustomizer}s that
|
||||
* will be applied to the Undertow {@link DeploymentInfo}.
|
||||
* @return the customizers that will be applied
|
||||
*/
|
||||
public Collection<UndertowDeploymentInfoCustomizer> getDeploymentInfoCustomizers() {
|
||||
return this.deploymentInfoCustomizers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDeploymentInfoCustomizers(
|
||||
UndertowDeploymentInfoCustomizer... customizers) {
|
||||
Assert.notNull(customizers, "UndertowDeploymentInfoCustomizers must not be null");
|
||||
this.deploymentInfoCustomizers.addAll(Arrays.asList(customizers));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogDirectory(File accessLogDirectory) {
|
||||
this.accessLogDirectory = accessLogDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogPattern(String accessLogPattern) {
|
||||
this.accessLogPattern = accessLogPattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogPrefix(String accessLogPrefix) {
|
||||
this.accessLogPrefix = accessLogPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogSuffix(String accessLogSuffix) {
|
||||
this.accessLogSuffix = accessLogSuffix;
|
||||
}
|
||||
|
||||
public boolean isAccessLogEnabled() {
|
||||
return this.accessLogEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogEnabled(boolean accessLogEnabled) {
|
||||
this.accessLogEnabled = accessLogEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogRotate(boolean accessLogRotate) {
|
||||
this.accessLogRotate = accessLogRotate;
|
||||
}
|
||||
|
||||
protected final boolean isUseForwardHeaders() {
|
||||
return this.useForwardHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseForwardHeaders(boolean useForwardHeaders) {
|
||||
this.useForwardHeaders = useForwardHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBufferSize(Integer bufferSize) {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIoThreads(Integer ioThreads) {
|
||||
this.ioThreads = ioThreads;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWorkerThreads(Integer workerThreads) {
|
||||
this.workerThreads = workerThreads;
|
||||
}
|
||||
|
||||
public void setDirectBuffers(Boolean directBuffers) {
|
||||
@Override
|
||||
public void setUseDirectBuffers(Boolean directBuffers) {
|
||||
this.directBuffers = directBuffers;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,17 +28,8 @@ import javax.servlet.ServletException;
|
||||
import io.undertow.Handlers;
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.Undertow.Builder;
|
||||
import io.undertow.attribute.RequestHeaderAttribute;
|
||||
import io.undertow.predicate.Predicate;
|
||||
import io.undertow.predicate.Predicates;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.server.handlers.encoding.ContentEncodingRepository;
|
||||
import io.undertow.server.handlers.encoding.EncodingHandler;
|
||||
import io.undertow.server.handlers.encoding.GzipEncodingProvider;
|
||||
import io.undertow.servlet.api.DeploymentManager;
|
||||
import io.undertow.util.Headers;
|
||||
import io.undertow.util.HttpString;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.xnio.channels.BoundChannel;
|
||||
@@ -47,9 +38,6 @@ import org.springframework.boot.web.server.Compression;
|
||||
import org.springframework.boot.web.server.PortInUseException;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.boot.web.server.WebServerException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -216,37 +204,14 @@ public class UndertowServletWebServer implements WebServer {
|
||||
}
|
||||
|
||||
private HttpHandler getContextHandler(HttpHandler httpHandler) {
|
||||
HttpHandler contextHandler = configurationCompressionIfNecessary(httpHandler);
|
||||
HttpHandler contextHandler = UndertowCompressionConfigurer
|
||||
.configureCompression(this.compression, httpHandler);
|
||||
if (StringUtils.isEmpty(this.contextPath)) {
|
||||
return contextHandler;
|
||||
}
|
||||
return Handlers.path().addPrefixPath(this.contextPath, contextHandler);
|
||||
}
|
||||
|
||||
private HttpHandler configurationCompressionIfNecessary(HttpHandler httpHandler) {
|
||||
if (this.compression == null || !this.compression.getEnabled()) {
|
||||
return httpHandler;
|
||||
}
|
||||
ContentEncodingRepository repository = new ContentEncodingRepository();
|
||||
repository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50,
|
||||
Predicates.and(getCompressionPredicates(this.compression)));
|
||||
return new EncodingHandler(repository).setNext(httpHandler);
|
||||
}
|
||||
|
||||
private Predicate[] getCompressionPredicates(Compression compression) {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
predicates.add(new MaxSizePredicate(compression.getMinResponseSize()));
|
||||
predicates.add(new CompressibleMimeTypePredicate(compression.getMimeTypes()));
|
||||
if (compression.getExcludedUserAgents() != null) {
|
||||
for (String agent : compression.getExcludedUserAgents()) {
|
||||
RequestHeaderAttribute agentHeader = new RequestHeaderAttribute(
|
||||
new HttpString(HttpHeaders.USER_AGENT));
|
||||
predicates.add(Predicates.not(Predicates.regex(agentHeader, agent)));
|
||||
}
|
||||
}
|
||||
return predicates.toArray(new Predicate[predicates.size()]);
|
||||
}
|
||||
|
||||
private String getPortsDescription() {
|
||||
List<Port> ports = getActualPorts();
|
||||
if (!ports.isEmpty()) {
|
||||
@@ -395,54 +360,4 @@ public class UndertowServletWebServer implements WebServer {
|
||||
|
||||
}
|
||||
|
||||
private static class CompressibleMimeTypePredicate implements Predicate {
|
||||
|
||||
private final List<MimeType> mimeTypes;
|
||||
|
||||
CompressibleMimeTypePredicate(String[] mimeTypes) {
|
||||
this.mimeTypes = new ArrayList<>(mimeTypes.length);
|
||||
for (String mimeTypeString : mimeTypes) {
|
||||
this.mimeTypes.add(MimeTypeUtils.parseMimeType(mimeTypeString));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resolve(HttpServerExchange value) {
|
||||
String contentType = value.getResponseHeaders()
|
||||
.getFirst(HttpHeaders.CONTENT_TYPE);
|
||||
if (contentType != null) {
|
||||
for (MimeType mimeType : this.mimeTypes) {
|
||||
if (mimeType
|
||||
.isCompatibleWith(MimeTypeUtils.parseMimeType(contentType))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate that returns true if the Content-Size of a request is above a given value
|
||||
* or is missing.
|
||||
*/
|
||||
private static class MaxSizePredicate implements Predicate {
|
||||
|
||||
private final Predicate maxContentSize;
|
||||
|
||||
MaxSizePredicate(int size) {
|
||||
this.maxContentSize = Predicates.maxContentSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resolve(HttpServerExchange value) {
|
||||
if (value.getResponseHeaders().contains(Headers.CONTENT_LENGTH)) {
|
||||
return this.maxContentSize.resolve(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ import org.springframework.util.Assert;
|
||||
* @see UndertowServletWebServer
|
||||
*/
|
||||
public class UndertowServletWebServerFactory extends AbstractServletWebServerFactory
|
||||
implements ResourceLoaderAware {
|
||||
implements ConfigurableUndertowWebServerFactory, ResourceLoaderAware {
|
||||
|
||||
private static final Set<Class<?>> NO_CLASSES = Collections.emptySet();
|
||||
|
||||
@@ -167,11 +167,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
return this.builderCustomizers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link UndertowBuilderCustomizer}s that should be used to customize the
|
||||
* Undertow {@link Builder}.
|
||||
* @param customizers the customizers to add
|
||||
*/
|
||||
@Override
|
||||
public void addBuilderCustomizers(UndertowBuilderCustomizer... customizers) {
|
||||
Assert.notNull(customizers, "Customizers must not be null");
|
||||
this.builderCustomizers.addAll(Arrays.asList(customizers));
|
||||
@@ -198,11 +194,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
return this.deploymentInfoCustomizers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link UndertowDeploymentInfoCustomizer}s that should be used to customize the
|
||||
* Undertow {@link DeploymentInfo}.
|
||||
* @param customizers the customizers to add
|
||||
*/
|
||||
@Override
|
||||
public void addDeploymentInfoCustomizers(
|
||||
UndertowDeploymentInfoCustomizer... customizers) {
|
||||
Assert.notNull(customizers, "UndertowDeploymentInfoCustomizers must not be null");
|
||||
@@ -449,10 +441,12 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBufferSize(Integer bufferSize) {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIoThreads(Integer ioThreads) {
|
||||
this.ioThreads = ioThreads;
|
||||
}
|
||||
@@ -461,14 +455,17 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
this.workerThreads = workerThreads;
|
||||
}
|
||||
|
||||
public void setDirectBuffers(Boolean directBuffers) {
|
||||
@Override
|
||||
public void setUseDirectBuffers(Boolean directBuffers) {
|
||||
this.directBuffers = directBuffers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogDirectory(File accessLogDirectory) {
|
||||
this.accessLogDirectory = accessLogDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogPattern(String accessLogPattern) {
|
||||
this.accessLogPattern = accessLogPattern;
|
||||
}
|
||||
@@ -477,14 +474,17 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
return this.accessLogPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogPrefix(String accessLogPrefix) {
|
||||
this.accessLogPrefix = accessLogPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogSuffix(String accessLogSuffix) {
|
||||
this.accessLogSuffix = accessLogSuffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogEnabled(boolean accessLogEnabled) {
|
||||
this.accessLogEnabled = accessLogEnabled;
|
||||
}
|
||||
@@ -493,6 +493,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
return this.accessLogEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogRotate(boolean accessLogRotate) {
|
||||
this.accessLogRotate = accessLogRotate;
|
||||
}
|
||||
@@ -501,11 +502,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
return this.useForwardHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if x-forward-* headers should be processed.
|
||||
* @param useForwardHeaders if x-forward headers should be used
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Override
|
||||
public void setUseForwardHeaders(boolean useForwardHeaders) {
|
||||
this.useForwardHeaders = useForwardHeaders;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user