Consolidate Undertow WebServers and simplify their constructors
Closes gh-21391 Co-authored-by: Phillip Webb <pwebb@pivotal.io>
This commit is contained in:
parent
0d00947735
commit
c42571ba40
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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;
|
|||
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 Phillip Webb
|
||||
*/
|
||||
final class UndertowCompressionConfigurer {
|
||||
class CompressionHttpHandlerFactory implements HttpHandlerFactory {
|
||||
|
||||
private UndertowCompressionConfigurer() {
|
||||
private final Compression compression;
|
||||
|
||||
CompressionHttpHandlerFactory(Compression compression) {
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static HttpHandler configureCompression(Compression compression, HttpHandler httpHandler) {
|
||||
if (compression == null || !compression.getEnabled()) {
|
||||
return httpHandler;
|
||||
@Override
|
||||
public HttpHandler getHandler(HttpHandler next) {
|
||||
if (!this.compression.getEnabled()) {
|
||||
return next;
|
||||
}
|
||||
ContentEncodingRepository repository = new ContentEncodingRepository();
|
||||
repository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50,
|
||||
Predicates.and(getCompressionPredicates(compression)));
|
||||
return new EncodingHandler(repository).setNext(httpHandler);
|
||||
Predicates.and(getCompressionPredicates(this.compression)));
|
||||
return new EncodingHandler(repository).setNext(next);
|
||||
}
|
||||
|
||||
private static Predicate[] getCompressionPredicates(Compression compression) {
|
||||
|
|
@ -76,6 +74,9 @@ final class UndertowCompressionConfigurer {
|
|||
return predicates.toArray(new Predicate[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate used to match specific mime types.
|
||||
*/
|
||||
private static class CompressibleMimeTypePredicate implements Predicate {
|
||||
|
||||
private final List<MimeType> mimeTypes;
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
import io.undertow.Undertow.Builder;
|
||||
|
||||
|
|
@ -32,6 +33,14 @@ import org.springframework.boot.web.server.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
|
||||
* Undertow {@link Builder}.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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;
|
|||
|
||||
import java.time.Duration;
|
||||
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.handlers.GracefulShutdownHandler;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
|
@ -25,34 +26,33 @@ import org.apache.commons.logging.LogFactory;
|
|||
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
|
||||
*/
|
||||
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 period;
|
||||
private final Duration gracePeriod;
|
||||
|
||||
private volatile boolean shuttingDown;
|
||||
|
||||
UndertowGracefulShutdown(GracefulShutdownHandler gracefulShutdownHandler, Duration period) {
|
||||
this.gracefulShutdownHandler = gracefulShutdownHandler;
|
||||
this.period = period;
|
||||
GracefulShutdownHttpHandler(HttpHandler next, Duration period) {
|
||||
super(next);
|
||||
this.gracePeriod = period;
|
||||
}
|
||||
|
||||
@Override
|
||||
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");
|
||||
this.gracefulShutdownHandler.shutdown();
|
||||
shutdown();
|
||||
this.shuttingDown = true;
|
||||
boolean graceful = false;
|
||||
try {
|
||||
graceful = this.gracefulShutdownHandler.awaitShutdown(this.period.toMillis());
|
||||
graceful = awaitShutdown(this.gracePeriod.toMillis());
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
|
@ -16,34 +16,16 @@
|
|||
|
||||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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.GracefulShutdownHandler;
|
||||
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.boot.web.reactive.server.AbstractReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.GracefulShutdown;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ReactiveWebServerFactory} that can be used to create {@link UndertowWebServer}s.
|
||||
|
|
@ -54,29 +36,7 @@ import org.springframework.util.Assert;
|
|||
public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerFactory
|
||||
implements ConfigurableUndertowWebServerFactory {
|
||||
|
||||
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;
|
||||
private UndertowWebServerFactoryDelegate delegate = new UndertowWebServerFactoryDelegate();
|
||||
|
||||
/**
|
||||
* Create a new {@link UndertowReactiveWebServerFactory} instance.
|
||||
|
|
@ -94,187 +54,13 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
|||
}
|
||||
|
||||
@Override
|
||||
public WebServer getWebServer(org.springframework.http.server.reactive.HttpHandler httpHandler) {
|
||||
Undertow.Builder builder = createBuilder(getPort());
|
||||
HttpHandler handler = new UndertowHttpHandlerAdapter(httpHandler);
|
||||
if (this.useForwardHeaders) {
|
||||
handler = Handlers.proxyPeerAddress(handler);
|
||||
}
|
||||
handler = UndertowCompressionConfigurer.configureCompression(getCompression(), handler);
|
||||
Closeable closeable = null;
|
||||
if (isAccessLogEnabled()) {
|
||||
AccessLogHandlerConfiguration accessLogHandlerConfiguration = configureAccessLogHandler(handler);
|
||||
closeable = accessLogHandlerConfiguration.closeable;
|
||||
handler = accessLogHandlerConfiguration.accessLogHandler;
|
||||
}
|
||||
GracefulShutdown gracefulShutdown = null;
|
||||
GracefulShutdownHandler gracefulShutdownHandler = Handlers.gracefulShutdown(handler);
|
||||
Duration gracePeriod = getShutdown().getGracePeriod();
|
||||
if (gracePeriod != null) {
|
||||
gracefulShutdown = new UndertowGracefulShutdown(gracefulShutdownHandler, gracePeriod);
|
||||
handler = gracefulShutdownHandler;
|
||||
}
|
||||
else {
|
||||
gracefulShutdown = GracefulShutdown.IMMEDIATE;
|
||||
}
|
||||
builder.setHandler(handler);
|
||||
return new UndertowWebServer(builder, getPort() >= 0, closeable, gracefulShutdown);
|
||||
}
|
||||
|
||||
private Undertow.Builder createBuilder(int port) {
|
||||
Undertow.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 (getSsl() != null && getSsl().isEnabled()) {
|
||||
customizeSsl(builder);
|
||||
}
|
||||
else {
|
||||
builder.addHttpListener(port, getListenAddress());
|
||||
}
|
||||
builder.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 0);
|
||||
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
|
||||
customizer.customize(builder);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
private AccessLogHandlerConfiguration configureAccessLogHandler(HttpHandler handler) {
|
||||
try {
|
||||
createAccessLogDirectoryIfNecessary();
|
||||
XnioWorker worker = createWorker();
|
||||
String prefix = (this.accessLogPrefix != null) ? this.accessLogPrefix : "access_log.";
|
||||
DefaultAccessLogReceiver accessLogReceiver = new DefaultAccessLogReceiver(worker, this.accessLogDirectory,
|
||||
prefix, this.accessLogSuffix, this.accessLogRotate);
|
||||
String formatString = ((this.accessLogPattern != null) ? this.accessLogPattern : "common");
|
||||
AccessLogHandler accessLogHandler = new AccessLogHandler(handler, accessLogReceiver, formatString,
|
||||
Undertow.class.getClassLoader());
|
||||
return new AccessLogHandlerConfiguration(accessLogHandler, () -> {
|
||||
try {
|
||||
accessLogReceiver.close();
|
||||
worker.shutdown();
|
||||
worker.awaitTermination(30, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
}
|
||||
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);
|
||||
if (getHttp2() != null) {
|
||||
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, getHttp2().isEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
private String getListenAddress() {
|
||||
if (getAddress() == null) {
|
||||
return "0.0.0.0";
|
||||
}
|
||||
return getAddress().getHostAddress();
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseDirectBuffers(Boolean directBuffers) {
|
||||
this.directBuffers = directBuffers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@link UndertowBuilderCustomizer}s that should be applied to the Undertow
|
||||
* {@link io.undertow.Undertow.Builder Builder}. Calling this method will replace any
|
||||
* existing customizers.
|
||||
* @param customizers the customizers to set
|
||||
*/
|
||||
public void setBuilderCustomizers(Collection<? extends UndertowBuilderCustomizer> customizers) {
|
||||
Assert.notNull(customizers, "Customizers must not be null");
|
||||
this.builderCustomizers = new LinkedHashSet<>(customizers);
|
||||
this.delegate.setBuilderCustomizers(customizers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBuilderCustomizers(UndertowBuilderCustomizer... customizers) {
|
||||
this.delegate.addBuilderCustomizers(customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -283,31 +69,78 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
|||
* @return the customizers that will be applied
|
||||
*/
|
||||
public Collection<UndertowBuilderCustomizer> getBuilderCustomizers() {
|
||||
return this.builderCustomizers;
|
||||
return this.delegate.getBuilderCustomizers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link UndertowBuilderCustomizer}s that should be used to customize the
|
||||
* Undertow {@link io.undertow.Undertow.Builder 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));
|
||||
public void setBufferSize(Integer bufferSize) {
|
||||
this.delegate.setBufferSize(bufferSize);
|
||||
}
|
||||
|
||||
private static final class AccessLogHandlerConfiguration {
|
||||
@Override
|
||||
public void setIoThreads(Integer ioThreads) {
|
||||
this.delegate.setIoThreads(ioThreads);
|
||||
}
|
||||
|
||||
private final AccessLogHandler accessLogHandler;
|
||||
@Override
|
||||
public void setWorkerThreads(Integer workerThreads) {
|
||||
this.delegate.setWorkerThreads(workerThreads);
|
||||
}
|
||||
|
||||
private final Closeable closeable;
|
||||
@Override
|
||||
public void setUseDirectBuffers(Boolean directBuffers) {
|
||||
this.delegate.setUseDirectBuffers(directBuffers);
|
||||
}
|
||||
|
||||
private AccessLogHandlerConfiguration(AccessLogHandler accessLogHandler, Closeable closeable) {
|
||||
this.accessLogHandler = accessLogHandler;
|
||||
this.closeable = closeable;
|
||||
}
|
||||
@Override
|
||||
public void setUseForwardHeaders(boolean useForwardHeaders) {
|
||||
this.delegate.setUseForwardHeaders(useForwardHeaders);
|
||||
}
|
||||
|
||||
protected final boolean isUseForwardHeaders() {
|
||||
return this.delegate.isUseForwardHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogDirectory(File accessLogDirectory) {
|
||||
this.delegate.setAccessLogDirectory(accessLogDirectory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogPattern(String accessLogPattern) {
|
||||
this.delegate.setAccessLogPattern(accessLogPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogPrefix(String accessLogPrefix) {
|
||||
this.delegate.setAccessLogPrefix(accessLogPrefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogSuffix(String accessLogSuffix) {
|
||||
this.delegate.setAccessLogSuffix(accessLogSuffix);
|
||||
}
|
||||
|
||||
public boolean isAccessLogEnabled() {
|
||||
return this.delegate.isAccessLogEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogEnabled(boolean accessLogEnabled) {
|
||||
this.delegate.setAccessLogEnabled(accessLogEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogRotate(boolean accessLogRotate) {
|
||||
this.delegate.setAccessLogRotate(accessLogRotate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebServer getWebServer(org.springframework.http.server.reactive.HttpHandler httpHandler) {
|
||||
Undertow.Builder builder = this.delegate.createBuilder(this);
|
||||
List<HttpHandlerFactory> httpHandlerFactories = this.delegate.createHttpHandlerFactories(this,
|
||||
(next) -> new UndertowHttpHandlerAdapter(httpHandler));
|
||||
return new UndertowWebServer(builder, httpHandlerFactories, getPort() >= 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,31 +16,13 @@
|
|||
|
||||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import io.undertow.Handlers;
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.Undertow.Builder;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.handlers.GracefulShutdownHandler;
|
||||
import io.undertow.servlet.api.DeploymentManager;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.xnio.channels.BoundChannel;
|
||||
|
||||
import org.springframework.boot.web.server.Compression;
|
||||
import org.springframework.boot.web.server.GracefulShutdown;
|
||||
import org.springframework.boot.web.server.PortInUseException;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.boot.web.server.WebServerException;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -56,33 +38,11 @@ import org.springframework.util.StringUtils;
|
|||
* @since 2.0.0
|
||||
* @see UndertowServletWebServerFactory
|
||||
*/
|
||||
public class UndertowServletWebServer implements WebServer {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(UndertowServletWebServer.class);
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final Builder builder;
|
||||
|
||||
private final DeploymentManager manager;
|
||||
public class UndertowServletWebServer extends UndertowWebServer {
|
||||
|
||||
private final String contextPath;
|
||||
|
||||
private final boolean useForwardHeaders;
|
||||
|
||||
private final boolean autoStart;
|
||||
|
||||
private final Compression compression;
|
||||
|
||||
private final String serverHeader;
|
||||
|
||||
private final Duration shutdownGracePeriod;
|
||||
|
||||
private Undertow undertow;
|
||||
|
||||
private volatile boolean started = false;
|
||||
|
||||
private volatile GracefulShutdown gracefulShutdown;
|
||||
private final DeploymentManager manager;
|
||||
|
||||
/**
|
||||
* Create a new {@link UndertowServletWebServer} instance.
|
||||
|
|
@ -91,7 +51,10 @@ public class UndertowServletWebServer implements WebServer {
|
|||
* @param contextPath the root context path
|
||||
* @param autoStart if the server should be started
|
||||
* @param compression compression configuration
|
||||
* @deprecated since 2.3.0 in favor of
|
||||
* {@link #UndertowServletWebServer(io.undertow.Undertow.Builder, Iterable, String, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public UndertowServletWebServer(Builder builder, DeploymentManager manager, String contextPath, boolean autoStart,
|
||||
Compression compression) {
|
||||
this(builder, manager, contextPath, false, autoStart, compression);
|
||||
|
|
@ -105,7 +68,10 @@ public class UndertowServletWebServer implements WebServer {
|
|||
* @param useForwardHeaders if x-forward headers should be used
|
||||
* @param autoStart if the server should be started
|
||||
* @param compression compression configuration
|
||||
* @deprecated since 2.3.0 in favor of
|
||||
* {@link #UndertowServletWebServer(io.undertow.Undertow.Builder, Iterable, String, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public UndertowServletWebServer(Builder builder, DeploymentManager manager, String contextPath,
|
||||
boolean useForwardHeaders, boolean autoStart, Compression compression) {
|
||||
this(builder, manager, contextPath, useForwardHeaders, autoStart, compression, null);
|
||||
|
|
@ -120,274 +86,60 @@ public class UndertowServletWebServer implements WebServer {
|
|||
* @param autoStart if the server should be started
|
||||
* @param compression compression configuration
|
||||
* @param serverHeader string to be used in HTTP header
|
||||
* @deprecated since 2.3.0 in favor of
|
||||
* {@link #UndertowServletWebServer(io.undertow.Undertow.Builder, Iterable, String, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public UndertowServletWebServer(Builder builder, DeploymentManager manager, String contextPath,
|
||||
boolean useForwardHeaders, boolean autoStart, Compression compression, String serverHeader) {
|
||||
this(builder, manager, contextPath, useForwardHeaders, autoStart, compression, serverHeader, null);
|
||||
this(builder, UndertowWebServerFactoryDelegate.createHttpHandlerFactories(compression, useForwardHeaders,
|
||||
serverHeader, null, new DeploymentManagerHttpHandlerFactory(manager)), contextPath, autoStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link UndertowServletWebServer} instance.
|
||||
* @param builder the builder
|
||||
* @param manager the deployment manager
|
||||
* @param httpHandlerFactories the handler factories
|
||||
* @param contextPath the root context path
|
||||
* @param useForwardHeaders if x-forward headers should be used
|
||||
* @param autoStart if the server should be started
|
||||
* @param compression compression configuration
|
||||
* @param serverHeader string to be used in HTTP header
|
||||
* @param shutdownGracePeriod the period to wait for activity to cease when shutting
|
||||
* down the server gracefully
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public UndertowServletWebServer(Builder builder, DeploymentManager manager, String contextPath,
|
||||
boolean useForwardHeaders, boolean autoStart, Compression compression, String serverHeader,
|
||||
Duration shutdownGracePeriod) {
|
||||
this.builder = builder;
|
||||
this.manager = manager;
|
||||
public UndertowServletWebServer(Builder builder, Iterable<HttpHandlerFactory> httpHandlerFactories,
|
||||
String contextPath, boolean autoStart) {
|
||||
super(builder, httpHandlerFactories, autoStart);
|
||||
this.contextPath = contextPath;
|
||||
this.useForwardHeaders = useForwardHeaders;
|
||||
this.autoStart = autoStart;
|
||||
this.compression = compression;
|
||||
this.serverHeader = serverHeader;
|
||||
this.shutdownGracePeriod = shutdownGracePeriod;
|
||||
this.manager = findManager(httpHandlerFactories);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws WebServerException {
|
||||
synchronized (this.monitor) {
|
||||
if (this.started) {
|
||||
return;
|
||||
private DeploymentManager findManager(Iterable<HttpHandlerFactory> httpHandlerFactories) {
|
||||
for (HttpHandlerFactory httpHandlerFactory : httpHandlerFactories) {
|
||||
if (httpHandlerFactory instanceof DeploymentManagerHttpHandlerFactory) {
|
||||
return ((DeploymentManagerHttpHandlerFactory) httpHandlerFactory).getDeploymentManager();
|
||||
}
|
||||
try {
|
||||
if (!this.autoStart) {
|
||||
return;
|
||||
}
|
||||
if (this.undertow == null) {
|
||||
this.undertow = createUndertowServer();
|
||||
}
|
||||
this.undertow.start();
|
||||
this.started = true;
|
||||
UndertowServletWebServer.logger.info("Undertow started on port(s) " + getPortsDescription()
|
||||
+ " with context path '" + this.contextPath + "'");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
try {
|
||||
PortInUseException.ifPortBindingException(ex, (bindException) -> {
|
||||
List<Port> failedPorts = getConfiguredPorts();
|
||||
failedPorts.removeAll(getActualPorts());
|
||||
if (failedPorts.size() == 1) {
|
||||
throw new PortInUseException(failedPorts.get(0).getNumber());
|
||||
}
|
||||
});
|
||||
throw new WebServerException("Unable to start embedded Undertow", ex);
|
||||
}
|
||||
finally {
|
||||
stopSilently();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DeploymentManager getDeploymentManager() {
|
||||
synchronized (this.monitor) {
|
||||
return this.manager;
|
||||
}
|
||||
}
|
||||
|
||||
private void stopSilently() {
|
||||
try {
|
||||
if (this.undertow != null) {
|
||||
this.undertow.stop();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
private Undertow createUndertowServer() throws ServletException {
|
||||
HttpHandler httpHandler = this.manager.start();
|
||||
httpHandler = getContextHandler(httpHandler);
|
||||
if (this.useForwardHeaders) {
|
||||
httpHandler = Handlers.proxyPeerAddress(httpHandler);
|
||||
}
|
||||
if (StringUtils.hasText(this.serverHeader)) {
|
||||
httpHandler = Handlers.header(httpHandler, "Server", this.serverHeader);
|
||||
}
|
||||
if (this.shutdownGracePeriod != null) {
|
||||
GracefulShutdownHandler gracefulShutdownHandler = Handlers.gracefulShutdown(httpHandler);
|
||||
this.gracefulShutdown = new UndertowGracefulShutdown(gracefulShutdownHandler, this.shutdownGracePeriod);
|
||||
httpHandler = gracefulShutdownHandler;
|
||||
}
|
||||
else {
|
||||
this.gracefulShutdown = GracefulShutdown.IMMEDIATE;
|
||||
}
|
||||
this.builder.setHandler(httpHandler);
|
||||
return this.builder.build();
|
||||
}
|
||||
|
||||
private HttpHandler getContextHandler(HttpHandler httpHandler) {
|
||||
HttpHandler contextHandler = UndertowCompressionConfigurer.configureCompression(this.compression, httpHandler);
|
||||
if (StringUtils.isEmpty(this.contextPath)) {
|
||||
return contextHandler;
|
||||
}
|
||||
return Handlers.path().addPrefixPath(this.contextPath, contextHandler);
|
||||
}
|
||||
|
||||
private String getPortsDescription() {
|
||||
List<Port> ports = getActualPorts();
|
||||
if (!ports.isEmpty()) {
|
||||
return StringUtils.collectionToDelimitedString(ports, " ");
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private List<Port> getActualPorts() {
|
||||
List<Port> ports = new ArrayList<>();
|
||||
try {
|
||||
if (!this.autoStart) {
|
||||
ports.add(new Port(-1, "unknown"));
|
||||
}
|
||||
else {
|
||||
for (BoundChannel channel : extractChannels()) {
|
||||
ports.add(getPortFromChannel(channel));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Continue
|
||||
}
|
||||
return ports;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<BoundChannel> extractChannels() {
|
||||
Field channelsField = ReflectionUtils.findField(Undertow.class, "channels");
|
||||
ReflectionUtils.makeAccessible(channelsField);
|
||||
return (List<BoundChannel>) ReflectionUtils.getField(channelsField, this.undertow);
|
||||
}
|
||||
|
||||
private Port getPortFromChannel(BoundChannel channel) {
|
||||
SocketAddress socketAddress = channel.getLocalAddress();
|
||||
if (socketAddress instanceof InetSocketAddress) {
|
||||
String protocol = (ReflectionUtils.findField(channel.getClass(), "ssl") != null) ? "https" : "http";
|
||||
return new Port(((InetSocketAddress) socketAddress).getPort(), protocol);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Port> getConfiguredPorts() {
|
||||
List<Port> ports = new ArrayList<>();
|
||||
for (Object listener : extractListeners()) {
|
||||
try {
|
||||
Port port = getPortFromListener(listener);
|
||||
if (port.getNumber() != 0) {
|
||||
ports.add(port);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Continue
|
||||
}
|
||||
@Override
|
||||
protected HttpHandler createHttpHandler() {
|
||||
HttpHandler handler = super.createHttpHandler();
|
||||
if (!StringUtils.isEmpty(this.contextPath)) {
|
||||
handler = Handlers.path().addPrefixPath(this.contextPath, handler);
|
||||
}
|
||||
return ports;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> extractListeners() {
|
||||
Field listenersField = ReflectionUtils.findField(Undertow.class, "listeners");
|
||||
ReflectionUtils.makeAccessible(listenersField);
|
||||
return (List<Object>) ReflectionUtils.getField(listenersField, this.undertow);
|
||||
}
|
||||
|
||||
private Port getPortFromListener(Object listener) {
|
||||
Field typeField = ReflectionUtils.findField(listener.getClass(), "type");
|
||||
ReflectionUtils.makeAccessible(typeField);
|
||||
String protocol = ReflectionUtils.getField(typeField, listener).toString();
|
||||
Field portField = ReflectionUtils.findField(listener.getClass(), "port");
|
||||
ReflectionUtils.makeAccessible(portField);
|
||||
int port = (Integer) ReflectionUtils.getField(portField, listener);
|
||||
return new Port(port, protocol);
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws WebServerException {
|
||||
synchronized (this.monitor) {
|
||||
if (!this.started) {
|
||||
return;
|
||||
}
|
||||
this.started = false;
|
||||
try {
|
||||
this.manager.stop();
|
||||
this.manager.undeploy();
|
||||
this.undertow.stop();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new WebServerException("Unable to stop undertow", ex);
|
||||
}
|
||||
protected String getStartLogMessage() {
|
||||
String message = super.getStartLogMessage();
|
||||
if (StringUtils.hasText(this.contextPath)) {
|
||||
message += " with context path '" + this.contextPath + "'";
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
List<Port> ports = getActualPorts();
|
||||
if (ports.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return ports.get(0).getNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shutDownGracefully() {
|
||||
return this.gracefulShutdown.shutDownGracefully();
|
||||
}
|
||||
|
||||
boolean inGracefulShutdown() {
|
||||
return this.gracefulShutdown.isShuttingDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* An active Undertow port.
|
||||
*/
|
||||
private static final class Port {
|
||||
|
||||
private final int number;
|
||||
|
||||
private final String protocol;
|
||||
|
||||
private Port(int number, String protocol) {
|
||||
this.number = number;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
int getNumber() {
|
||||
return this.number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Port other = (Port) obj;
|
||||
return this.number == other.number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.number + " (" + this.protocol + ")";
|
||||
}
|
||||
|
||||
public DeploymentManager getDeploymentManager() {
|
||||
return this.manager;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,29 +25,19 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EventListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.ServletContainerInitializer;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.Undertow.Builder;
|
||||
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.server.handlers.resource.FileResourceManager;
|
||||
import io.undertow.server.handlers.resource.Resource;
|
||||
import io.undertow.server.handlers.resource.ResourceChangeListener;
|
||||
|
|
@ -57,17 +47,12 @@ import io.undertow.server.session.SessionManager;
|
|||
import io.undertow.servlet.Servlets;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.api.DeploymentManager;
|
||||
import io.undertow.servlet.api.ListenerInfo;
|
||||
import io.undertow.servlet.api.MimeMapping;
|
||||
import io.undertow.servlet.api.ServletContainerInitializerInfo;
|
||||
import io.undertow.servlet.api.ServletStackTraces;
|
||||
import io.undertow.servlet.core.DeploymentImpl;
|
||||
import io.undertow.servlet.handlers.DefaultServlet;
|
||||
import io.undertow.servlet.util.ImmediateInstanceFactory;
|
||||
import org.xnio.OptionMap;
|
||||
import org.xnio.Options;
|
||||
import org.xnio.Xnio;
|
||||
import org.xnio.XnioWorker;
|
||||
|
||||
import org.springframework.boot.web.server.ErrorPage;
|
||||
import org.springframework.boot.web.server.MimeMappings.Mapping;
|
||||
|
|
@ -100,34 +85,12 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
|
||||
private static final Set<Class<?>> NO_CLASSES = Collections.emptySet();
|
||||
|
||||
private Set<UndertowBuilderCustomizer> builderCustomizers = new LinkedHashSet<>();
|
||||
private UndertowWebServerFactoryDelegate delegate = new UndertowWebServerFactoryDelegate();
|
||||
|
||||
private Set<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers = new LinkedHashSet<>();
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
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;
|
||||
|
||||
private boolean eagerInitFilters = true;
|
||||
|
||||
/**
|
||||
|
|
@ -158,14 +121,14 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
getJsp().setRegistered(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Override
|
||||
public void setBuilderCustomizers(Collection<? extends UndertowBuilderCustomizer> customizers) {
|
||||
Assert.notNull(customizers, "Customizers must not be null");
|
||||
this.builderCustomizers = new LinkedHashSet<>(customizers);
|
||||
this.delegate.setBuilderCustomizers(customizers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBuilderCustomizers(UndertowBuilderCustomizer... customizers) {
|
||||
this.delegate.addBuilderCustomizers(customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -174,13 +137,74 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
* @return the customizers that will be applied
|
||||
*/
|
||||
public Collection<UndertowBuilderCustomizer> getBuilderCustomizers() {
|
||||
return this.builderCustomizers;
|
||||
return this.delegate.getBuilderCustomizers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBuilderCustomizers(UndertowBuilderCustomizer... customizers) {
|
||||
Assert.notNull(customizers, "Customizers must not be null");
|
||||
this.builderCustomizers.addAll(Arrays.asList(customizers));
|
||||
public void setBufferSize(Integer bufferSize) {
|
||||
this.delegate.setBufferSize(bufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIoThreads(Integer ioThreads) {
|
||||
this.delegate.setIoThreads(ioThreads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWorkerThreads(Integer workerThreads) {
|
||||
this.delegate.setWorkerThreads(workerThreads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseDirectBuffers(Boolean directBuffers) {
|
||||
this.delegate.setUseDirectBuffers(directBuffers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogDirectory(File accessLogDirectory) {
|
||||
this.delegate.setAccessLogDirectory(accessLogDirectory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogPattern(String accessLogPattern) {
|
||||
this.delegate.setAccessLogPattern(accessLogPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogPrefix(String accessLogPrefix) {
|
||||
this.delegate.setAccessLogPrefix(accessLogPrefix);
|
||||
}
|
||||
|
||||
public String getAccessLogPrefix() {
|
||||
return this.delegate.getAccessLogPrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogSuffix(String accessLogSuffix) {
|
||||
this.delegate.setAccessLogSuffix(accessLogSuffix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogEnabled(boolean accessLogEnabled) {
|
||||
this.delegate.setAccessLogEnabled(accessLogEnabled);
|
||||
}
|
||||
|
||||
public boolean isAccessLogEnabled() {
|
||||
return this.delegate.isAccessLogEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessLogRotate(boolean accessLogRotate) {
|
||||
this.delegate.setAccessLogRotate(accessLogRotate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseForwardHeaders(boolean useForwardHeaders) {
|
||||
this.delegate.setUseForwardHeaders(useForwardHeaders);
|
||||
}
|
||||
|
||||
protected final boolean isUseForwardHeaders() {
|
||||
return this.delegate.isUseForwardHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -194,15 +218,6 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
this.deploymentInfoCustomizers = new LinkedHashSet<>(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link UndertowDeploymentInfoCustomizer}s that should be used to customize the
|
||||
* Undertow {@link DeploymentInfo}.
|
||||
|
|
@ -213,56 +228,47 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
this.deploymentInfoCustomizers.addAll(Arrays.asList(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 setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if filters should be initialized eagerly.
|
||||
* @return {@code true} if filters are initialized eagerly, otherwise {@code false}.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public boolean isEagerInitFilters() {
|
||||
return this.eagerInitFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether filters should be initialized eagerly.
|
||||
* @param eagerInitFilters {@code true} if filters are initialized eagerly, otherwise
|
||||
* {@code false}.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public void setEagerInitFilters(boolean eagerInitFilters) {
|
||||
this.eagerInitFilters = eagerInitFilters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebServer getWebServer(ServletContextInitializer... initializers) {
|
||||
DeploymentManager manager = createDeploymentManager(initializers);
|
||||
int port = getPort();
|
||||
Builder builder = createBuilder(port);
|
||||
return getUndertowWebServer(builder, manager, port);
|
||||
Builder builder = this.delegate.createBuilder(this);
|
||||
DeploymentManager manager = createManager(initializers);
|
||||
return getUndertowWebServer(builder, manager, getPort());
|
||||
}
|
||||
|
||||
private Builder createBuilder(int port) {
|
||||
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 (getSsl() != null && getSsl().isEnabled()) {
|
||||
customizeSsl(builder);
|
||||
}
|
||||
else {
|
||||
builder.addHttpListener(port, getListenAddress());
|
||||
}
|
||||
builder.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 0);
|
||||
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
|
||||
customizer.customize(builder);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void customizeSsl(Builder builder) {
|
||||
new SslBuilderCustomizer(getPort(), getAddress(), getSsl(), getSslStoreProvider()).customize(builder);
|
||||
if (getHttp2() != null) {
|
||||
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, getHttp2().isEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
private String getListenAddress() {
|
||||
if (getAddress() == null) {
|
||||
return "0.0.0.0";
|
||||
}
|
||||
return getAddress().getHostAddress();
|
||||
}
|
||||
|
||||
private DeploymentManager createDeploymentManager(ServletContextInitializer... initializers) {
|
||||
private DeploymentManager createManager(ServletContextInitializer... initializers) {
|
||||
DeploymentInfo deployment = Servlets.deployment();
|
||||
registerServletContainerInitializerToDriveServletContextInitializers(deployment, initializers);
|
||||
deployment.setClassLoader(getServletClassLoader());
|
||||
|
|
@ -281,9 +287,6 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
for (UndertowDeploymentInfoCustomizer customizer : this.deploymentInfoCustomizers) {
|
||||
customizer.customize(deployment);
|
||||
}
|
||||
if (isAccessLogEnabled()) {
|
||||
configureAccessLog(deployment);
|
||||
}
|
||||
if (getSession().isPersistent()) {
|
||||
File dir = getValidSessionStoreDir();
|
||||
deployment.setSessionPersistenceManager(new FileSessionPersistence(dir));
|
||||
|
|
@ -305,42 +308,6 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
return timeoutDuration == null || timeoutDuration.isZero() || timeoutDuration.isNegative();
|
||||
}
|
||||
|
||||
private void configureAccessLog(DeploymentInfo deploymentInfo) {
|
||||
try {
|
||||
createAccessLogDirectoryIfNecessary();
|
||||
XnioWorker worker = createWorker();
|
||||
String prefix = (this.accessLogPrefix != null) ? this.accessLogPrefix : "access_log.";
|
||||
DefaultAccessLogReceiver accessLogReceiver = new DefaultAccessLogReceiver(worker, this.accessLogDirectory,
|
||||
prefix, this.accessLogSuffix, this.accessLogRotate);
|
||||
EventListener listener = new AccessLogShutdownListener(worker, accessLogReceiver);
|
||||
deploymentInfo.addListener(
|
||||
new ListenerInfo(AccessLogShutdownListener.class, new ImmediateInstanceFactory<>(listener)));
|
||||
deploymentInfo
|
||||
.addInitialHandlerChainWrapper((handler) -> createAccessLogHandler(handler, accessLogReceiver));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to create AccessLogHandler", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private AccessLogHandler createAccessLogHandler(HttpHandler handler, AccessLogReceiver accessLogReceiver) {
|
||||
createAccessLogDirectoryIfNecessary();
|
||||
String formatString = (this.accessLogPattern != null) ? this.accessLogPattern : "common";
|
||||
return new AccessLogHandler(handler, accessLogReceiver, formatString, Undertow.class.getClassLoader());
|
||||
}
|
||||
|
||||
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 addLocaleMappings(DeploymentInfo deployment) {
|
||||
getLocaleCharsetMappings().forEach(
|
||||
(locale, charset) -> deployment.addLocaleCharsetMapping(locale.toString(), charset.toString()));
|
||||
|
|
@ -449,99 +416,30 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
* @return a new {@link UndertowServletWebServer} instance
|
||||
*/
|
||||
protected UndertowServletWebServer getUndertowWebServer(Builder builder, DeploymentManager manager, int port) {
|
||||
return new UndertowServletWebServer(builder, manager, getContextPath(), isUseForwardHeaders(), port >= 0,
|
||||
getCompression(), getServerHeader(), getShutdown().getGracePeriod());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
public String getAccessLogPrefix() {
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean isAccessLogEnabled() {
|
||||
return this.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;
|
||||
List<HttpHandlerFactory> httpHandlerFactories = this.delegate.createHttpHandlerFactories(this,
|
||||
new DeploymentManagerHttpHandlerFactory(manager));
|
||||
return new UndertowServletWebServer(builder, httpHandlerFactories, getContextPath(), port >= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if filters should be initialized eagerly.
|
||||
* @return {@code true} if filters are initialized eagerly, otherwise {@code false}.
|
||||
* @since 2.0.0
|
||||
* {@link ServletContainerInitializer} to initialize {@link ServletContextInitializer
|
||||
* ServletContextInitializers}.
|
||||
*/
|
||||
public boolean isEagerInitFilters() {
|
||||
return this.eagerInitFilters;
|
||||
}
|
||||
private static class Initializer implements ServletContainerInitializer {
|
||||
|
||||
private final ServletContextInitializer[] initializers;
|
||||
|
||||
Initializer(ServletContextInitializer[] initializers) {
|
||||
this.initializers = initializers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
|
||||
for (ServletContextInitializer initializer : this.initializers) {
|
||||
initializer.onStartup(servletContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether filters should be initialized eagerly.
|
||||
* @param eagerInitFilters {@code true} if filters are initialized eagerly, otherwise
|
||||
* {@code false}.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public void setEagerInitFilters(boolean eagerInitFilters) {
|
||||
this.eagerInitFilters = eagerInitFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -603,26 +501,8 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
}
|
||||
|
||||
/**
|
||||
* {@link ServletContainerInitializer} to initialize {@link ServletContextInitializer
|
||||
* ServletContextInitializers}.
|
||||
* {@link ResourceManager} to hide Spring Boot loader classes.
|
||||
*/
|
||||
private static class Initializer implements ServletContainerInitializer {
|
||||
|
||||
private final ServletContextInitializer[] initializers;
|
||||
|
||||
Initializer(ServletContextInitializer[] initializers) {
|
||||
this.initializers = initializers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
|
||||
for (ServletContextInitializer initializer : this.initializers) {
|
||||
initializer.onStartup(servletContext);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class LoaderHidingResourceManager implements ResourceManager {
|
||||
|
||||
private final ResourceManager delegate;
|
||||
|
|
@ -661,36 +541,4 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
|
||||
}
|
||||
|
||||
private static class AccessLogShutdownListener implements ServletContextListener {
|
||||
|
||||
private final XnioWorker worker;
|
||||
|
||||
private final DefaultAccessLogReceiver accessLogReceiver;
|
||||
|
||||
AccessLogShutdownListener(XnioWorker worker, DefaultAccessLogReceiver accessLogReceiver) {
|
||||
this.worker = worker;
|
||||
this.accessLogReceiver = accessLogReceiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
try {
|
||||
this.accessLogReceiver.close();
|
||||
this.worker.shutdown();
|
||||
this.worker.awaitTermination(30, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,17 @@
|
|||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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.LogFactory;
|
||||
import org.xnio.channels.BoundChannel;
|
||||
|
|
@ -32,6 +36,7 @@ import org.springframework.boot.web.server.GracefulShutdown;
|
|||
import org.springframework.boot.web.server.PortInUseException;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.boot.web.server.WebServerException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
|
@ -55,16 +60,18 @@ public class UndertowWebServer implements WebServer {
|
|||
|
||||
private final Undertow.Builder builder;
|
||||
|
||||
private final Iterable<HttpHandlerFactory> httpHandlerFactories;
|
||||
|
||||
private final boolean autoStart;
|
||||
|
||||
private final Closeable closeable;
|
||||
|
||||
private final GracefulShutdown gracefulShutdown;
|
||||
|
||||
private Undertow undertow;
|
||||
|
||||
private volatile boolean started = false;
|
||||
|
||||
private volatile GracefulShutdown gracefulShutdown;
|
||||
|
||||
private volatile List<Closeable> closeables;
|
||||
|
||||
/**
|
||||
* Create a new {@link UndertowWebServer} instance.
|
||||
* @param builder the builder
|
||||
|
|
@ -80,25 +87,26 @@ public class UndertowWebServer implements WebServer {
|
|||
* @param autoStart if the server should be started
|
||||
* @param closeable called when the server is stopped
|
||||
* @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) {
|
||||
this(builder, autoStart, closeable, GracefulShutdown.IMMEDIATE);
|
||||
this(builder, Collections.singleton(new CloseableHttpHandlerFactory(closeable)), autoStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link UndertowWebServer} instance.
|
||||
* @param builder the builder
|
||||
* @param httpHandlerFactories the handler factories
|
||||
* @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
|
||||
*/
|
||||
public UndertowWebServer(Undertow.Builder builder, boolean autoStart, Closeable closeable,
|
||||
GracefulShutdown gracefulShutdown) {
|
||||
public UndertowWebServer(Undertow.Builder builder, Iterable<HttpHandlerFactory> httpHandlerFactories,
|
||||
boolean autoStart) {
|
||||
this.builder = builder;
|
||||
this.httpHandlerFactories = httpHandlerFactories;
|
||||
this.autoStart = autoStart;
|
||||
this.closeable = closeable;
|
||||
this.gracefulShutdown = gracefulShutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -112,11 +120,12 @@ public class UndertowWebServer implements WebServer {
|
|||
return;
|
||||
}
|
||||
if (this.undertow == null) {
|
||||
this.undertow = this.builder.build();
|
||||
this.undertow = createUndertowServer();
|
||||
}
|
||||
this.undertow.start();
|
||||
this.started = true;
|
||||
logger.info("Undertow started on port(s) " + getPortsDescription());
|
||||
String message = getStartLogMessage();
|
||||
logger.info(message);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
try {
|
||||
|
|
@ -140,7 +149,7 @@ public class UndertowWebServer implements WebServer {
|
|||
try {
|
||||
if (this.undertow != null) {
|
||||
this.undertow.stop();
|
||||
this.closeable.close();
|
||||
this.closeables.forEach(this::closeSilently);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
|
@ -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() {
|
||||
List<UndertowWebServer.Port> ports = getActualPorts();
|
||||
if (!ports.isEmpty()) {
|
||||
|
|
@ -156,11 +196,11 @@ public class UndertowWebServer implements WebServer {
|
|||
return "unknown";
|
||||
}
|
||||
|
||||
private List<UndertowWebServer.Port> getActualPorts() {
|
||||
List<UndertowWebServer.Port> ports = new ArrayList<>();
|
||||
private List<Port> getActualPorts() {
|
||||
List<Port> ports = new ArrayList<>();
|
||||
try {
|
||||
if (!this.autoStart) {
|
||||
ports.add(new UndertowWebServer.Port(-1, "unknown"));
|
||||
ports.add(new Port(-1, "unknown"));
|
||||
}
|
||||
else {
|
||||
for (BoundChannel channel : extractChannels()) {
|
||||
|
|
@ -192,10 +232,13 @@ public class UndertowWebServer implements WebServer {
|
|||
}
|
||||
|
||||
private List<UndertowWebServer.Port> getConfiguredPorts() {
|
||||
List<UndertowWebServer.Port> ports = new ArrayList<>();
|
||||
List<Port> ports = new ArrayList<>();
|
||||
for (Object listener : extractListeners()) {
|
||||
try {
|
||||
ports.add(getPortFromListener(listener));
|
||||
Port port = getPortFromListener(listener);
|
||||
if (port.getNumber() != 0) {
|
||||
ports.add(port);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Continue
|
||||
|
|
@ -230,8 +273,8 @@ public class UndertowWebServer implements WebServer {
|
|||
this.started = false;
|
||||
try {
|
||||
this.undertow.stop();
|
||||
if (this.closeable != null) {
|
||||
this.closeable.close();
|
||||
for (Closeable closeable : this.closeables) {
|
||||
closeable.close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
|
@ -242,7 +285,7 @@ public class UndertowWebServer implements WebServer {
|
|||
|
||||
@Override
|
||||
public int getPort() {
|
||||
List<UndertowWebServer.Port> ports = getActualPorts();
|
||||
List<Port> ports = getActualPorts();
|
||||
if (ports.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -251,11 +294,15 @@ public class UndertowWebServer implements WebServer {
|
|||
|
||||
@Override
|
||||
public boolean shutDownGracefully() {
|
||||
return (this.gracefulShutdown != null) && this.gracefulShutdown.shutDownGracefully();
|
||||
return (this.gracefulShutdown != null) ? this.gracefulShutdown.shutDownGracefully() : false;
|
||||
}
|
||||
|
||||
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 {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue