diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java index 1687b696c47..66b72769f77 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java @@ -30,11 +30,14 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -55,6 +58,7 @@ import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfigurat import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -104,6 +108,8 @@ public class EndpointWebMvcAutoConfiguration private static final Log logger = LogFactory .getLog(EndpointWebMvcAutoConfiguration.class); + private static final ConfigurableListableBeanFactory BeanDefinitionRegistry = null; + private ApplicationContext applicationContext; private BeanFactory beanFactory; @@ -164,7 +170,7 @@ public class EndpointWebMvcAutoConfiguration } private void createChildManagementContext() { - final AnnotationConfigEmbeddedWebApplicationContext childContext = new AnnotationConfigEmbeddedWebApplicationContext(); + AnnotationConfigEmbeddedWebApplicationContext childContext = new AnnotationConfigEmbeddedWebApplicationContext(); childContext.setParent(this.applicationContext); childContext.setNamespace("management"); childContext.setId(this.applicationContext.getId() + ":management"); @@ -172,12 +178,30 @@ public class EndpointWebMvcAutoConfiguration PropertyPlaceholderAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class, DispatcherServletAutoConfiguration.class); + registerEmbeddedServletContainerFactory(childContext); CloseEventPropagationListener.addIfPossible(this.applicationContext, childContext); childContext.refresh(); managementContextResolver().setApplicationContext(childContext); } + private void registerEmbeddedServletContainerFactory( + AnnotationConfigEmbeddedWebApplicationContext childContext) { + try { + EmbeddedServletContainerFactory servletContainerFactory = this.applicationContext + .getBean(EmbeddedServletContainerFactory.class); + ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory(); + if (beanFactory instanceof BeanDefinitionRegistry) { + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + registry.registerBeanDefinition("embeddedServletContainerFactory", + new RootBeanDefinition(servletContainerFactory.getClass())); + } + } + catch (NoSuchBeanDefinitionException ex) { + // Ignore and assume auto-configuration + } + } + /** * Add an alias for 'local.management.port' that actually resolves using * 'local.server.port'. diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsChannelAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsChannelAutoConfiguration.java index fd44d6b104b..61b856cad9d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsChannelAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsChannelAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -41,6 +41,7 @@ import org.springframework.messaging.MessageChannel; public class MetricsChannelAutoConfiguration { @Bean + @ExportMetricWriter @ConditionalOnMissingBean public MessageChannelMetricWriter messageChannelMetricWriter( @Qualifier("metricsChannel") MessageChannel channel) { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index 32cfce561b3..fbbf99c2bb7 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -58,7 +58,9 @@ import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebAppl import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerException; +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.testutil.Matched; @@ -176,6 +178,35 @@ public class EndpointWebMvcAutoConfigurationTests { assertAllClosed(); } + @Test + public void onDifferentPortWithSpecificContainer() throws Exception { + this.applicationContext.register(SpecificContainerConfig.class, RootConfig.class, + DifferentPortConfig.class, EndpointConfig.class, BaseConfiguration.class, + EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); + this.applicationContext.refresh(); + assertContent("/controller", ports.get().server, "controlleroutput"); + assertContent("/endpoint", ports.get().server, null); + assertContent("/controller", ports.get().management, null); + assertContent("/endpoint", ports.get().management, "endpointoutput"); + assertContent("/error", ports.get().management, startsWith("{")); + ApplicationContext managementContext = this.applicationContext + .getBean(ManagementContextResolver.class).getApplicationContext(); + List interceptors = (List) ReflectionTestUtils.getField( + managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); + assertThat(interceptors).hasSize(1); + EmbeddedServletContainerFactory parentContainerFactory = this.applicationContext + .getBean(EmbeddedServletContainerFactory.class); + EmbeddedServletContainerFactory managementContainerFactory = managementContext + .getBean(EmbeddedServletContainerFactory.class); + assertThat(parentContainerFactory) + .isInstanceOf(SpecificEmbeddedServletContainerFactory.class); + assertThat(managementContainerFactory) + .isInstanceOf(SpecificEmbeddedServletContainerFactory.class); + assertThat(managementContainerFactory).isNotSameAs(parentContainerFactory); + this.applicationContext.close(); + assertAllClosed(); + } + @Test public void onDifferentPortAndContext() throws Exception { this.applicationContext.register(RootConfig.class, EndpointConfig.class, @@ -609,6 +640,16 @@ public class EndpointWebMvcAutoConfigurationTests { } + @Configuration + public static class SpecificContainerConfig { + + @Bean + public SpecificEmbeddedServletContainerFactory embeddedServletContainerFactory() { + return new SpecificEmbeddedServletContainerFactory(); + } + + } + @Configuration @Import(ServerPortConfig.class) public static class DifferentPortConfig { @@ -636,6 +677,7 @@ public class EndpointWebMvcAutoConfigurationTests { } protected static class TestInterceptor extends HandlerInterceptorAdapter { + private int count = 0; @Override @@ -648,6 +690,7 @@ public class EndpointWebMvcAutoConfigurationTests { public int getCount() { return this.count; } + } } @@ -728,4 +771,9 @@ public class EndpointWebMvcAutoConfigurationTests { } + private static class SpecificEmbeddedServletContainerFactory + extends TomcatEmbeddedServletContainerFactory { + + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java index 8d945fb68c2..cda91b4815b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java @@ -75,6 +75,7 @@ public class MetricExportAutoConfigurationTests { PropertyPlaceholderAutoConfiguration.class); MetricExporters exporter = this.context.getBean(MetricExporters.class); assertThat(exporter).isNotNull(); + assertThat(exporter.getExporters()).containsKey("messageChannelMetricWriter"); } @Test diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index aeaad83b98a..f9e2eb40805 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -961,14 +961,30 @@ public class ServerProperties void customizeUndertow(ServerProperties serverProperties, UndertowEmbeddedServletContainerFactory factory) { - factory.setBufferSize(this.bufferSize); - factory.setBuffersPerRegion(this.buffersPerRegion); - factory.setIoThreads(this.ioThreads); - factory.setWorkerThreads(this.workerThreads); - factory.setDirectBuffers(this.directBuffers); - factory.setAccessLogDirectory(this.accesslog.dir); - factory.setAccessLogPattern(this.accesslog.pattern); - factory.setAccessLogEnabled(this.accesslog.enabled); + if (this.bufferSize != null) { + factory.setBufferSize(this.bufferSize); + } + if (this.buffersPerRegion != null) { + factory.setBuffersPerRegion(this.buffersPerRegion); + } + if (this.ioThreads != null) { + factory.setIoThreads(this.ioThreads); + } + if (this.workerThreads != null) { + factory.setWorkerThreads(this.workerThreads); + } + if (this.directBuffers != null) { + factory.setDirectBuffers(this.directBuffers); + } + if (this.accesslog.dir != null) { + factory.setAccessLogDirectory(this.accesslog.dir); + } + if (this.accesslog.pattern != null) { + factory.setAccessLogPattern(this.accesslog.pattern); + } + if (this.accesslog.enabled != null) { + factory.setAccessLogEnabled(this.accesslog.enabled); + } factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders()); } @@ -977,7 +993,7 @@ public class ServerProperties /** * Enable access log. */ - private boolean enabled = false; + private Boolean enabled; /** * Format pattern for access logs. @@ -989,11 +1005,11 @@ public class ServerProperties */ private File dir = new File("logs"); - public boolean isEnabled() { + public Boolean getEnabled() { return this.enabled; } - public void setEnabled(boolean enabled) { + public void setEnabled(Boolean enabled) { this.enabled = enabled; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index a72fcc6b1a3..c0d6ada93fe 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -48,6 +48,7 @@ import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -418,6 +419,14 @@ public class ServerPropertiesTests { verify(container).setSessionStoreDir(new File("myfolder")); } + @Test + public void skipNullElementsForUndertow() throws Exception { + UndertowEmbeddedServletContainerFactory container = mock( + UndertowEmbeddedServletContainerFactory.class); + this.properties.customize(container); + verify(container, never()).setAccessLogEnabled(anyBoolean()); + } + private void bindProperties(Map map) { new RelaxedDataBinder(this.properties, "server") .bind(new MutablePropertyValues(map)); diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedErrorHandler.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedErrorHandler.java new file mode 100644 index 00000000000..42f4ffafb56 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedErrorHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.context.embedded.jetty; + +import java.io.IOException; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.ErrorHandler; + +/** + * Variation of Jetty's {@link ErrorHandler} that supports all {@link HttpMethod + * HttpMethods} rather than just {@code GET}, {@code POST} and {@code HEAD}. Jetty + * intentionally only + * supports a limited set of HTTP methods for error pages, however, Spring Boot + * prefers Tomcat, Jetty and Undertow to all behave in the same way. + * + * @author Phillip Webb + */ +class JettyEmbeddedErrorHandler extends ErrorHandler { + + private final ErrorHandler delegate; + + JettyEmbeddedErrorHandler(ErrorHandler delegate) { + this.delegate = delegate; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, + HttpServletResponse response) throws IOException { + String method = request.getMethod(); + if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) + && !HttpMethod.HEAD.is(method)) { + request = new ErrorHttpServletRequest(request); + } + this.delegate.handle(target, baseRequest, request, response); + } + + private static class ErrorHttpServletRequest extends HttpServletRequestWrapper { + + private boolean simulateGetMethod = true; + + ErrorHttpServletRequest(HttpServletRequest request) { + super(request); + } + + @Override + public String getMethod() { + return (this.simulateGetMethod ? HttpMethod.GET.toString() + : super.getMethod()); + } + + @Override + public ServletContext getServletContext() { + this.simulateGetMethod = false; + return super.getServletContext(); + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java index ffddec54ad5..98f68ff0ff5 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java @@ -410,11 +410,14 @@ public class JettyEmbeddedServletContainerFactory */ private Configuration getErrorPageConfiguration() { return new AbstractConfiguration() { + @Override public void configure(WebAppContext context) throws Exception { ErrorHandler errorHandler = context.getErrorHandler(); + context.setErrorHandler(new JettyEmbeddedErrorHandler(errorHandler)); addJettyErrorPages(errorHandler, getErrorPages()); } + }; } @@ -424,6 +427,7 @@ public class JettyEmbeddedServletContainerFactory */ private Configuration getMimeTypeConfiguration() { return new AbstractConfiguration() { + @Override public void configure(WebAppContext context) throws Exception { MimeTypes mimeTypes = context.getMimeTypes(); @@ -432,6 +436,7 @@ public class JettyEmbeddedServletContainerFactory mapping.getMimeType()); } } + }; } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java index c50c8dffe57..efef94b3e92 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java @@ -334,6 +334,19 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { assertThat(getResponse(getLocalUrl("/bang"))).isEqualTo("Hello World"); } + @Test + public void errorPageFromPutRequest() throws Exception { + AbstractEmbeddedServletContainerFactory factory = getFactory(); + factory.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/hello")); + this.container = factory.getEmbeddedServletContainer(exampleServletRegistration(), + errorServletRegistration()); + this.container.start(); + assertThat(getResponse(getLocalUrl("/hello"), HttpMethod.PUT), + equalTo("Hello World")); + assertThat(getResponse(getLocalUrl("/bang"), HttpMethod.PUT), + equalTo("Hello World")); + } + @Test public void basicSslFromClassPath() throws Exception { testBasicSslWithKeyStore("classpath:test.jks"); @@ -869,7 +882,12 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { protected String getResponse(String url, String... headers) throws IOException, URISyntaxException { - ClientHttpResponse response = getClientResponse(url, headers); + return getResponse(url, HttpMethod.GET, headers); + } + + protected String getResponse(String url, HttpMethod method, String... headers) + throws IOException, URISyntaxException { + ClientHttpResponse response = getClientResponse(url, method, headers); try { return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8")); } @@ -881,7 +899,14 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { protected String getResponse(String url, HttpComponentsClientHttpRequestFactory requestFactory, String... headers) throws IOException, URISyntaxException { - ClientHttpResponse response = getClientResponse(url, requestFactory, headers); + return getResponse(url, HttpMethod.GET, requestFactory, headers); + } + + protected String getResponse(String url, HttpMethod method, + HttpComponentsClientHttpRequestFactory requestFactory, String... headers) + throws IOException, URISyntaxException { + ClientHttpResponse response = getClientResponse(url, method, requestFactory, + headers); try { return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8")); } @@ -892,21 +917,27 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { protected ClientHttpResponse getClientResponse(String url, String... headers) throws IOException, URISyntaxException { - return getClientResponse(url, new HttpComponentsClientHttpRequestFactory() { + return getClientResponse(url, HttpMethod.GET, headers); + } - @Override - protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) { - return AbstractEmbeddedServletContainerFactoryTests.this.httpClientContext; - } + protected ClientHttpResponse getClientResponse(String url, HttpMethod method, + String... headers) throws IOException, URISyntaxException { + return getClientResponse(url, method, + new HttpComponentsClientHttpRequestFactory() { + + @Override + protected HttpContext createHttpContext(HttpMethod httpMethod, + URI uri) { + return AbstractEmbeddedServletContainerFactoryTests.this.httpClientContext; + } - }, headers); + }, headers); } - protected ClientHttpResponse getClientResponse(String url, + protected ClientHttpResponse getClientResponse(String url, HttpMethod method, HttpComponentsClientHttpRequestFactory requestFactory, String... headers) throws IOException, URISyntaxException { - ClientHttpRequest request = requestFactory.createRequest(new URI(url), - HttpMethod.GET); + ClientHttpRequest request = requestFactory.createRequest(new URI(url), method); request.getHeaders().add("Cookie", "JSESSIONID=" + "123"); for (String header : headers) { String[] parts = header.split(":");