This commit is contained in:
Phillip Webb 2020-05-13 13:24:36 -07:00
parent 4d521e712f
commit 12381467da
23 changed files with 775 additions and 517 deletions

View File

@ -20,6 +20,7 @@ import java.util.Map;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
@ -60,16 +61,16 @@ public class ManagementErrorEndpoint {
private ErrorAttributeOptions getErrorAttributeOptions(ServletWebRequest request) {
ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
if (this.errorProperties.isIncludeException()) {
options = options.including(ErrorAttributeOptions.Include.EXCEPTION);
options = options.including(Include.EXCEPTION);
}
if (includeStackTrace(request)) {
options = options.including(ErrorAttributeOptions.Include.STACK_TRACE);
options = options.including(Include.STACK_TRACE);
}
if (includeMessage(request)) {
options = options.including(ErrorAttributeOptions.Include.MESSAGE);
options = options.including(Include.MESSAGE);
}
if (includeBindingErrors(request)) {
options = options.including(ErrorAttributeOptions.Include.BINDING_ERRORS);
options = options.including(Include.BINDING_ERRORS);
}
return options;
}

View File

@ -16,7 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.web.servlet;
import java.util.HashMap;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
@ -30,6 +30,7 @@ import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link ManagementErrorEndpoint}.
@ -108,27 +109,27 @@ class ManagementErrorEndpointTests {
@Test
void errorResponseWithCustomErrorAttributesUsingDeprecatedApi() {
ErrorAttributes attributes = new ErrorAttributes() {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> response = new HashMap<>();
response.put("message", "An error occurred");
return response;
return Collections.singletonMap("message", "An error occurred");
}
@Override
public Throwable getError(WebRequest webRequest) {
return null;
}
};
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(attributes, this.errorProperties);
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(new MockHttpServletRequest()));
assertThat(response).hasSize(1);
assertThat(response).containsEntry("message", "An error occurred");
assertThat(response).containsExactly(entry("message", "An error occurred"));
}
@Test
void errorResponseWithDefaultErrorAttributesSubclassUsingDeprecatedApiAndDelegation() {
ErrorAttributes attributes = new DefaultErrorAttributes() {
@Override
@SuppressWarnings("deprecation")
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
@ -138,6 +139,7 @@ class ManagementErrorEndpointTests {
response.remove("path");
return response;
}
};
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(attributes, this.errorProperties);
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(new MockHttpServletRequest()));
@ -150,18 +152,16 @@ class ManagementErrorEndpointTests {
@Test
void errorResponseWithDefaultErrorAttributesSubclassUsingDeprecatedApiWithoutDelegation() {
ErrorAttributes attributes = new DefaultErrorAttributes() {
@Override
@SuppressWarnings("deprecation")
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> response = new HashMap<>();
response.put("error", "custom error");
return response;
return Collections.singletonMap("error", "custom error");
}
};
ManagementErrorEndpoint endpoint = new ManagementErrorEndpoint(attributes, this.errorProperties);
Map<String, Object> response = endpoint.invoke(new ServletWebRequest(new MockHttpServletRequest()));
assertThat(response).hasSize(1);
assertThat(response).containsEntry("error", "custom error");
assertThat(response).containsExactly(entry("error", "custom error"));
}
}

View File

@ -56,12 +56,21 @@ final class RemoteHttpClientTransport extends HttpClientTransport {
static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory) {
String host = environment.get(DOCKER_HOST);
if (host == null || Files.exists(Paths.get(host))) {
if (host == null || isLocalFileReference(host)) {
return null;
}
return create(environment, sslContextFactory, HttpHost.create(host));
}
private static boolean isLocalFileReference(String host) {
try {
return Files.exists(Paths.get(host));
}
catch (Exception ex) {
return false;
}
}
private static RemoteHttpClientTransport create(Environment environment, SslContextFactory sslContextFactory,
HttpHost tcpHost) {
HttpClientBuilder builder = HttpClients.custom();

View File

@ -0,0 +1,102 @@
/*
* 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.jetty;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.springframework.boot.web.server.GracefulShutdownCallback;
import org.springframework.boot.web.server.GracefulShutdownResult;
import org.springframework.core.log.LogMessage;
/**
* Handles Jetty graceful shutdown.
*
* @author Andy Wilkinson
*/
final class GracefulShutdown {
private static final Log logger = LogFactory.getLog(JettyWebServer.class);
private final Server server;
private final Supplier<Integer> activeRequests;
private volatile boolean shuttingDown = false;
GracefulShutdown(Server server, Supplier<Integer> activeRequests) {
this.server = server;
this.activeRequests = activeRequests;
}
void shutDownGracefully(GracefulShutdownCallback callback) {
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
for (Connector connector : this.server.getConnectors()) {
shutdown(connector);
}
this.shuttingDown = true;
new Thread(() -> awaitShutdown(callback), "jetty-shutdown").start();
}
private void shutdown(Connector connector) {
try {
connector.shutdown().get();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (ExecutionException ex) {
// Continue
}
}
private void awaitShutdown(GracefulShutdownCallback callback) {
while (this.shuttingDown && this.activeRequests.get() > 0) {
sleep(100);
}
this.shuttingDown = false;
long activeRequests = this.activeRequests.get();
if (activeRequests == 0) {
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
else {
logger.info(LogMessage.format("Graceful shutdown aborted with %d request(s) still active", activeRequests));
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
}
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
void abort() {
this.shuttingDown = false;
}
}

View File

@ -20,8 +20,6 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
@ -286,12 +284,11 @@ public class JettyWebServer implements WebServer {
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
if (this.gracefulShutdown != null) {
this.gracefulShutdown.shutDownGracefully(callback);
}
else {
if (this.gracefulShutdown == null) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
return;
}
this.gracefulShutdown.shutDownGracefully(callback);
}
/**
@ -302,68 +299,4 @@ public class JettyWebServer implements WebServer {
return this.server;
}
static final class GracefulShutdown {
private static final Log logger = LogFactory.getLog(GracefulShutdown.class);
private final Server server;
private final Supplier<Integer> activeRequests;
private volatile boolean shuttingDown = false;
private GracefulShutdown(Server server, Supplier<Integer> activeRequests) {
this.server = server;
this.activeRequests = activeRequests;
}
private void shutDownGracefully(GracefulShutdownCallback callback) {
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
for (Connector connector : this.server.getConnectors()) {
shutdown(connector);
}
this.shuttingDown = true;
new Thread(() -> {
while (this.shuttingDown && this.activeRequests.get() > 0) {
try {
Thread.sleep(100);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
this.shuttingDown = false;
long activeRequests = this.activeRequests.get();
if (activeRequests == 0) {
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
else {
if (logger.isInfoEnabled()) {
logger.info("Graceful shutdown aborted with " + activeRequests + " request(s) still active");
}
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
}
}, "jetty-shutdown").start();
}
private void shutdown(Connector connector) {
try {
connector.shutdown().get();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (ExecutionException ex) {
// Continue
}
}
private void abort() {
this.shuttingDown = false;
}
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.netty;
import java.time.Duration;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.netty.DisposableServer;
import org.springframework.boot.web.server.GracefulShutdownCallback;
import org.springframework.boot.web.server.GracefulShutdownResult;
/**
* Handles Netty graceful shutdown.
*
* @author Andy Wilkinson
*/
final class GracefulShutdown {
private static final Log logger = LogFactory.getLog(GracefulShutdown.class);
private final Supplier<DisposableServer> disposableServer;
private volatile Thread shutdownThread;
private volatile boolean shuttingDown;
GracefulShutdown(Supplier<DisposableServer> disposableServer) {
this.disposableServer = disposableServer;
}
void shutDownGracefully(GracefulShutdownCallback callback) {
DisposableServer server = this.disposableServer.get();
if (server == null) {
return;
}
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
this.shutdownThread = new Thread(() -> doShutdown(callback, server), "netty-shutdown");
this.shutdownThread.start();
}
private void doShutdown(GracefulShutdownCallback callback, DisposableServer server) {
this.shuttingDown = true;
try {
server.disposeNow(Duration.ofMillis(Long.MAX_VALUE));
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
catch (Exception ex) {
logger.info("Graceful shutdown aborted with one or more active requests");
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
}
finally {
this.shutdownThread = null;
this.shuttingDown = false;
}
}
void abort() {
Thread shutdownThread = this.shutdownThread;
if (shutdownThread != null) {
while (!this.shuttingDown) {
sleep(50);
}
this.shutdownThread.interrupt();
}
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}

View File

@ -21,7 +21,6 @@ import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.unix.Errors.NativeIoException;
@ -125,12 +124,11 @@ public class NettyWebServer implements WebServer {
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
if (this.gracefulShutdown != null) {
this.gracefulShutdown.shutDownGracefully(callback);
}
else {
if (this.gracefulShutdown == null) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
return;
}
this.gracefulShutdown.shutDownGracefully(callback);
}
private DisposableServer startHttpServer() {
@ -197,60 +195,4 @@ public class NettyWebServer implements WebServer {
return 0;
}
private static final class GracefulShutdown {
private static final Log logger = LogFactory.getLog(GracefulShutdown.class);
private final Supplier<DisposableServer> disposableServer;
private volatile Thread shutdownThread;
private volatile boolean shuttingDown;
private GracefulShutdown(Supplier<DisposableServer> disposableServer) {
this.disposableServer = disposableServer;
}
private void shutDownGracefully(GracefulShutdownCallback callback) {
DisposableServer server = this.disposableServer.get();
if (server == null) {
return;
}
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
this.shutdownThread = new Thread(() -> {
this.shuttingDown = true;
try {
server.disposeNow(Duration.ofMillis(Long.MAX_VALUE));
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
catch (Exception ex) {
logger.info("Graceful shutdown aborted with one or more active requests");
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
}
finally {
this.shutdownThread = null;
this.shuttingDown = false;
}
}, "netty-shutdown");
this.shutdownThread.start();
}
private void abort() {
Thread shutdownThread = this.shutdownThread;
if (shutdownThread != null) {
while (!this.shuttingDown) {
try {
Thread.sleep(50);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
this.shutdownThread.interrupt();
}
}
}
}

View File

@ -0,0 +1,116 @@
/*
* 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.tomcat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.catalina.Container;
import org.apache.catalina.Service;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.web.server.GracefulShutdownCallback;
import org.springframework.boot.web.server.GracefulShutdownResult;
/**
* Handles Tomcat graceful shutdown.
*
* @author Andy Wilkinson
*/
final class GracefulShutdown {
private static final Log logger = LogFactory.getLog(GracefulShutdown.class);
private final Tomcat tomcat;
private volatile boolean aborted = false;
GracefulShutdown(Tomcat tomcat) {
this.tomcat = tomcat;
}
void shutDownGracefully(GracefulShutdownCallback callback) {
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
new Thread(() -> doShutdown(callback), "tomcat-shutdown").start();
}
private void doShutdown(GracefulShutdownCallback callback) {
List<Connector> connectors = getConnectors();
connectors.forEach(this::close);
try {
for (Container host : this.tomcat.getEngine().findChildren()) {
for (Container context : host.findChildren()) {
while (isActive(context)) {
if (this.aborted) {
logger.info("Graceful shutdown aborted with one or more requests still active");
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
return;
}
Thread.sleep(50);
}
}
}
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
private List<Connector> getConnectors() {
List<Connector> connectors = new ArrayList<>();
for (Service service : this.tomcat.getServer().findServices()) {
Collections.addAll(connectors, service.findConnectors());
}
return connectors;
}
private void close(Connector connector) {
connector.pause();
connector.getProtocolHandler().closeServerSocketGraceful();
}
private boolean isActive(Container context) {
try {
if (((StandardContext) context).getInProgressAsyncCount() > 0) {
return true;
}
for (Container wrapper : context.findChildren()) {
if (((StandardWrapper) wrapper).getCountAllocated() > 0) {
return true;
}
}
return false;
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
void abort() {
this.aborted = true;
}
}

View File

@ -179,7 +179,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
int port = Math.max(getPort(), 0);
connector.setPort(port);
if (StringUtils.hasText(getServerHeader())) {
connector.setAttribute("server", getServerHeader());
connector.setProperty("server", getServerHeader());
}
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());

View File

@ -301,7 +301,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
int port = Math.max(getPort(), 0);
connector.setPort(port);
if (StringUtils.hasText(getServerHeader())) {
connector.setAttribute("server", getServerHeader());
connector.setProperty("server", getServerHeader());
}
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());

View File

@ -16,11 +16,8 @@
package org.springframework.boot.web.embedded.tomcat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@ -35,8 +32,6 @@ import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Service;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -388,86 +383,11 @@ public class TomcatWebServer implements WebServer {
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
if (this.gracefulShutdown != null) {
this.gracefulShutdown.shutDownGracefully(callback);
}
else {
if (this.gracefulShutdown == null) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
return;
}
}
private static final class GracefulShutdown {
private static final Log logger = LogFactory.getLog(GracefulShutdown.class);
private final Tomcat tomcat;
private volatile boolean aborted = false;
private GracefulShutdown(Tomcat tomcat) {
this.tomcat = tomcat;
}
private void shutDownGracefully(GracefulShutdownCallback callback) {
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
new Thread(() -> {
List<Connector> connectors = getConnectors();
for (Connector connector : connectors) {
connector.pause();
connector.getProtocolHandler().closeServerSocketGraceful();
}
try {
for (Container host : this.tomcat.getEngine().findChildren()) {
for (Container context : host.findChildren()) {
while (active(context)) {
if (this.aborted) {
logger.info("Graceful shutdown aborted with one or more requests still active");
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
return;
}
Thread.sleep(50);
}
}
}
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}, "tomcat-shutdown").start();
}
private void abort() {
this.aborted = true;
}
private boolean active(Container context) {
try {
if (((StandardContext) context).getInProgressAsyncCount() > 0) {
return true;
}
for (Container wrapper : context.findChildren()) {
if (((StandardWrapper) wrapper).getCountAllocated() > 0) {
return true;
}
}
return false;
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private List<Connector> getConnectors() {
List<Connector> connectors = new ArrayList<>();
for (Service service : this.tomcat.getServer().findServices()) {
Collections.addAll(connectors, service.findConnectors());
}
return connectors;
}
this.gracefulShutdown.shutDownGracefully(callback);
}
}

View File

@ -302,15 +302,14 @@ public class UndertowWebServer implements WebServer {
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
if (this.gracefulShutdown != null) {
logger.info("Commencing graceful shutdown. Wait for active requests to complete");
this.gracefulShutdownCallback.set(callback);
this.gracefulShutdown.shutdown();
this.gracefulShutdown.addShutdownListener((success) -> notifyGracefulCallback(success));
}
else {
if (this.gracefulShutdown == null) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
return;
}
logger.info("Commencing graceful shutdown. Wait for active requests to complete");
this.gracefulShutdownCallback.set(callback);
this.gracefulShutdown.shutdown();
this.gracefulShutdown.addShutdownListener((success) -> notifyGracefulCallback(success));
}
private void notifyGracefulCallback(boolean success) {

View File

@ -16,10 +16,6 @@
package org.springframework.boot.web.reactive.context;
import java.util.function.Supplier;
import reactor.core.publisher.Mono;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.availability.AvailabilityChangeEvent;
@ -28,11 +24,7 @@ import org.springframework.boot.web.context.ConfigurableWebServerApplicationCont
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.SmartLifecycle;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@ -45,7 +37,7 @@ import org.springframework.util.StringUtils;
public class ReactiveWebServerApplicationContext extends GenericReactiveWebApplicationContext
implements ConfigurableWebServerApplicationContext {
private volatile ServerManager serverManager;
private volatile WebServerManager serverManager;
private String serverNamespace;
@ -70,9 +62,9 @@ public class ReactiveWebServerApplicationContext extends GenericReactiveWebAppli
super.refresh();
}
catch (RuntimeException ex) {
ServerManager serverManager = this.serverManager;
WebServerManager serverManager = this.serverManager;
if (serverManager != null) {
serverManager.server.stop();
serverManager.getWebServer().stop();
}
throw ex;
}
@ -90,12 +82,12 @@ public class ReactiveWebServerApplicationContext extends GenericReactiveWebAppli
}
private void createWebServer() {
ServerManager serverManager = this.serverManager;
WebServerManager serverManager = this.serverManager;
if (serverManager == null) {
String webServerFactoryBeanName = getWebServerFactoryBeanName();
ReactiveWebServerFactory webServerFactory = getWebServerFactory(webServerFactoryBeanName);
boolean lazyInit = getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit();
this.serverManager = new ServerManager(webServerFactory, this::getHttpHandler, lazyInit);
this.serverManager = new WebServerManager(this, webServerFactory, this::getHttpHandler, lazyInit);
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.serverManager));
getBeanFactory().registerSingleton("webServerStartStop",
@ -155,8 +147,8 @@ public class ReactiveWebServerApplicationContext extends GenericReactiveWebAppli
*/
@Override
public WebServer getWebServer() {
ServerManager serverManager = this.serverManager;
return (serverManager != null) ? serverManager.server : null;
WebServerManager serverManager = this.serverManager;
return (serverManager != null) ? serverManager.getWebServer() : null;
}
@Override
@ -169,160 +161,4 @@ public class ReactiveWebServerApplicationContext extends GenericReactiveWebAppli
this.serverNamespace = serverNamespace;
}
/**
* Internal class used to manage the server and the {@link HttpHandler}, taking care
* not to initialize the handler too early.
*/
final class ServerManager {
private final WebServer server;
private DelayedInitializationHttpHandler handler;
private ServerManager(ReactiveWebServerFactory factory, Supplier<HttpHandler> handlerSupplier,
boolean lazyInit) {
Assert.notNull(factory, "ReactiveWebServerFactory must not be null");
this.handler = new DelayedInitializationHttpHandler(handlerSupplier, lazyInit);
this.server = factory.getWebServer(this.handler);
}
private void start() {
this.handler.initializeHandler();
this.server.start();
ReactiveWebServerApplicationContext.this.publishEvent(
new ReactiveWebServerInitializedEvent(this.server, ReactiveWebServerApplicationContext.this));
}
void shutDownGracefully(Runnable callback) {
this.server.shutDownGracefully((result) -> callback.run());
}
void stop() {
this.server.stop();
}
HttpHandler getHandler() {
return this.handler;
}
}
static final class DelayedInitializationHttpHandler implements HttpHandler {
private final Supplier<HttpHandler> handlerSupplier;
private final boolean lazyInit;
private volatile HttpHandler delegate = this::handleUninitialized;
private DelayedInitializationHttpHandler(Supplier<HttpHandler> handlerSupplier, boolean lazyInit) {
this.handlerSupplier = handlerSupplier;
this.lazyInit = lazyInit;
}
private Mono<Void> handleUninitialized(ServerHttpRequest request, ServerHttpResponse response) {
throw new IllegalStateException("The HttpHandler has not yet been initialized");
}
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
return this.delegate.handle(request, response);
}
private void initializeHandler() {
this.delegate = this.lazyInit ? new LazyHttpHandler(Mono.fromSupplier(this.handlerSupplier))
: this.handlerSupplier.get();
}
HttpHandler getHandler() {
return this.delegate;
}
}
/**
* {@link HttpHandler} that initializes its delegate on first request.
*/
private static final class LazyHttpHandler implements HttpHandler {
private final Mono<HttpHandler> delegate;
private LazyHttpHandler(Mono<HttpHandler> delegate) {
this.delegate = delegate;
}
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
return this.delegate.flatMap((handler) -> handler.handle(request, response));
}
}
private static final class WebServerStartStopLifecycle implements SmartLifecycle {
private final ServerManager serverManager;
private volatile boolean running;
private WebServerStartStopLifecycle(ServerManager serverManager) {
this.serverManager = serverManager;
}
@Override
public void start() {
this.serverManager.start();
this.running = true;
}
@Override
public void stop() {
this.running = false;
this.serverManager.stop();
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE - 1;
}
}
private static final class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
private final ServerManager serverManager;
private volatile boolean running;
private WebServerGracefulShutdownLifecycle(ServerManager serverManager) {
this.serverManager = serverManager;
}
@Override
public void start() {
this.running = true;
}
@Override
public void stop() {
throw new UnsupportedOperationException("Stop must not be invoked directly");
}
@Override
public void stop(Runnable callback) {
this.running = false;
this.serverManager.shutDownGracefully(callback);
}
@Override
public boolean isRunning() {
return this.running;
}
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.reactive.context;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.SmartLifecycle;
/**
* {@link SmartLifecycle} to trigger {@link WebServer} graceful shutdown in a
* {@link ReactiveWebServerApplicationContext}.
*
* @author Andy Wilkinson
*/
class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
private final WebServerManager serverManager;
private volatile boolean running;
WebServerGracefulShutdownLifecycle(WebServerManager serverManager) {
this.serverManager = serverManager;
}
@Override
public void start() {
this.running = true;
}
@Override
public void stop() {
throw new UnsupportedOperationException("Stop must not be invoked directly");
}
@Override
public void stop(Runnable callback) {
this.running = false;
this.serverManager.shutDownGracefully(callback);
}
@Override
public boolean isRunning() {
return this.running;
}
}

View File

@ -0,0 +1,128 @@
/*
* 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.reactive.context;
import java.util.function.Supplier;
import reactor.core.publisher.Mono;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
/**
* Internal class used to manage the server and the {@link HttpHandler}, taking care not
* to initialize the handler too early.
*
* @author Andy Wilkinson
*/
class WebServerManager {
private final ReactiveWebServerApplicationContext applicationContext;
private final DelayedInitializationHttpHandler handler;
private final WebServer webServer;
WebServerManager(ReactiveWebServerApplicationContext applicationContext, ReactiveWebServerFactory factory,
Supplier<HttpHandler> handlerSupplier, boolean lazyInit) {
this.applicationContext = applicationContext;
Assert.notNull(factory, "Factory must not be null");
this.handler = new DelayedInitializationHttpHandler(handlerSupplier, lazyInit);
this.webServer = factory.getWebServer(this.handler);
}
void start() {
this.handler.initializeHandler();
this.webServer.start();
this.applicationContext
.publishEvent(new ReactiveWebServerInitializedEvent(this.webServer, this.applicationContext));
}
void shutDownGracefully(Runnable callback) {
this.webServer.shutDownGracefully((result) -> callback.run());
}
void stop() {
this.webServer.stop();
}
WebServer getWebServer() {
return this.webServer;
}
HttpHandler getHandler() {
return this.handler;
}
/**
* A delayed {@link HttpHandler} that doesn't initialize things too early.
*/
static final class DelayedInitializationHttpHandler implements HttpHandler {
private final Supplier<HttpHandler> handlerSupplier;
private final boolean lazyInit;
private volatile HttpHandler delegate = this::handleUninitialized;
private DelayedInitializationHttpHandler(Supplier<HttpHandler> handlerSupplier, boolean lazyInit) {
this.handlerSupplier = handlerSupplier;
this.lazyInit = lazyInit;
}
private Mono<Void> handleUninitialized(ServerHttpRequest request, ServerHttpResponse response) {
throw new IllegalStateException("The HttpHandler has not yet been initialized");
}
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
return this.delegate.handle(request, response);
}
void initializeHandler() {
this.delegate = this.lazyInit ? new LazyHttpHandler(this.handlerSupplier) : this.handlerSupplier.get();
}
HttpHandler getHandler() {
return this.delegate;
}
}
/**
* {@link HttpHandler} that initializes its delegate on first request.
*/
private static final class LazyHttpHandler implements HttpHandler {
private final Mono<HttpHandler> delegate;
private LazyHttpHandler(Supplier<HttpHandler> handlerSupplier) {
this.delegate = Mono.fromSupplier(handlerSupplier);
}
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
return this.delegate.flatMap((handler) -> handler.handle(request, response));
}
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.reactive.context;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.SmartLifecycle;
/**
* {@link SmartLifecycle} to start and stop the {@link WebServer} in a
* {@link ReactiveWebServerApplicationContext}.
*
* @author Andy Wilkinson
*/
class WebServerStartStopLifecycle implements SmartLifecycle {
private final WebServerManager weServerManager;
private volatile boolean running;
WebServerStartStopLifecycle(WebServerManager weServerManager) {
this.weServerManager = weServerManager;
}
@Override
public void start() {
this.weServerManager.start();
this.running = true;
}
@Override
public void stop() {
this.running = false;
this.weServerManager.stop();
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE - 1;
}
}

View File

@ -83,24 +83,6 @@ public class DefaultErrorAttributes implements ErrorAttributes {
this.includeException = includeException;
}
@Override
@Deprecated
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path());
Throwable error = getError(request);
MergedAnnotation<ResponseStatus> responseStatusAnnotation = MergedAnnotations
.from(error.getClass(), SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation);
errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
errorAttributes.put("message", determineMessage(error, responseStatusAnnotation));
errorAttributes.put("requestId", request.exchange().getRequest().getId());
handleException(errorAttributes, determineException(error));
return errorAttributes;
}
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(request, options.isIncluded(Include.STACK_TRACE));
@ -122,6 +104,24 @@ public class DefaultErrorAttributes implements ErrorAttributes {
return errorAttributes;
}
@Override
@Deprecated
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path());
Throwable error = getError(request);
MergedAnnotation<ResponseStatus> responseStatusAnnotation = MergedAnnotations
.from(error.getClass(), SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation);
errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
errorAttributes.put("message", determineMessage(error, responseStatusAnnotation));
errorAttributes.put("requestId", request.exchange().getRequest().getId());
handleException(errorAttributes, determineException(error), includeStackTrace);
return errorAttributes;
}
private HttpStatus determineHttpStatus(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
if (error instanceof ResponseStatusException) {
return ((ResponseStatusException) error).getStatus();
@ -157,9 +157,11 @@ public class DefaultErrorAttributes implements ErrorAttributes {
errorAttributes.put("trace", stackTrace.toString());
}
private void handleException(Map<String, Object> errorAttributes, Throwable error) {
private void handleException(Map<String, Object> errorAttributes, Throwable error, boolean includeStackTrace) {
errorAttributes.put("exception", error.getClass().getName());
addStackTrace(errorAttributes, error);
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
if (error instanceof BindingResult) {
BindingResult result = (BindingResult) error;
if (result.hasErrors()) {

View File

@ -48,7 +48,6 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ContextLoader;
@ -177,7 +176,8 @@ public class ServletWebServerApplicationContext extends GenericWebApplicationCon
this.webServer = factory.getWebServer(getSelfInitializer());
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
@ -370,72 +370,4 @@ public class ServletWebServerApplicationContext extends GenericWebApplicationCon
}
private final class WebServerStartStopLifecycle implements SmartLifecycle {
private final WebServer webServer;
private volatile boolean running;
private WebServerStartStopLifecycle(WebServer webServer) {
this.webServer = webServer;
}
@Override
public void start() {
this.webServer.start();
this.running = true;
ServletWebServerApplicationContext.this.publishEvent(
new ServletWebServerInitializedEvent(this.webServer, ServletWebServerApplicationContext.this));
}
@Override
public void stop() {
this.webServer.stop();
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE - 1;
}
}
private final class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
private final WebServer webServer;
private volatile boolean running;
WebServerGracefulShutdownLifecycle(WebServer webServer) {
this.webServer = webServer;
}
@Override
public void start() {
this.running = true;
}
@Override
public void stop() {
throw new UnsupportedOperationException("Stop must not be invoked directly");
}
@Override
public void stop(Runnable callback) {
this.running = false;
this.webServer.shutDownGracefully((result) -> callback.run());
}
@Override
public boolean isRunning() {
return this.running;
}
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.servlet.context;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.SmartLifecycle;
/**
* {@link SmartLifecycle} to trigger {@link WebServer} graceful shutdown in a
* {@link ServletWebServerApplicationContext}.
*
* @author Andy Wilkinson
*/
class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
private final WebServer webServer;
private volatile boolean running;
WebServerGracefulShutdownLifecycle(WebServer webServer) {
this.webServer = webServer;
}
@Override
public void start() {
this.running = true;
}
@Override
public void stop() {
throw new UnsupportedOperationException("Stop must not be invoked directly");
}
@Override
public void stop(Runnable callback) {
this.running = false;
this.webServer.shutDownGracefully((result) -> callback.run());
}
@Override
public boolean isRunning() {
return this.running;
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.servlet.context;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.SmartLifecycle;
/**
* {@link SmartLifecycle} to start and stop the {@link WebServer} in a
* {@link ServletWebServerApplicationContext}.
*
* @author Andy Wilkinson
*/
class WebServerStartStopLifecycle implements SmartLifecycle {
private final ServletWebServerApplicationContext applicationContext;
private final WebServer webServer;
private volatile boolean running;
WebServerStartStopLifecycle(ServletWebServerApplicationContext applicationContext, WebServer webServer) {
this.applicationContext = applicationContext;
this.webServer = webServer;
}
@Override
public void start() {
this.webServer.start();
this.running = true;
this.applicationContext
.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}
@Override
public void stop() {
this.webServer.stop();
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE - 1;
}
}

View File

@ -105,17 +105,6 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
request.setAttribute(ERROR_ATTRIBUTE, ex);
}
@Override
@Deprecated
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
@ -137,6 +126,17 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
return errorAttributes;
}
@Override
@Deprecated
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
Integer status = getAttribute(requestAttributes, RequestDispatcher.ERROR_STATUS_CODE);
if (status == null) {
@ -154,14 +154,17 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException
}
}
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest) {
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,
boolean includeStackTrace) {
Throwable error = getError(webRequest);
if (error != null) {
while (error instanceof ServletException && error.getCause() != null) {
error = error.getCause();
}
errorAttributes.put("exception", error.getClass().getName());
addStackTrace(errorAttributes, error);
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
}
addErrorMessage(errorAttributes, webRequest, error);
}

View File

@ -129,7 +129,6 @@ class NettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactor
@Disabled("Flaky due to https://github.com/reactor/reactor-netty/issues/1093")
@Override
protected void whenARequestRemainsInFlightThenShutDownGracefullyDoesNotInvokeCallbackUntilTheRequestCompletes() {
}
protected Mono<String> testSslWithAlias(String alias) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -18,7 +18,7 @@ package org.springframework.boot.web.reactive.context;
import org.junit.jupiter.api.Test;
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.DelayedInitializationHttpHandler;
import org.springframework.boot.web.reactive.context.WebServerManager.DelayedInitializationHttpHandler;
import org.springframework.boot.web.reactive.context.config.ExampleReactiveWebServerApplicationConfiguration;
import org.springframework.boot.web.reactive.server.MockReactiveWebServerFactory;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;