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:
Brian Clozel
2018-01-11 11:15:23 +01:00
parent 15bc718248
commit 98ac2218db
10 changed files with 581 additions and 190 deletions

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);

View File

@@ -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" />

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}