diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/TomcatCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/TomcatCustomizer.java index e5f0f599b7..3d49a1f2d3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/TomcatCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/tomcat/TomcatCustomizer.java @@ -20,11 +20,14 @@ import java.time.Duration; import org.apache.catalina.Lifecycle; import org.apache.catalina.valves.AccessLogValve; +import org.apache.catalina.valves.ErrorReportValve; import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.AbstractProtocol; import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http11.AbstractHttp11Protocol; +import org.springframework.boot.autoconfigure.web.ErrorProperties; +import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.context.properties.PropertyMapper; @@ -88,6 +91,7 @@ public final class TomcatCustomizer { .when(TomcatCustomizer::isPositive) .to((acceptCount) -> customizeAcceptCount(factory, acceptCount)); customizeStaticResources(serverProperties.getTomcat().getResource(), factory); + customizeErrorReportValve(serverProperties.getError(), factory); } private static boolean isPositive(int value) { @@ -241,4 +245,16 @@ public final class TomcatCustomizer { }); } + private static void customizeErrorReportValve(ErrorProperties error, + ConfigurableTomcatWebServerFactory factory) { + if (error.getIncludeStacktrace() == IncludeStacktrace.NEVER) { + factory.addContextCustomizers((context) -> { + ErrorReportValve valve = new ErrorReportValve(); + valve.setShowServerInfo(false); + valve.setShowReport(false); + context.getParent().getPipeline().addValve(valve); + }); + } + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java index 19be920fd3..7980e6f476 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/DefaultReactiveWebServerFactoryCustomizerTests.java @@ -28,6 +28,7 @@ import org.apache.catalina.Context; import org.apache.catalina.Valve; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.valves.AccessLogValve; +import org.apache.catalina.valves.ErrorReportValve; import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.AbstractProtocol; import org.eclipse.jetty.server.NCSARequestLog; @@ -371,6 +372,24 @@ public class DefaultReactiveWebServerFactoryCustomizerTests { } } + @Test + public void errorReportValveIsConfiguredToNotReportStackTraces() { + TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); + Map map = new HashMap(); + bindProperties(map); + this.customizer.customize(factory); + Valve[] valves = ((TomcatWebServer) factory.getWebServer(mock(HttpHandler.class))) + .getTomcat().getHost().getPipeline().getValves(); + assertThat(valves).hasAtLeastOneElementOfType(ErrorReportValve.class); + for (Valve valve : valves) { + if (valve instanceof ErrorReportValve) { + ErrorReportValve errorReportValve = (ErrorReportValve) valve; + assertThat(errorReportValve.isShowReport()).isFalse(); + assertThat(errorReportValve.isShowServerInfo()).isFalse(); + } + } + } + @Test public void defaultUseForwardHeadersJetty() { JettyReactiveWebServerFactory factory = spy(new JettyReactiveWebServerFactory()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizerTests.java index 71b148ed6e..ddf4ef56f5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizerTests.java @@ -28,6 +28,7 @@ import org.apache.catalina.Context; import org.apache.catalina.Valve; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.valves.AccessLogValve; +import org.apache.catalina.valves.ErrorReportValve; import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.AbstractProtocol; import org.eclipse.jetty.server.NCSARequestLog; @@ -182,6 +183,24 @@ public class DefaultServletWebServerFactoryCustomizerTests { verify(context).setUseRelativeRedirects(true); } + @Test + public void errorReportValveIsConfiguredToNotReportStackTraces() { + TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); + Map map = new HashMap(); + bindProperties(map); + this.customizer.customize(factory); + Valve[] valves = ((TomcatWebServer) factory.getWebServer()).getTomcat().getHost() + .getPipeline().getValves(); + assertThat(valves).hasAtLeastOneElementOfType(ErrorReportValve.class); + for (Valve valve : valves) { + if (valve instanceof ErrorReportValve) { + ErrorReportValve errorReportValve = (ErrorReportValve) valve; + assertThat(errorReportValve.isShowReport()).isFalse(); + assertThat(errorReportValve.isShowServerInfo()).isFalse(); + } + } + } + @Test public void testCustomizeTomcat() { ConfigurableServletWebServerFactory factory = mock( diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java index 859d064d44..84e602716e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java @@ -133,8 +133,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac context.setLoader(loader); Tomcat.addServlet(context, "httpHandlerServlet", servlet); context.addServletMappingDecoded("/", "httpHandlerServlet"); - configureContext(context); host.addChild(context); + configureContext(context); } private void skipAllTldScanning(TomcatEmbeddedContext context) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java index 37ea7c5668..c428c64bc6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java @@ -216,8 +216,8 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); - configureContext(context, initializersToUse); host.addChild(context); + configureContext(context, initializersToUse); postProcessContext(context); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java index be7cdb4c80..68db29755a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -25,14 +25,18 @@ import org.apache.catalina.connector.Connector; import org.apache.catalina.core.AprLifecycleListener; import org.junit.Test; import org.mockito.InOrder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests; import org.springframework.http.server.reactive.HttpHandler; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link TomcatReactiveWebServerFactory}. @@ -64,6 +68,25 @@ public class TomcatReactiveWebServerFactoryTests } } + @Test + public void contextIsAddedToHostBeforeCustomizersAreCalled() throws Exception { + TomcatReactiveWebServerFactory factory = getFactory(); + TomcatContextCustomizer customizer = mock(TomcatContextCustomizer.class); + doAnswer(new Answer() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + assertThat(((Context) invocation.getArguments()[0]).getParent()) + .isNotNull(); + return null; + } + + }).when(customizer).customize(any(Context.class)); + factory.addContextCustomizers(customizer); + this.webServer = factory.getWebServer(mock(HttpHandler.class)); + verify(customizer).customize(any(Context.class)); + } + @Test public void defaultTomcatListeners() { TomcatReactiveWebServerFactory factory = getFactory(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java index a2817d061f..e8335c8aca 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java @@ -50,6 +50,8 @@ import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.mockito.InOrder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.springframework.boot.testsupport.rule.OutputCapture; import org.springframework.boot.web.server.WebServerException; @@ -61,6 +63,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -143,6 +146,25 @@ public class TomcatServletWebServerFactoryTests } } + @Test + public void contextIsAddedToHostBeforeCustomizersAreCalled() throws Exception { + TomcatServletWebServerFactory factory = getFactory(); + TomcatContextCustomizer customizer = mock(TomcatContextCustomizer.class); + doAnswer(new Answer() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + assertThat(((Context) invocation.getArguments()[0]).getParent()) + .isNotNull(); + return null; + } + + }).when(customizer).customize(any(Context.class)); + factory.addContextCustomizers(customizer); + this.webServer = factory.getWebServer(); + verify(customizer).customize(any(Context.class)); + } + @Test public void tomcatConnectorCustomizers() { TomcatServletWebServerFactory factory = getFactory();