Commit c42571ba authored by Andy Wilkinson's avatar Andy Wilkinson

Consolidate Undertow WebServers and simplify their constructors

Closes gh-21391
Co-authored-by: 's avatarPhillip Webb <pwebb@pivotal.io>
parent 0d009477
/*
* Copyright 2012-2020 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
*
* https://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.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.accesslog.AccessLogHandler;
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
import org.xnio.OptionMap;
import org.xnio.Options;
import org.xnio.Xnio;
import org.xnio.XnioWorker;
import org.springframework.util.Assert;
/**
* A {@link HttpHandlerFactory} for an {@link AccessLogHandler}.
*
* @author Andy Wilkinson
*/
class AccessLogHttpHandlerFactory implements HttpHandlerFactory {
private final File directory;
private final String pattern;
private final String prefix;
private final String suffix;
private final boolean rotate;
AccessLogHttpHandlerFactory(File directory, String pattern, String prefix, String suffix, boolean rotate) {
this.directory = directory;
this.pattern = pattern;
this.prefix = prefix;
this.suffix = suffix;
this.rotate = rotate;
}
@Override
public HttpHandler getHandler(HttpHandler next) {
try {
createAccessLogDirectoryIfNecessary();
XnioWorker worker = createWorker();
String baseName = (this.prefix != null) ? this.prefix : "access_log.";
String formatString = (this.pattern != null) ? this.pattern : "common";
return new ClosableAccessLogHandler(next, worker,
new DefaultAccessLogReceiver(worker, this.directory, baseName, this.suffix, this.rotate),
formatString);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to create AccessLogHandler", ex);
}
}
private void createAccessLogDirectoryIfNecessary() {
Assert.state(this.directory != null, "Access log directory is not set");
if (!this.directory.isDirectory() && !this.directory.mkdirs()) {
throw new IllegalStateException("Failed to create access log directory '" + this.directory + "'");
}
}
private XnioWorker createWorker() throws IOException {
Xnio xnio = Xnio.getInstance(Undertow.class.getClassLoader());
return xnio.createWorker(OptionMap.builder().set(Options.THREAD_DAEMON, true).getMap());
}
/**
* {@link Closeable} variant of {@link AccessLogHandler}.
*/
private static class ClosableAccessLogHandler extends AccessLogHandler implements Closeable {
private final DefaultAccessLogReceiver accessLogReceiver;
private final XnioWorker worker;
ClosableAccessLogHandler(HttpHandler next, XnioWorker worker, DefaultAccessLogReceiver accessLogReceiver,
String formatString) {
super(next, accessLogReceiver, formatString, Undertow.class.getClassLoader());
this.worker = worker;
this.accessLogReceiver = accessLogReceiver;
}
@Override
public void close() throws IOException {
try {
this.accessLogReceiver.close();
this.worker.shutdown();
this.worker.awaitTermination(30, TimeUnit.SECONDS);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
...@@ -37,30 +37,28 @@ import org.springframework.util.MimeType; ...@@ -37,30 +37,28 @@ import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils; import org.springframework.util.MimeTypeUtils;
/** /**
* Configure the HTTP compression on an Undertow {@link HttpHandler}. * {@link HttpHandlerFactory} that adds a compression handler.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb * @author Phillip Webb
*/ */
final class UndertowCompressionConfigurer { class CompressionHttpHandlerFactory implements HttpHandlerFactory {
private UndertowCompressionConfigurer() { private final Compression compression;
CompressionHttpHandlerFactory(Compression compression) {
this.compression = compression;
} }
/** @Override
* Optionally wrap the given {@link HttpHandler} for HTTP compression support. public HttpHandler getHandler(HttpHandler next) {
* @param compression the HTTP compression configuration if (!this.compression.getEnabled()) {
* @param httpHandler the HTTP handler to wrap return next;
* @return the wrapped HTTP handler if compression is enabled, or the handler itself
*/
static HttpHandler configureCompression(Compression compression, HttpHandler httpHandler) {
if (compression == null || !compression.getEnabled()) {
return httpHandler;
} }
ContentEncodingRepository repository = new ContentEncodingRepository(); ContentEncodingRepository repository = new ContentEncodingRepository();
repository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50, repository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50,
Predicates.and(getCompressionPredicates(compression))); Predicates.and(getCompressionPredicates(this.compression)));
return new EncodingHandler(repository).setNext(httpHandler); return new EncodingHandler(repository).setNext(next);
} }
private static Predicate[] getCompressionPredicates(Compression compression) { private static Predicate[] getCompressionPredicates(Compression compression) {
...@@ -76,6 +74,9 @@ final class UndertowCompressionConfigurer { ...@@ -76,6 +74,9 @@ final class UndertowCompressionConfigurer {
return predicates.toArray(new Predicate[0]); return predicates.toArray(new Predicate[0]);
} }
/**
* Predicate used to match specific mime types.
*/
private static class CompressibleMimeTypePredicate implements Predicate { private static class CompressibleMimeTypePredicate implements Predicate {
private final List<MimeType> mimeTypes; private final List<MimeType> mimeTypes;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.web.embedded.undertow; package org.springframework.boot.web.embedded.undertow;
import java.io.File; import java.io.File;
import java.util.Collection;
import io.undertow.Undertow.Builder; import io.undertow.Undertow.Builder;
...@@ -32,6 +33,14 @@ import org.springframework.boot.web.server.ConfigurableWebServerFactory; ...@@ -32,6 +33,14 @@ import org.springframework.boot.web.server.ConfigurableWebServerFactory;
*/ */
public interface ConfigurableUndertowWebServerFactory extends ConfigurableWebServerFactory { public interface ConfigurableUndertowWebServerFactory extends ConfigurableWebServerFactory {
/**
* Set {@link UndertowBuilderCustomizer}s that should be applied to the Undertow
* {@link Builder}. Calling this method will replace any existing customizers.
* @param customizers the customizers to set
* @since 2.3.0
*/
void setBuilderCustomizers(Collection<? extends UndertowBuilderCustomizer> customizers);
/** /**
* Add {@link UndertowBuilderCustomizer}s that should be used to customize the * Add {@link UndertowBuilderCustomizer}s that should be used to customize the
* Undertow {@link Builder}. * Undertow {@link Builder}.
......
/*
* Copyright 2012-2020 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
*
* https://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.Closeable;
import java.io.IOException;
import javax.servlet.ServletException;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.api.DeploymentManager;
import org.springframework.util.Assert;
/**
* {@link HttpHandlerFactory} that for a {@link DeploymentManager}.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
class DeploymentManagerHttpHandlerFactory implements HttpHandlerFactory {
private final DeploymentManager deploymentManager;
DeploymentManagerHttpHandlerFactory(DeploymentManager deploymentManager) {
this.deploymentManager = deploymentManager;
}
@Override
public HttpHandler getHandler(HttpHandler next) {
Assert.state(next == null, "DeploymentManagerHttpHandlerFactory must be first");
return new DeploymentManagerHandler(this.deploymentManager);
}
DeploymentManager getDeploymentManager() {
return this.deploymentManager;
}
/**
* {@link HttpHandler} that delegates to a {@link DeploymentManager}.
*/
static class DeploymentManagerHandler implements HttpHandler, Closeable {
private final DeploymentManager deploymentManager;
private final HttpHandler handler;
DeploymentManagerHandler(DeploymentManager deploymentManager) {
this.deploymentManager = deploymentManager;
try {
this.handler = deploymentManager.start();
}
catch (ServletException ex) {
throw new RuntimeException(ex);
}
}
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
this.handler.handleRequest(exchange);
}
@Override
public void close() throws IOException {
try {
this.deploymentManager.stop();
this.deploymentManager.undeploy();
}
catch (ServletException ex) {
throw new RuntimeException(ex);
}
}
DeploymentManager getDeploymentManager() {
return this.deploymentManager;
}
}
}
...@@ -18,6 +18,7 @@ package org.springframework.boot.web.embedded.undertow; ...@@ -18,6 +18,7 @@ package org.springframework.boot.web.embedded.undertow;
import java.time.Duration; import java.time.Duration;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.GracefulShutdownHandler; import io.undertow.server.handlers.GracefulShutdownHandler;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
...@@ -25,34 +26,33 @@ import org.apache.commons.logging.LogFactory; ...@@ -25,34 +26,33 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.boot.web.server.GracefulShutdown; import org.springframework.boot.web.server.GracefulShutdown;
/** /**
* {@link GracefulShutdown} for Undertow. * A {@link GracefulShutdownHandler} with support for our own {@link GracefulShutdown}
* interface.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
class UndertowGracefulShutdown implements GracefulShutdown { class GracefulShutdownHttpHandler extends GracefulShutdownHandler implements GracefulShutdown {
private static final Log logger = LogFactory.getLog(UndertowGracefulShutdown.class); private static final Log logger = LogFactory.getLog(GracefulShutdownHttpHandler.class);
private final GracefulShutdownHandler gracefulShutdownHandler; private final Duration gracePeriod;
private final Duration period;
private volatile boolean shuttingDown; private volatile boolean shuttingDown;
UndertowGracefulShutdown(GracefulShutdownHandler gracefulShutdownHandler, Duration period) { GracefulShutdownHttpHandler(HttpHandler next, Duration period) {
this.gracefulShutdownHandler = gracefulShutdownHandler; super(next);
this.period = period; this.gracePeriod = period;
} }
@Override @Override
public boolean shutDownGracefully() { public boolean shutDownGracefully() {
logger.info("Commencing graceful shutdown, allowing up to " + this.period.getSeconds() logger.info("Commencing graceful shutdown, allowing up to " + this.gracePeriod.getSeconds()
+ "s for active requests to complete"); + "s for active requests to complete");
this.gracefulShutdownHandler.shutdown(); shutdown();
this.shuttingDown = true; this.shuttingDown = true;
boolean graceful = false; boolean graceful = false;
try { try {
graceful = this.gracefulShutdownHandler.awaitShutdown(this.period.toMillis()); graceful = awaitShutdown(this.gracePeriod.toMillis());
} }
catch (InterruptedException ex) { catch (InterruptedException ex) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
......
/*
* Copyright 2012-2020 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
*
* https://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.Closeable;
import io.undertow.server.HttpHandler;
import org.springframework.boot.web.server.GracefulShutdown;
/**
* Factory used by {@link UndertowServletWebServer} to add {@link HttpHandler
* HttpHandlers}. Instances returned from this factory may optionally implement the
* following interfaces:
* <ul>
* <li>{@link Closeable} - if they wish to be closed just before server stops.</li>
* <li>{@link GracefulShutdown} - if they wish to manage graceful shutdown.</li>
* </ul>
*
* @author Phillip Webb
* @since 2.3.0
*/
@FunctionalInterface
public interface HttpHandlerFactory {
/**
* Create the {@link HttpHandler} instance that should be added.
* @param next the next handler in the chain
* @return the new HTTP handler instance
*/
HttpHandler getHandler(HttpHandler next);
}
...@@ -17,13 +17,17 @@ ...@@ -17,13 +17,17 @@
package org.springframework.boot.web.embedded.undertow; package org.springframework.boot.web.embedded.undertow;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import io.undertow.Undertow; import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.xnio.channels.BoundChannel; import org.xnio.channels.BoundChannel;
...@@ -32,6 +36,7 @@ import org.springframework.boot.web.server.GracefulShutdown; ...@@ -32,6 +36,7 @@ import org.springframework.boot.web.server.GracefulShutdown;
import org.springframework.boot.web.server.PortInUseException; import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.server.WebServerException;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -55,16 +60,18 @@ public class UndertowWebServer implements WebServer { ...@@ -55,16 +60,18 @@ public class UndertowWebServer implements WebServer {
private final Undertow.Builder builder; private final Undertow.Builder builder;
private final boolean autoStart; private final Iterable<HttpHandlerFactory> httpHandlerFactories;
private final Closeable closeable;
private final GracefulShutdown gracefulShutdown; private final boolean autoStart;
private Undertow undertow; private Undertow undertow;
private volatile boolean started = false; private volatile boolean started = false;
private volatile GracefulShutdown gracefulShutdown;
private volatile List<Closeable> closeables;
/** /**
* Create a new {@link UndertowWebServer} instance. * Create a new {@link UndertowWebServer} instance.
* @param builder the builder * @param builder the builder
...@@ -80,25 +87,26 @@ public class UndertowWebServer implements WebServer { ...@@ -80,25 +87,26 @@ public class UndertowWebServer implements WebServer {
* @param autoStart if the server should be started * @param autoStart if the server should be started
* @param closeable called when the server is stopped * @param closeable called when the server is stopped
* @since 2.0.4 * @since 2.0.4
* @deprecated since 2.3.0 in favor of
* {@link #UndertowWebServer(io.undertow.Undertow.Builder, Iterable, boolean)}
*/ */
@Deprecated
public UndertowWebServer(Undertow.Builder builder, boolean autoStart, Closeable closeable) { public UndertowWebServer(Undertow.Builder builder, boolean autoStart, Closeable closeable) {
this(builder, autoStart, closeable, GracefulShutdown.IMMEDIATE); this(builder, Collections.singleton(new CloseableHttpHandlerFactory(closeable)), autoStart);
} }
/** /**
* Create a new {@link UndertowWebServer} instance. * Create a new {@link UndertowWebServer} instance.
* @param builder the builder * @param builder the builder
* @param httpHandlerFactories the handler factories
* @param autoStart if the server should be started * @param autoStart if the server should be started
* @param closeable called when the server is stopped
* @param gracefulShutdown handler for graceful shutdown
* @since 2.3.0 * @since 2.3.0
*/ */
public UndertowWebServer(Undertow.Builder builder, boolean autoStart, Closeable closeable, public UndertowWebServer(Undertow.Builder builder, Iterable<HttpHandlerFactory> httpHandlerFactories,
GracefulShutdown gracefulShutdown) { boolean autoStart) {
this.builder = builder; this.builder = builder;
this.httpHandlerFactories = httpHandlerFactories;
this.autoStart = autoStart; this.autoStart = autoStart;
this.closeable = closeable;
this.gracefulShutdown = gracefulShutdown;
} }
@Override @Override
...@@ -112,11 +120,12 @@ public class UndertowWebServer implements WebServer { ...@@ -112,11 +120,12 @@ public class UndertowWebServer implements WebServer {
return; return;
} }
if (this.undertow == null) { if (this.undertow == null) {
this.undertow = this.builder.build(); this.undertow = createUndertowServer();
} }
this.undertow.start(); this.undertow.start();
this.started = true; this.started = true;
logger.info("Undertow started on port(s) " + getPortsDescription()); String message = getStartLogMessage();
logger.info(message);
} }
catch (Exception ex) { catch (Exception ex) {
try { try {
...@@ -140,7 +149,7 @@ public class UndertowWebServer implements WebServer { ...@@ -140,7 +149,7 @@ public class UndertowWebServer implements WebServer {
try { try {
if (this.undertow != null) { if (this.undertow != null) {
this.undertow.stop(); this.undertow.stop();
this.closeable.close(); this.closeables.forEach(this::closeSilently);
} }
} }
catch (Exception ex) { catch (Exception ex) {
...@@ -148,6 +157,37 @@ public class UndertowWebServer implements WebServer { ...@@ -148,6 +157,37 @@ public class UndertowWebServer implements WebServer {
} }
} }
private void closeSilently(Closeable closeable) {
try {
closeable.close();
}
catch (Exception ex) {
}
}
private Undertow createUndertowServer() {
this.closeables = new ArrayList<>();
this.gracefulShutdown = null;
HttpHandler handler = createHttpHandler();
this.builder.setHandler(handler);
return this.builder.build();
}
protected HttpHandler createHttpHandler() {
HttpHandler handler = null;
for (HttpHandlerFactory factory : this.httpHandlerFactories) {
handler = factory.getHandler(handler);
if (handler instanceof Closeable) {
this.closeables.add((Closeable) handler);
}
if (handler instanceof GracefulShutdown) {
Assert.isNull(this.gracefulShutdown, "Only a single GracefulShutdown handler can be defined");
this.gracefulShutdown = (GracefulShutdown) handler;
}
}
return handler;
}
private String getPortsDescription() { private String getPortsDescription() {
List<UndertowWebServer.Port> ports = getActualPorts(); List<UndertowWebServer.Port> ports = getActualPorts();
if (!ports.isEmpty()) { if (!ports.isEmpty()) {
...@@ -156,11 +196,11 @@ public class UndertowWebServer implements WebServer { ...@@ -156,11 +196,11 @@ public class UndertowWebServer implements WebServer {
return "unknown"; return "unknown";
} }
private List<UndertowWebServer.Port> getActualPorts() { private List<Port> getActualPorts() {
List<UndertowWebServer.Port> ports = new ArrayList<>(); List<Port> ports = new ArrayList<>();
try { try {
if (!this.autoStart) { if (!this.autoStart) {
ports.add(new UndertowWebServer.Port(-1, "unknown")); ports.add(new Port(-1, "unknown"));
} }
else { else {
for (BoundChannel channel : extractChannels()) { for (BoundChannel channel : extractChannels()) {
...@@ -192,10 +232,13 @@ public class UndertowWebServer implements WebServer { ...@@ -192,10 +232,13 @@ public class UndertowWebServer implements WebServer {
} }
private List<UndertowWebServer.Port> getConfiguredPorts() { private List<UndertowWebServer.Port> getConfiguredPorts() {
List<UndertowWebServer.Port> ports = new ArrayList<>(); List<Port> ports = new ArrayList<>();
for (Object listener : extractListeners()) { for (Object listener : extractListeners()) {
try { try {
ports.add(getPortFromListener(listener)); Port port = getPortFromListener(listener);
if (port.getNumber() != 0) {
ports.add(port);
}
} }
catch (Exception ex) { catch (Exception ex) {
// Continue // Continue
...@@ -230,8 +273,8 @@ public class UndertowWebServer implements WebServer { ...@@ -230,8 +273,8 @@ public class UndertowWebServer implements WebServer {
this.started = false; this.started = false;
try { try {
this.undertow.stop(); this.undertow.stop();
if (this.closeable != null) { for (Closeable closeable : this.closeables) {
this.closeable.close(); closeable.close();
} }
} }
catch (Exception ex) { catch (Exception ex) {
...@@ -242,7 +285,7 @@ public class UndertowWebServer implements WebServer { ...@@ -242,7 +285,7 @@ public class UndertowWebServer implements WebServer {
@Override @Override
public int getPort() { public int getPort() {
List<UndertowWebServer.Port> ports = getActualPorts(); List<Port> ports = getActualPorts();
if (ports.isEmpty()) { if (ports.isEmpty()) {
return 0; return 0;
} }
...@@ -251,11 +294,15 @@ public class UndertowWebServer implements WebServer { ...@@ -251,11 +294,15 @@ public class UndertowWebServer implements WebServer {
@Override @Override
public boolean shutDownGracefully() { public boolean shutDownGracefully() {
return (this.gracefulShutdown != null) && this.gracefulShutdown.shutDownGracefully(); return (this.gracefulShutdown != null) ? this.gracefulShutdown.shutDownGracefully() : false;
} }
boolean inGracefulShutdown() { boolean inGracefulShutdown() {
return (this.gracefulShutdown != null) && this.gracefulShutdown.isShuttingDown(); return (this.gracefulShutdown != null) ? this.gracefulShutdown.isShuttingDown() : false;
}
protected String getStartLogMessage() {
return "Undertow started on port(s) " + getPortsDescription();
} }
/** /**
...@@ -303,4 +350,44 @@ public class UndertowWebServer implements WebServer { ...@@ -303,4 +350,44 @@ public class UndertowWebServer implements WebServer {
} }
/**
* {@link HttpHandlerFactory} to wrap a closable.
*/
private static final class CloseableHttpHandlerFactory implements HttpHandlerFactory {
private final Closeable closeable;
private CloseableHttpHandlerFactory(Closeable closeable) {
this.closeable = closeable;
}
@Override
public HttpHandler getHandler(HttpHandler next) {
if (this.closeable == null) {
return next;
}
return new CloseableHttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
next.handleRequest(exchange);
}
@Override
public void close() throws IOException {
CloseableHttpHandlerFactory.this.closeable.close();
}
};
}
}
/**
* {@link Closeable} {@link HttpHandler}.
*/
private interface CloseableHttpHandler extends HttpHandler, Closeable {
}
} }
/*
* Copyright 2012-2020 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
*
* https://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 java.net.InetAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.UndertowOptions;
import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory;
import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Ssl;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Delegate class used by {@link UndertowServletWebServerFactory} and
* {@link UndertowReactiveWebServerFactory}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class UndertowWebServerFactoryDelegate {
private Set<UndertowBuilderCustomizer> builderCustomizers = new LinkedHashSet<>();
private Integer bufferSize;
private Integer ioThreads;
private Integer workerThreads;
private Boolean directBuffers;
private File accessLogDirectory;
private String accessLogPattern;
private String accessLogPrefix;
private String accessLogSuffix;
private boolean accessLogEnabled = false;
private boolean accessLogRotate = true;
private boolean useForwardHeaders;
void setBuilderCustomizers(Collection<? extends UndertowBuilderCustomizer> customizers) {
Assert.notNull(customizers, "Customizers must not be null");
this.builderCustomizers = new LinkedHashSet<>(customizers);
}
void addBuilderCustomizers(UndertowBuilderCustomizer... customizers) {
Assert.notNull(customizers, "Customizers must not be null");
this.builderCustomizers.addAll(Arrays.asList(customizers));
}
Collection<UndertowBuilderCustomizer> getBuilderCustomizers() {
return this.builderCustomizers;
}
void setBufferSize(Integer bufferSize) {
this.bufferSize = bufferSize;
}
void setIoThreads(Integer ioThreads) {
this.ioThreads = ioThreads;
}
void setWorkerThreads(Integer workerThreads) {
this.workerThreads = workerThreads;
}
void setUseDirectBuffers(Boolean directBuffers) {
this.directBuffers = directBuffers;
}
void setAccessLogDirectory(File accessLogDirectory) {
this.accessLogDirectory = accessLogDirectory;
}
void setAccessLogPattern(String accessLogPattern) {
this.accessLogPattern = accessLogPattern;
}
void setAccessLogPrefix(String accessLogPrefix) {
this.accessLogPrefix = accessLogPrefix;
}
String getAccessLogPrefix() {
return this.accessLogPrefix;
}
void setAccessLogSuffix(String accessLogSuffix) {
this.accessLogSuffix = accessLogSuffix;
}
void setAccessLogEnabled(boolean accessLogEnabled) {
this.accessLogEnabled = accessLogEnabled;
}
boolean isAccessLogEnabled() {
return this.accessLogEnabled;
}
void setAccessLogRotate(boolean accessLogRotate) {
this.accessLogRotate = accessLogRotate;
}
void setUseForwardHeaders(boolean useForwardHeaders) {
this.useForwardHeaders = useForwardHeaders;
}
boolean isUseForwardHeaders() {
return this.useForwardHeaders;
}
Builder createBuilder(AbstractConfigurableWebServerFactory factory) {
Ssl ssl = factory.getSsl();
InetAddress address = factory.getAddress();
int port = factory.getPort();
Builder builder = Undertow.builder();
if (this.bufferSize != null) {
builder.setBufferSize(this.bufferSize);
}
if (this.ioThreads != null) {
builder.setIoThreads(this.ioThreads);
}
if (this.workerThreads != null) {
builder.setWorkerThreads(this.workerThreads);
}
if (this.directBuffers != null) {
builder.setDirectBuffers(this.directBuffers);
}
if (ssl != null && ssl.isEnabled()) {
new SslBuilderCustomizer(factory.getPort(), address, ssl, factory.getSslStoreProvider()).customize(builder);
Http2 http2 = factory.getHttp2();
if (http2 != null) {
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, http2.isEnabled());
}
}
else {
builder.addHttpListener(port, (address != null) ? address.getHostAddress() : "0.0.0.0");
}
builder.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 0);
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
return builder;
}
List<HttpHandlerFactory> createHttpHandlerFactories(AbstractConfigurableWebServerFactory webServerFactory,
HttpHandlerFactory... initialHttpHandlerFactories) {
List<HttpHandlerFactory> factories = createHttpHandlerFactories(webServerFactory.getCompression(),
this.useForwardHeaders, webServerFactory.getServerHeader(),
webServerFactory.getShutdown().getGracePeriod(), initialHttpHandlerFactories);
if (isAccessLogEnabled()) {
factories.add(new AccessLogHttpHandlerFactory(this.accessLogDirectory, this.accessLogPattern,
this.accessLogPrefix, this.accessLogSuffix, this.accessLogRotate));
}
return factories;
}
static List<HttpHandlerFactory> createHttpHandlerFactories(Compression compression, boolean useForwardHeaders,
String serverHeader, Duration shutdownGracePeriod, HttpHandlerFactory... initialHttpHandlerFactories) {
List<HttpHandlerFactory> factories = new ArrayList<HttpHandlerFactory>();
factories.addAll(Arrays.asList(initialHttpHandlerFactories));
if (compression != null && compression.getEnabled()) {
factories.add(new CompressionHttpHandlerFactory(compression));
}
if (useForwardHeaders) {
factories.add(Handlers::proxyPeerAddress);
}
if (StringUtils.hasText(serverHeader)) {
factories.add((next) -> Handlers.header(next, "Server", serverHeader));
}
if (shutdownGracePeriod != null) {
factories.add((next) -> new GracefulShutdownHttpHandler(next, shutdownGracePeriod));
}
return factories;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment