Consolidate Undertow WebServers and simplify their constructors
Closes gh-21391 Co-authored-by: Phillip Webb <pwebb@pivotal.io>pull/21440/head
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -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