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