From c501b889af0483b1c73c6ea0a5793c729ab4d6bd Mon Sep 17 00:00:00 2001 From: "sopov.ivan" Date: Fri, 14 Nov 2014 11:20:14 +0300 Subject: [PATCH 1/2] Add support for using Undertow as an embedded container See gh-1779 --- .gitignore | 3 +- spring-boot-autoconfigure/pom.xml | 5 + ...ddedServletContainerAutoConfiguration.java | 19 + .../autoconfigure/web/ServerProperties.java | 68 ++- .../web/MultipartAutoConfigurationTests.java | 80 +++- ...erverPropertiesAutoConfigurationTests.java | 43 +- spring-boot-dependencies/pom.xml | 11 + spring-boot-samples/pom.xml | 2 + .../spring-boot-sample-undertow-ssl/pom.xml | 53 +++ .../SampleUndertowSslApplication.java | 29 ++ .../undertow/service/HelloWorldService.java | 32 ++ .../sample/undertow/web/SampleController.java | 37 ++ .../src/main/resources/application.properties | 4 + .../src/main/resources/sample.jks | Bin 0 -> 2264 bytes .../SampleUndertowSslApplicationTests.java | 72 ++++ .../spring-boot-sample-undertow/pom.xml | 48 +++ .../undertow/SampleUndertowApplication.java | 29 ++ .../undertow/service/HelloWorldService.java | 32 ++ .../sample/undertow/web/SampleController.java | 37 ++ .../SampleUndertowApplicationTests.java | 56 +++ spring-boot-starters/pom.xml | 1 + .../spring-boot-starter-undertow/pom.xml | 27 ++ spring-boot/pom.xml | 5 + .../UndertowEmbeddedServletContainer.java | 77 ++++ ...dertowEmbeddedServletContainerFactory.java | 396 ++++++++++++++++++ ...edServletContainerMvcIntegrationTests.java | 22 +- ...wEmbeddedServletContainerFactoryTests.java | 34 ++ 27 files changed, 1215 insertions(+), 7 deletions(-) create mode 100644 spring-boot-samples/spring-boot-sample-undertow-ssl/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/SampleUndertowSslApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/service/HelloWorldService.java create mode 100644 spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/web/SampleController.java create mode 100644 spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/resources/sample.jks create mode 100644 spring-boot-samples/spring-boot-sample-undertow-ssl/src/test/java/sample/undertow/SampleUndertowSslApplicationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-undertow/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/SampleUndertowApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/service/HelloWorldService.java create mode 100644 spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/web/SampleController.java create mode 100644 spring-boot-samples/spring-boot-sample-undertow/src/test/java/sample/undertow/SampleUndertowApplicationTests.java create mode 100644 spring-boot-starters/spring-boot-starter-undertow/pom.xml create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactoryTests.java diff --git a/.gitignore b/.gitignore index 3fa1136eb05..b4c28f3b0e0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ .classpath .project .settings +.metadata bin build lib/ @@ -28,4 +29,4 @@ overridedb.* *.iws .idea *.jar -.DS_Store \ No newline at end of file +.DS_Store diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index c040e7c1331..ccab61becd1 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -160,6 +160,11 @@ jetty-webapp true + + io.undertow + undertow-servlet + true + org.freemarker freemarker diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java index 6ed5985c33a..3461b0a94f1 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.web; +import io.undertow.Undertow; + import javax.servlet.Servlet; import org.apache.catalina.startup.Tomcat; @@ -37,6 +39,7 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomi import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -87,6 +90,22 @@ public class EmbeddedServletContainerAutoConfiguration { } } + + /** + * Nested configuration if Undertow is being used. + */ + @Configuration + @ConditionalOnClass({ Servlet.class, Undertow.class }) + @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) + public static class EmbeddedUndertow { + + @Bean + public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() { + return new UndertowEmbeddedServletContainerFactory(); + } + + } + /** * Registers a {@link EmbeddedServletContainerCustomizerBeanPostProcessor}. Registered 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 7fe8ced57ef..e9137186382 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 @@ -40,6 +40,7 @@ import org.springframework.boot.context.embedded.Ssl; import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.util.StringUtils; @@ -71,6 +72,8 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { private String servletPath = "/"; private final Tomcat tomcat = new Tomcat(); + + private final Undertow undertow = new Undertow(); private final Map contextParameters = new HashMap(); @@ -78,6 +81,10 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { return this.tomcat; } + public Undertow getUndertow() { + return this.undertow; + } + public String getContextPath() { return this.contextPath; } @@ -179,7 +186,10 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { getTomcat() .customizeTomcat((TomcatEmbeddedServletContainerFactory) container); } - + if (container instanceof UndertowEmbeddedServletContainerFactory) { + getUndertow().customizeUndertow( + (UndertowEmbeddedServletContainerFactory) container); + } container.addInitializers(new InitParameterConfiguringServletContextInitializer( getContextParameters())); } @@ -210,6 +220,62 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { return prefix + path; } + public static class Undertow { + private Integer bufferSize; + private Integer buffersPerRegion; + private Integer ioThreads; + private Integer workerThreads; + private Boolean directBuffers; + + public Integer getBufferSize() { + return this.bufferSize; + } + + public void setBufferSize(Integer bufferSize) { + this.bufferSize = bufferSize; + } + + public Integer getBuffersPerRegion() { + return this.buffersPerRegion; + } + + public void setBuffersPerRegion(Integer buffersPerRegion) { + this.buffersPerRegion = buffersPerRegion; + } + + public Integer getIoThreads() { + return this.ioThreads; + } + + public void setIoThreads(Integer ioThreads) { + this.ioThreads = ioThreads; + } + + public Integer getWorkerThreads() { + return this.workerThreads; + } + + public void setWorkerThreads(Integer workerThreads) { + this.workerThreads = workerThreads; + } + + public Boolean getDirectBuffers() { + return this.directBuffers; + } + + public void setDirectBuffers(Boolean directBuffers) { + this.directBuffers = directBuffers; + } + + void customizeUndertow(UndertowEmbeddedServletContainerFactory factory) { + factory.setBufferSize(bufferSize); + factory.setBuffersPerRegion(buffersPerRegion); + factory.setIoThreads(ioThreads); + factory.setWorkerThreads(workerThreads); + factory.setDirectBuffers(directBuffers); + } + } + public static class Tomcat { private String accessLogPattern; diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java index 3d047230d7b..bec1f66bca9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java @@ -16,6 +16,10 @@ package org.springframework.boot.autoconfigure.web; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + import javax.servlet.MultipartConfigElement; import org.junit.After; @@ -25,10 +29,16 @@ import org.junit.rules.ExpectedException; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.PropertySource; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @@ -71,10 +81,11 @@ public class MultipartAutoConfigurationTests { } @Test - public void containerWithNothing() { + public void containerWithNothing() throws Exception { this.context = new AnnotationConfigEmbeddedWebApplicationContext( ContainerWithNothing.class, BaseConfiguration.class); DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class); + verify404(); assertNotNull(servlet.getMultipartResolver()); assertThat(this.context.getBeansOfType(StandardServletMultipartResolver.class) .size(), equalTo(1)); @@ -112,6 +123,32 @@ public class MultipartAutoConfigurationTests { } } + @Test + public void containerWithNoMultipartUndertowConfiguration() { + this.context = new AnnotationConfigEmbeddedWebApplicationContext( + ContainerWithNoMultipartUndertow.class, BaseConfiguration.class); + DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class); + verifyServletWorks(); + assertNotNull(servlet.getMultipartResolver()); + assertThat(this.context.getBeansOfType(StandardServletMultipartResolver.class) + .size(), equalTo(1)); + assertThat(this.context.getBeansOfType(MultipartResolver.class).size(), + equalTo(1)); + } + + @Configuration + public static class ContainerWithNoMultipartUndertow { + @Bean + UndertowEmbeddedServletContainerFactory containerFactory() { + return new UndertowEmbeddedServletContainerFactory(); + } + + @Bean + WebController controller() { + return new WebController(); + } + } + @Test public void containerWithNoMultipartTomcatConfiguration() { this.context = new AnnotationConfigEmbeddedWebApplicationContext( @@ -148,6 +185,16 @@ public class MultipartAutoConfigurationTests { verifyServletWorks(); } + @Test + public void containerWithAutomatedMultipartUndertowConfiguration() { + this.context = new AnnotationConfigEmbeddedWebApplicationContext( + ContainerWithEverythingUndertow.class, BaseConfiguration.class); + this.context.getBean(MultipartConfigElement.class); + verifyServletWorks(); + assertSame(this.context.getBean(DispatcherServlet.class).getMultipartResolver(), + this.context.getBean(StandardServletMultipartResolver.class)); + } + @Test public void containerWithMultipartConfigDisabled() { @@ -178,6 +225,16 @@ public class MultipartAutoConfigurationTests { not(instanceOf(StandardServletMultipartResolver.class))); } + private void verify404() throws Exception { + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); + ClientHttpRequest request = requestFactory.createRequest(new URI( + "http://localhost:" + + this.context.getEmbeddedServletContainer().getPort() + "/"), + HttpMethod.GET); + ClientHttpResponse response = request.execute(); + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + } + private void verifyServletWorks() { RestTemplate restTemplate = new RestTemplate(); assertEquals("Hello", restTemplate.getForObject("http://localhost:" @@ -256,6 +313,27 @@ public class MultipartAutoConfigurationTests { } + @Configuration + @EnableWebMvc + public static class ContainerWithEverythingUndertow { + + @Bean + MultipartConfigElement multipartConfigElement() { + return new MultipartConfigElement(""); + } + + @Bean + UndertowEmbeddedServletContainerFactory containerFactory() { + return new UndertowEmbeddedServletContainerFactory(); + } + + @Bean + WebController webController() { + return new WebController(); + } + + } + public static class ContainerWithCustomMultipartResolver { @Bean diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfigurationTests.java index b2ade70eced..cf684455cef 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfigurationTests.java @@ -32,6 +32,7 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomi import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.ApplicationContextException; import org.springframework.context.annotation.Bean; @@ -97,9 +98,9 @@ public class ServerPropertiesAutoConfigurationTests { } @Test - public void customizeWithContainerFactory() throws Exception { + public void customizeWithJettyContainerFactory() throws Exception { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); - this.context.register(CustomContainerConfig.class, + this.context.register(CustomJettyContainerConfig.class, ServerPropertiesAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); @@ -111,6 +112,22 @@ public class ServerPropertiesAutoConfigurationTests { // factory should take precedence... assertEquals(3000, containerFactory.getPort()); } + + + @Test + public void customizeWithUndertowContainerFactory() throws Exception { + this.context = new AnnotationConfigEmbeddedWebApplicationContext(); + this.context.register(CustomUndertowContainerConfig.class, + ServerPropertiesAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + containerFactory = this.context + .getBean(AbstractEmbeddedServletContainerFactory.class); + ServerProperties server = this.context.getBean(ServerProperties.class); + assertNotNull(server); + assertEquals(3000, containerFactory.getPort()); + } + @Test public void customizeTomcatWithCustomizer() throws Exception { @@ -154,7 +171,7 @@ public class ServerPropertiesAutoConfigurationTests { } @Configuration - protected static class CustomContainerConfig { + protected static class CustomJettyContainerConfig { @Bean public EmbeddedServletContainerFactory containerFactory() { @@ -169,6 +186,26 @@ public class ServerPropertiesAutoConfigurationTests { } } + + @Configuration + protected static class CustomUndertowContainerConfig { + + @Bean + public EmbeddedServletContainerFactory containerFactory() { + UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory(); + factory.setPort(3000); + return factory; + } + + @Bean + public EmbeddedServletContainerCustomizerBeanPostProcessor embeddedServletContainerCustomizerBeanPostProcessor() { + return new EmbeddedServletContainerCustomizerBeanPostProcessor(); + } + + } + + + @Configuration protected static class CustomizeConfig { diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 5d09759bff0..bdeef0e508c 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -130,6 +130,7 @@ 1.7 2.0 1.6.3 + 1.1.0.Final 3.0.2 @@ -289,6 +290,11 @@ spring-boot-starter-jta-bitronix 1.2.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-starter-undertow + 1.2.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-starter-log4j @@ -701,6 +707,11 @@ tomcat-jsp-api ${tomcat.version} + + io.undertow + undertow-servlet + ${undertow.version} + org.apache.velocity velocity diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index b74248f5956..0bda3a1d39a 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -59,6 +59,8 @@ spring-boot-sample-tomcat-multi-connectors spring-boot-sample-tomcat7-jsp spring-boot-sample-traditional + spring-boot-sample-undertow + spring-boot-sample-undertow-ssl spring-boot-sample-velocity spring-boot-sample-web-freemarker spring-boot-sample-web-groovy-templates diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/pom.xml b/spring-boot-samples/spring-boot-sample-undertow-ssl/pom.xml new file mode 100644 index 00000000000..ee20b2b9d27 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.2.0.BUILD-SNAPSHOT + + spring-boot-sample-undertow-ssl + Spring Boot Undertow SSL Sample + Spring Boot Undertow SSL Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-undertow + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.httpcomponents + httpclient + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/SampleUndertowSslApplication.java b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/SampleUndertowSslApplication.java new file mode 100644 index 00000000000..dbeac870e5b --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/SampleUndertowSslApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2013 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 sample.undertow; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleUndertowSslApplication { + + public static void main(String[] args) throws Exception { + SpringApplication.run(SampleUndertowSslApplication.class, args); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/service/HelloWorldService.java b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/service/HelloWorldService.java new file mode 100644 index 00000000000..0865d25d2d9 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/service/HelloWorldService.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2013 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 sample.undertow.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class HelloWorldService { + + @Value("${name:World}") + private String name; + + public String getHelloMessage() { + return "Hello " + this.name; + } + +} diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/web/SampleController.java b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/web/SampleController.java new file mode 100644 index 00000000000..78b5c0c776e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/web/SampleController.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2013 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 sample.undertow.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import sample.undertow.service.HelloWorldService; + +@Controller +public class SampleController { + + @Autowired + private HelloWorldService helloWorldService; + + @RequestMapping("/") + @ResponseBody + public String helloWorld() { + return this.helloWorldService.getHelloMessage(); + } +} diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/resources/application.properties new file mode 100644 index 00000000000..953abe0d6c9 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/resources/application.properties @@ -0,0 +1,4 @@ +server.port = 8443 +server.ssl.key-store = classpath:sample.jks +server.ssl.key-store-password = secret +server.ssl.key-password = password \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/resources/sample.jks b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/resources/sample.jks new file mode 100644 index 0000000000000000000000000000000000000000..6aa9a28053a591e41453e665e5024e8a8cb78b3d GIT binary patch literal 2264 zcmchYX*3iJ7sqE|hQS!q5Mv)4GM2$i#uAFqC`%7x7baWA*i&dRX>3`uq(XS?3XSYp z%38`&ib7E$8j~$cF^}gt?|I+noW8#w?uYxk=iGD8|K9Vzd#pVc0002(2k@T|2@MMI zqxqr2AhQO*TVi`j@((S;e;g;l$#dAA{>vf0kX$R(Qn4oKgGEYjZ5zti2dw?Z6A zh%LuFCNI?9o+Z1duJL-++e#cjO`zlK?u9s030=k_*wD1#-$FbIDRDnA^vo@fm( zzjt(3VJrGOr0iHXSTM|rYN#>RZ@Dp`PwB2zrDQffLvuoR2~V3ReYa0&vU^dXd8isV zsAf*@!8s%xBvHLseXn6f?1kefe(8uAmAbaF$x{Ykzb6c6jdUwY1$y4tFzsj7 zIghr!T#ODfu@Po!a29@kXQ8kY#(LE<0o7?7PQ|eMeY@Equ?R-6*f@Na3o&stDQ=6( zQzDSQhCnS(9Bu9W_~giknP0vECqUsr4_9y_}nEU`cy z4}dApnAip92wMwgzciAFpc3i}+-#Zlq+iF7d1y}d4Qsp8=%l1N8NIs161I`HmkcpQ zY4*CUCFJJf(2!M{`&qQ}3($KeTQ=)mMrBs`DOb;%Of0tC)9he_p~w&CO#DfCgx(%s z{@|D(brX_Gb}ZDLmGej*JgEl0Et>q~kgTXuJg-PwvRjNx8sBbIShxD=xOySzw{;^X zAvrh5HTg>Xq@<{#^!Kg}B?qz@b<{ebD)yaSf&RChBIJQo-?Ahzw@qopSe^e&>^IuU zydM4Y1_C&>k7u|}=; z63R7$H6zat=hNExxEwXu1fQ*ytuEkP!{w{|#6TIEq1#*ck=6_NM*ILF65tmD-O5&R zMI!-MT<3U~t@}(CN4@RlZ~1I>C=!ywF)dNI{VvH;5Y3(Z4jY^%_c&fsm4Q`<1g|qX z&!h29jXjVE3nJnet*L)XL?-8<>qDbVGP%i^NwOZfwWO7?Mr!X7 zl}sG@9S_5}}td}$xrWIYY=e(VVBiv%A+M-{M z!3_^Tc=pV?niT!{D`!{e@W;MvrZ(OER{x7itVAtwE~spPtPtma|J=5dv&_oE!5H#` zdgXJ;+gJ4hI}*9QX9jpL`Gb)yCe%1}t!&O-^sihyZys%%5uF~WhsR_w(q7;vV5d4P zr%ZUA2}kO+L^2ePTgGT9Ua71w<+)poSyjTdLq&xbUn`<6&SpwFp(HRHUyU6J3WZ_! zfztko79+94Tq%mTYj53(RYcL&1~5`I#+w3`(Q|r+P(aT z%?r(^?IWw~19CB&uvXf(f7&BnEE{zwK4piVU`I4j1j?v5d4N<7VUJ8nM`$7S*mfKR z#9-JzPRZ?{M!@L+0N^V)IyeeP2T|^UK|m0QD+Ibs!wEoml^N!YO#vW~j~jraX(0A3 z6Kux?IRLez`O^X;{!4g%BhcRn>^H*qKZ3*|{_YGuz)KCJcu;)DSES5D2tDE`C02YR0R%Vy1T7k|RQ;3g<0icA$AuP0pOvc~jGl zz+NeKv_FT_;GWK&8XlDUv&hv9kxg?@c!bu?83i=YQ$S!K09Y)Glg3Hz?@|)ZCBlVz zP8i}#XZkMoje3I=h&I!!s_m?Qi@1MR`yv7X*yEs47qOs^t^?&=;*IQ!q&)gq_Sx5* z?fhU8Q*PSe*w7y)FH#P!9R^Xw!lTT+zI39L<&8cViaj$A(Z2Cg7!{V?uuyi#vlNCg z40i}2ivw&y&1-&Nh&WMG`&aIt>)(#tKTJ}^@696Kw1-{IzSOTnFF+0@k$o3%ZHS;Q#;t literal 0 HcmV?d00001 diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/test/java/sample/undertow/SampleUndertowSslApplicationTests.java b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/test/java/sample/undertow/SampleUndertowSslApplicationTests.java new file mode 100644 index 00000000000..3b450065d32 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/test/java/sample/undertow/SampleUndertowSslApplicationTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2014 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 sample.undertow; + +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContextBuilder; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.HttpClients; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import static org.junit.Assert.assertEquals; + +/** + * Basic integration tests for demo application. + * + * @author Dave Syer + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SampleUndertowSslApplication.class) +@WebAppConfiguration +@IntegrationTest("server.port:0") +@DirtiesContext +public class SampleUndertowSslApplicationTests { + + @Value("${local.server.port}") + private int port; + + @Test + public void testHome() throws Exception { + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( + new SSLContextBuilder().loadTrustMaterial(null, + new TrustSelfSignedStrategy()).build()); + + HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory) + .build(); + + TestRestTemplate testRestTemplate = new TestRestTemplate(); + ((HttpComponentsClientHttpRequestFactory) testRestTemplate.getRequestFactory()) + .setHttpClient(httpClient); + ResponseEntity entity = testRestTemplate.getForEntity( + "https://localhost:" + this.port, String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertEquals("Hello World", entity.getBody()); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-undertow/pom.xml b/spring-boot-samples/spring-boot-sample-undertow/pom.xml new file mode 100644 index 00000000000..2819c6ba943 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.2.0.BUILD-SNAPSHOT + + spring-boot-sample-undertow + Spring Boot Undertow Sample + Spring Boot Undertow Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-undertow + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/SampleUndertowApplication.java b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/SampleUndertowApplication.java new file mode 100644 index 00000000000..a81adcd1090 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/SampleUndertowApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2013 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 sample.undertow; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleUndertowApplication { + + public static void main(String[] args) throws Exception { + SpringApplication.run(SampleUndertowApplication.class, args); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/service/HelloWorldService.java b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/service/HelloWorldService.java new file mode 100644 index 00000000000..0865d25d2d9 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/service/HelloWorldService.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2013 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 sample.undertow.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class HelloWorldService { + + @Value("${name:World}") + private String name; + + public String getHelloMessage() { + return "Hello " + this.name; + } + +} diff --git a/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/web/SampleController.java b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/web/SampleController.java new file mode 100644 index 00000000000..78b5c0c776e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/web/SampleController.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2013 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 sample.undertow.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import sample.undertow.service.HelloWorldService; + +@Controller +public class SampleController { + + @Autowired + private HelloWorldService helloWorldService; + + @RequestMapping("/") + @ResponseBody + public String helloWorld() { + return this.helloWorldService.getHelloMessage(); + } +} diff --git a/spring-boot-samples/spring-boot-sample-undertow/src/test/java/sample/undertow/SampleUndertowApplicationTests.java b/spring-boot-samples/spring-boot-sample-undertow/src/test/java/sample/undertow/SampleUndertowApplicationTests.java new file mode 100644 index 00000000000..c786bf140fa --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-undertow/src/test/java/sample/undertow/SampleUndertowApplicationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2014 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 sample.undertow; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import static org.junit.Assert.assertEquals; + +/** + * Basic integration tests for demo application. + * + * @author Ivan Sopov + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SampleUndertowApplication.class) +@WebAppConfiguration +@IntegrationTest("server.port:0") +@DirtiesContext +public class SampleUndertowApplicationTests { + + @Value("${local.server.port}") + private int port; + + @Test + public void testHome() throws Exception { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:" + this.port, String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertEquals("Hello World", entity.getBody()); + } + +} diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index 014017a453d..c93402ea3a8 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -56,6 +56,7 @@ spring-boot-starter-test spring-boot-starter-thymeleaf spring-boot-starter-tomcat + spring-boot-starter-undertow spring-boot-starter-velocity spring-boot-starter-web spring-boot-starter-websocket diff --git a/spring-boot-starters/spring-boot-starter-undertow/pom.xml b/spring-boot-starters/spring-boot-starter-undertow/pom.xml new file mode 100644 index 00000000000..0bcb12fc6b5 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-undertow/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starters + 1.2.0.BUILD-SNAPSHOT + + spring-boot-starter-undertow + Spring Boot Undertow Starter + Spring Boot Undertow Starter + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + io.undertow + undertow-servlet + + + diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index 2c50461ec50..bf47c8d1413 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -129,6 +129,11 @@ jetty-util true + + io.undertow + undertow-servlet + true + org.hibernate hibernate-entitymanager diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java new file mode 100644 index 00000000000..43fb3912f53 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java @@ -0,0 +1,77 @@ +package org.springframework.boot.context.embedded.undertow; + +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.Undertow.Builder; +import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.api.DeploymentManager; + +import javax.servlet.ServletException; + +import org.springframework.boot.context.embedded.EmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerException; +import org.springframework.util.StringUtils; + +/** + * @author Ivan Sopov + */ +public class UndertowEmbeddedServletContainer implements EmbeddedServletContainer { + + private final DeploymentManager manager; + private final Builder builder; + private final String contextPath; + private final int port; + private final boolean autoStart; + private Undertow undertow; + private boolean started = false; + + public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager, + String contextPath, int port, boolean autoStart) { + this.builder = builder; + this.manager = manager; + this.contextPath = contextPath; + this.port = port; + this.autoStart = autoStart; + } + + @Override + public synchronized void start() throws EmbeddedServletContainerException { + if (!this.autoStart) { + return; + } + if (undertow == null) { + try { + HttpHandler servletHandler = manager.start(); + if (StringUtils.isEmpty(contextPath)) { + builder.setHandler(servletHandler); + } + else { + PathHandler pathHandler = Handlers.path().addPrefixPath(contextPath, + servletHandler); + builder.setHandler(pathHandler); + } + undertow = builder.build(); + } + catch (ServletException ex) { + throw new EmbeddedServletContainerException( + "Unable to start embdedded Undertow", ex); + } + } + undertow.start(); + started = true; + } + + @Override + public synchronized void stop() throws EmbeddedServletContainerException { + if (started) { + started = false; + undertow.stop(); + } + } + + @Override + public int getPort() { + return port; + } +} \ No newline at end of file diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java new file mode 100644 index 00000000000..ad112a65660 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java @@ -0,0 +1,396 @@ +package org.springframework.boot.context.embedded.undertow; + +import static io.undertow.servlet.Servlets.defaultContainer; +import static io.undertow.servlet.Servlets.deployment; +import static io.undertow.servlet.Servlets.servlet; +import static org.xnio.Options.SSL_CLIENT_AUTH_MODE; +import static org.xnio.SslClientAuthMode.NOT_REQUESTED; +import static org.xnio.SslClientAuthMode.REQUESTED; +import static org.xnio.SslClientAuthMode.REQUIRED; +import io.undertow.Undertow; +import io.undertow.Undertow.Builder; +import io.undertow.UndertowMessages; +import io.undertow.server.handlers.resource.ClassPathResourceManager; +import io.undertow.server.handlers.resource.FileResourceManager; +import io.undertow.server.handlers.resource.Resource; +import io.undertow.server.handlers.resource.ResourceChangeListener; +import io.undertow.server.handlers.resource.ResourceManager; +import io.undertow.server.handlers.resource.URLResource; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.InstanceHandle; +import io.undertow.servlet.api.ListenerInfo; +import io.undertow.servlet.api.MimeMapping; +import io.undertow.servlet.api.ServletStackTraces; +import io.undertow.servlet.handlers.DefaultServlet; +import io.undertow.servlet.util.ImmediateInstanceHandle; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; + +import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.EmbeddedServletContainer; +import org.springframework.boot.context.embedded.ErrorPage; +import org.springframework.boot.context.embedded.MimeMappings.Mapping; +import org.springframework.boot.context.embedded.ServletContextInitializer; +import org.springframework.boot.context.embedded.Ssl; +import org.springframework.boot.context.embedded.Ssl.ClientAuth; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.ResourceUtils; +import org.springframework.util.SocketUtils; + +/** + * @author Ivan Sopov + */ +public class UndertowEmbeddedServletContainerFactory extends + AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware { + + private ResourceLoader resourceLoader; + + private Integer bufferSize; + private Integer buffersPerRegion; + private Integer ioThreads; + private Integer workerThreads; + private Boolean directBuffers; + + /** + * Create a new + * {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory} + * instance. + */ + public UndertowEmbeddedServletContainerFactory() { + super(); + } + + /** + * Create a new + * {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory} + * that listens for requests using the specified port. + * + * @param port the port to listen on + */ + public UndertowEmbeddedServletContainerFactory(int port) { + super(port); + } + + /** + * Create a new + * {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory} + * with the specified context path and port. + * + * @param contextPath root the context path + * @param port the port to listen on + */ + public UndertowEmbeddedServletContainerFactory(String contextPath, int port) { + super(contextPath, port); + } + + @Override + public EmbeddedServletContainer getEmbeddedServletContainer( + ServletContextInitializer... initializers) { + DeploymentInfo servletBuilder = deployment(); + + servletBuilder.addListener(new ListenerInfo( + UndertowSpringServletContextListener.class, + new UndertowSpringServletContextListenerFactory( + new UndertowSpringServletContextListener( + mergeInitializers(initializers))))); + + if (resourceLoader != null) { + servletBuilder.setClassLoader(resourceLoader.getClassLoader()); + } + else { + servletBuilder.setClassLoader(getClass().getClassLoader()); + } + servletBuilder.setContextPath(getContextPath()); + servletBuilder.setDeploymentName("spring-boot"); + if (isRegisterDefaultServlet()) { + servletBuilder.addServlet(servlet("default", DefaultServlet.class)); + } + if (isRegisterJspServlet()) { + logger.error("JSPs are not supported with Undertow"); + } + for (ErrorPage springErrorPage : getErrorPages()) { + if (springErrorPage.getStatus() != null) { + io.undertow.servlet.api.ErrorPage undertowErrorpage = new io.undertow.servlet.api.ErrorPage( + springErrorPage.getPath(), springErrorPage.getStatusCode()); + servletBuilder.addErrorPage(undertowErrorpage); + } + else if (springErrorPage.getException() != null) { + io.undertow.servlet.api.ErrorPage undertowErrorpage = new io.undertow.servlet.api.ErrorPage( + springErrorPage.getPath(), springErrorPage.getException()); + servletBuilder.addErrorPage(undertowErrorpage); + } + else { + io.undertow.servlet.api.ErrorPage undertowErrorpage = new io.undertow.servlet.api.ErrorPage( + springErrorPage.getPath()); + servletBuilder.addErrorPage(undertowErrorpage); + } + } + servletBuilder.setServletStackTraces(ServletStackTraces.NONE); + + File root = getValidDocumentRoot(); + if (root != null && root.isDirectory()) { + servletBuilder.setResourceManager(new FileResourceManager(root, 0)); + } + else if (root != null && root.isFile()) { + servletBuilder.setResourceManager(new JarResourcemanager(root)); + } + else if (resourceLoader != null) { + servletBuilder.setResourceManager(new ClassPathResourceManager(resourceLoader + .getClassLoader(), "")); + } + else { + servletBuilder.setResourceManager(new ClassPathResourceManager(getClass() + .getClassLoader(), "")); + } + for (Mapping mimeMapping : getMimeMappings()) { + servletBuilder.addMimeMapping(new MimeMapping(mimeMapping.getExtension(), + mimeMapping.getMimeType())); + } + + DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); + + manager.deploy(); + + manager.getDeployment().getSessionManager() + .setDefaultSessionTimeout(getSessionTimeout()); + + Builder builder = Undertow.builder(); + if (bufferSize != null) { + builder.setBufferSize(bufferSize); + } + if (buffersPerRegion != null) { + builder.setBuffersPerRegion(buffersPerRegion); + } + if (ioThreads != null) { + builder.setIoThreads(ioThreads); + } + if (workerThreads != null) { + builder.setWorkerThreads(workerThreads); + } + if (directBuffers != null) { + builder.setDirectBuffers(directBuffers); + } + + int realPort = getPort(); + if (realPort == 0) { + realPort = SocketUtils.findAvailableTcpPort(40000); + } + if (getSsl() == null) { + builder.addHttpListener(realPort, "0.0.0.0"); + } + else { + try { + Ssl ssl = getSsl(); + SSLContext sslContext = SSLContext.getInstance(ssl.getProtocol()); + sslContext.init(getKeyManagers(), getTrustManagers(), null); + builder.addHttpsListener(realPort, "0.0.0.0", sslContext); + if (ssl.getClientAuth() == ClientAuth.NEED) { + builder.setSocketOption(SSL_CLIENT_AUTH_MODE, REQUIRED); + } + else if (ssl.getClientAuth() == ClientAuth.WANT) { + builder.setSocketOption(SSL_CLIENT_AUTH_MODE, REQUESTED); + } + else { + builder.setSocketOption(SSL_CLIENT_AUTH_MODE, NOT_REQUESTED); + } + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + catch (KeyManagementException e) { + throw new RuntimeException(e); + } + } + return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(), + realPort, realPort > 0); + + } + + private KeyManager[] getKeyManagers() { + try { + Ssl ssl = getSsl(); + + String keyStoreType = ssl.getKeyStoreType(); + if (keyStoreType == null) { + keyStoreType = "JKS"; + } + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + URL url = ResourceUtils.getURL(ssl.getKeyStore()); + keyStore.load(url.openStream(), ssl.getKeyStorePassword().toCharArray()); + + // Get key manager to provide client credentials. + KeyManagerFactory keyManagerFactory = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + char[] keyPassword = ssl.getKeyPassword() != null ? ssl.getKeyPassword() + .toCharArray() : ssl.getKeyStorePassword().toCharArray(); + keyManagerFactory.init(keyStore, keyPassword); + return keyManagerFactory.getKeyManagers(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + private TrustManager[] getTrustManagers() { + try { + Ssl ssl = getSsl(); + + String trustStoreType = ssl.getTrustStoreType(); + if (trustStoreType == null) { + trustStoreType = "JKS"; + } + String trustStore = ssl.getTrustStore(); + if (trustStore == null) { + return null; + } + KeyStore trustedKeyStore = KeyStore.getInstance(trustStoreType); + URL url = ResourceUtils.getURL(trustStore); + trustedKeyStore.load(url.openStream(), ssl.getTrustStorePassword() + .toCharArray()); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustedKeyStore); + return trustManagerFactory.getTrustManagers(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + } + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + public void setBufferSize(Integer bufferSize) { + this.bufferSize = bufferSize; + } + + public void setBuffersPerRegion(Integer buffersPerRegion) { + this.buffersPerRegion = buffersPerRegion; + } + + public void setIoThreads(Integer ioThreads) { + this.ioThreads = ioThreads; + } + + public void setWorkerThreads(Integer workerThreads) { + this.workerThreads = workerThreads; + } + + public void setDirectBuffers(Boolean directBuffers) { + this.directBuffers = directBuffers; + } + + private static class JarResourcemanager implements ResourceManager { + private final String jarPath; + + public JarResourcemanager(File jarFile) { + this(jarFile.getAbsolutePath()); + } + + public JarResourcemanager(String jarPath) { + this.jarPath = jarPath; + } + + @Override + public void close() throws IOException { + // no code + } + + @Override + public Resource getResource(String path) throws IOException { + URL url = new URL("jar:file:" + jarPath + "!" + path); + URLResource resource = new URLResource(url, url.openConnection(), path); + if (resource.getContentLength() < 0) { + return null; + } + return resource; + } + + @Override + public boolean isResourceChangeListenerSupported() { + return false; + } + + @Override + public void registerResourceChangeListener(ResourceChangeListener listener) { + throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); + + } + + @Override + public void removeResourceChangeListener(ResourceChangeListener listener) { + throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); + + } + + } + + private static class UndertowSpringServletContextListenerFactory implements + InstanceFactory { + + private final UndertowSpringServletContextListener listener; + + public UndertowSpringServletContextListenerFactory( + UndertowSpringServletContextListener listener) { + this.listener = listener; + } + + @Override + public InstanceHandle createInstance() + throws InstantiationException { + return new ImmediateInstanceHandle( + listener); + } + + } + + private static class UndertowSpringServletContextListener implements + ServletContextListener { + private final ServletContextInitializer[] initializers; + + public UndertowSpringServletContextListener( + ServletContextInitializer... initializers) { + this.initializers = initializers; + } + + @Override + public void contextInitialized(ServletContextEvent sce) { + try { + for (ServletContextInitializer initializer : initializers) { + initializer.onStartup(sce.getServletContext()); + } + } + catch (ServletException e) { + throw new RuntimeException(e); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + // no code + } + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerMvcIntegrationTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerMvcIntegrationTests.java index c8f9ca2969e..fd60f366a66 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerMvcIntegrationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerMvcIntegrationTests.java @@ -24,6 +24,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -68,7 +69,7 @@ public class EmbeddedServletContainerMvcIntegrationTests { TomcatConfig.class); doTest(this.context, "/hello"); } - + @Test public void jetty() throws Exception { this.context = new AnnotationConfigEmbeddedWebApplicationContext( @@ -76,6 +77,16 @@ public class EmbeddedServletContainerMvcIntegrationTests { doTest(this.context, "/hello"); } + @Test + public void undertow() throws Exception { + this.context = new AnnotationConfigEmbeddedWebApplicationContext( + UndertowConfig.class); + doTest(this.context, "/hello"); + } + + + + @Test public void advancedConfig() throws Exception { this.context = new AnnotationConfigEmbeddedWebApplicationContext( @@ -117,6 +128,15 @@ public class EmbeddedServletContainerMvcIntegrationTests { return new JettyEmbeddedServletContainerFactory(0); } } + + @Configuration + @Import(Config.class) + public static class UndertowConfig { + @Bean + public EmbeddedServletContainerFactory containerFactory() { + return new UndertowEmbeddedServletContainerFactory(0); + } + } @Configuration @EnableWebMvc diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactoryTests.java new file mode 100644 index 00000000000..a25ded4dca5 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactoryTests.java @@ -0,0 +1,34 @@ +package org.springframework.boot.context.embedded.undertow; +import org.junit.Test; +import org.springframework.boot.context.embedded.*; +import org.springframework.http.HttpStatus; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + + +/** + * Tests for {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory} and + * {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainer}. + * + * @author Ivan Sopov + */ +public class UndertowEmbeddedServletContainerFactoryTests extends AbstractEmbeddedServletContainerFactoryTests +{ + + @Override + protected UndertowEmbeddedServletContainerFactory getFactory() + { + return new UndertowEmbeddedServletContainerFactory(); + } + + @Test + public void errorPage404() throws Exception { + AbstractEmbeddedServletContainerFactory factory = getFactory(); + factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/hello")); + this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean(new ExampleServlet(), "/hello")); + this.container.start(); + assertThat(getResponse("http://localhost:8080/hello"), equalTo("Hello World")); + assertThat(getResponse("http://localhost:8080/not-found"), equalTo("Hello World")); + } +} From 1864d7907710bfad4cdcbd77763a8c1a886af06d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 18 Nov 2014 21:00:28 +0000 Subject: [PATCH 2/2] Polish Undertow contribution Closes gh-1779 --- .gitignore | 3 +- ...ddedServletContainerAutoConfiguration.java | 4 +- .../autoconfigure/web/ServerProperties.java | 121 +++--- .../web/MultipartAutoConfigurationTests.java | 3 +- ...erverPropertiesAutoConfigurationTests.java | 10 +- spring-boot-dependencies/pom.xml | 17 +- .../src/main/asciidoc/cloud-deployment.adoc | 4 +- spring-boot-docs/src/main/asciidoc/howto.adoc | 55 +++ .../main/asciidoc/spring-boot-features.adoc | 21 +- .../src/main/asciidoc/using-spring-boot.adoc | 3 + .../SampleUndertowSslApplication.java | 2 +- .../undertow/service/HelloWorldService.java | 2 +- .../sample/undertow/web/SampleController.java | 2 +- .../SampleUndertowSslApplicationTests.java | 2 +- .../undertow/SampleUndertowApplication.java | 2 +- .../undertow/service/HelloWorldService.java | 2 +- .../sample/undertow/web/SampleController.java | 2 +- .../spring-boot-starter-undertow/pom.xml | 14 + .../undertow/UndertowBuilderCustomizer.java | 35 ++ .../UndertowEmbeddedServletContainer.java | 58 ++- ...dertowEmbeddedServletContainerFactory.java | 381 +++++++++++------- .../embedded/undertow/package-info.java | 23 ++ ...edServletContainerMvcIntegrationTests.java | 8 +- ...wEmbeddedServletContainerFactoryTests.java | 102 ++++- 24 files changed, 608 insertions(+), 268 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowBuilderCustomizer.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/package-info.java diff --git a/.gitignore b/.gitignore index b4c28f3b0e0..58de8c72404 100644 --- a/.gitignore +++ b/.gitignore @@ -28,5 +28,4 @@ overridedb.* *.ipr *.iws .idea -*.jar -.DS_Store +*.jar \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java index 3461b0a94f1..925bf1ca4bc 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java @@ -54,6 +54,7 @@ import org.springframework.util.ObjectUtils; * * @author Phillip Webb * @author Dave Syer + * @author Ivan Sopov */ @Order(Ordered.HIGHEST_PRECEDENCE) @Configuration @@ -90,7 +91,7 @@ public class EmbeddedServletContainerAutoConfiguration { } } - + /** * Nested configuration if Undertow is being used. */ @@ -105,7 +106,6 @@ public class EmbeddedServletContainerAutoConfiguration { } } - /** * Registers a {@link EmbeddedServletContainerCustomizerBeanPostProcessor}. Registered 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 e9137186382..3f4f3afe12d 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 @@ -53,6 +53,7 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Stephane Nicoll * @author Andy Wilkinson + * @author Ivan Sopov */ @ConfigurationProperties(prefix = "server", ignoreUnknownFields = false) public class ServerProperties implements EmbeddedServletContainerCustomizer { @@ -72,7 +73,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { private String servletPath = "/"; private final Tomcat tomcat = new Tomcat(); - + private final Undertow undertow = new Undertow(); private final Map contextParameters = new HashMap(); @@ -220,62 +221,6 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { return prefix + path; } - public static class Undertow { - private Integer bufferSize; - private Integer buffersPerRegion; - private Integer ioThreads; - private Integer workerThreads; - private Boolean directBuffers; - - public Integer getBufferSize() { - return this.bufferSize; - } - - public void setBufferSize(Integer bufferSize) { - this.bufferSize = bufferSize; - } - - public Integer getBuffersPerRegion() { - return this.buffersPerRegion; - } - - public void setBuffersPerRegion(Integer buffersPerRegion) { - this.buffersPerRegion = buffersPerRegion; - } - - public Integer getIoThreads() { - return this.ioThreads; - } - - public void setIoThreads(Integer ioThreads) { - this.ioThreads = ioThreads; - } - - public Integer getWorkerThreads() { - return this.workerThreads; - } - - public void setWorkerThreads(Integer workerThreads) { - this.workerThreads = workerThreads; - } - - public Boolean getDirectBuffers() { - return this.directBuffers; - } - - public void setDirectBuffers(Boolean directBuffers) { - this.directBuffers = directBuffers; - } - - void customizeUndertow(UndertowEmbeddedServletContainerFactory factory) { - factory.setBufferSize(bufferSize); - factory.setBuffersPerRegion(buffersPerRegion); - factory.setIoThreads(ioThreads); - factory.setWorkerThreads(workerThreads); - factory.setDirectBuffers(directBuffers); - } - } - public static class Tomcat { private String accessLogPattern; @@ -462,4 +407,66 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { } + public static class Undertow { + + private Integer bufferSize; + + private Integer buffersPerRegion; + + private Integer ioThreads; + + private Integer workerThreads; + + private Boolean directBuffers; + + public Integer getBufferSize() { + return this.bufferSize; + } + + public void setBufferSize(Integer bufferSize) { + this.bufferSize = bufferSize; + } + + public Integer getBuffersPerRegion() { + return this.buffersPerRegion; + } + + public void setBuffersPerRegion(Integer buffersPerRegion) { + this.buffersPerRegion = buffersPerRegion; + } + + public Integer getIoThreads() { + return this.ioThreads; + } + + public void setIoThreads(Integer ioThreads) { + this.ioThreads = ioThreads; + } + + public Integer getWorkerThreads() { + return this.workerThreads; + } + + public void setWorkerThreads(Integer workerThreads) { + this.workerThreads = workerThreads; + } + + public Boolean getDirectBuffers() { + return this.directBuffers; + } + + public void setDirectBuffers(Boolean directBuffers) { + this.directBuffers = directBuffers; + } + + void customizeUndertow(UndertowEmbeddedServletContainerFactory factory) { + factory.setBufferSize(this.bufferSize); + factory.setBuffersPerRegion(this.buffersPerRegion); + factory.setIoThreads(this.ioThreads); + factory.setWorkerThreads(this.workerThreads); + factory.setDirectBuffers(this.directBuffers); + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java index bec1f66bca9..0c93d3796a1 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java @@ -16,9 +16,7 @@ package org.springframework.boot.autoconfigure.web; -import java.io.IOException; import java.net.URI; -import java.net.URISyntaxException; import javax.servlet.MultipartConfigElement; @@ -65,6 +63,7 @@ import static org.mockito.Mockito.mock; * @author Greg Turnquist * @author Dave Syer * @author Josh Long + * @author Ivan Sopov */ public class MultipartAutoConfigurationTests { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfigurationTests.java index cf684455cef..94370097288 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfigurationTests.java @@ -47,6 +47,7 @@ import static org.mockito.Mockito.verify; * Tests for {@link ServerPropertiesAutoConfiguration}. * * @author Dave Syer + * @author Ivan Sopov */ public class ServerPropertiesAutoConfigurationTests { @@ -112,8 +113,7 @@ public class ServerPropertiesAutoConfigurationTests { // factory should take precedence... assertEquals(3000, containerFactory.getPort()); } - - + @Test public void customizeWithUndertowContainerFactory() throws Exception { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); @@ -127,7 +127,6 @@ public class ServerPropertiesAutoConfigurationTests { assertNotNull(server); assertEquals(3000, containerFactory.getPort()); } - @Test public void customizeTomcatWithCustomizer() throws Exception { @@ -186,7 +185,7 @@ public class ServerPropertiesAutoConfigurationTests { } } - + @Configuration protected static class CustomUndertowContainerConfig { @@ -203,9 +202,6 @@ public class ServerPropertiesAutoConfigurationTests { } } - - - @Configuration protected static class CustomizeConfig { diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index bdeef0e508c..3154aab7ace 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -127,10 +127,10 @@ 1.2.7 1.3 8.0.15 + 1.1.0.Final 1.7 2.0 1.6.3 - 1.1.0.Final 3.0.2 @@ -540,6 +540,16 @@ metrics-servlets ${dropwizard-metrics.version} + + io.undertow + undertow-core + ${undertow.version} + + + io.undertow + undertow-servlet + ${undertow.version} + javax.cache cache-api @@ -707,11 +717,6 @@ tomcat-jsp-api ${tomcat.version} - - io.undertow - undertow-servlet - ${undertow.version} - org.apache.velocity velocity diff --git a/spring-boot-docs/src/main/asciidoc/cloud-deployment.adoc b/spring-boot-docs/src/main/asciidoc/cloud-deployment.adoc index c9919472a0c..5f66b71646d 100644 --- a/spring-boot-docs/src/main/asciidoc/cloud-deployment.adoc +++ b/spring-boot-docs/src/main/asciidoc/cloud-deployment.adoc @@ -158,8 +158,8 @@ for our starter REST application: Spring Boot makes `-D` arguments available as properties accessible from a Spring `Environment` instance. The `server.port` configuration property is fed to the embedded -Tomcat or Jetty instance which then uses it when it starts up. The `$PORT` environment -variable is assigned to us by the Heroku PaaS. +Tomcat, Jetty or Undertow instance which then uses it when it starts up. The `$PORT` +environment variable is assigned to us by the Heroku PaaS. Heroku by default will use Java 1.6. This is fine as long as your Maven or Gradle build is set to use the same version (Maven users can use the `java.version` property). If you diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 56f3ce125d1..49fe253acc1 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -556,6 +556,61 @@ of ways. Or the nuclear option is to add your own `JettyEmbeddedServletContainer +[[howto-use-undertow-instead-of-tomcat]] +=== Use Undertow instead of Tomcat +Using Undertow instead of Tomcat is very similar to <>. You need to exclude the Tomcat dependencies and include +the Undertow starter instead. + +Example in Maven: + +[source,xml,indent=0,subs="verbatim,quotes,attributes"] +---- + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-undertow + +---- + +Example in Gradle: + +[source,groovy,indent=0,subs="verbatim,quotes,attributes"] +---- + configurations { + compile.exclude module: "spring-boot-starter-tomcat" + } + + dependencies { + compile("org.springframework.boot:spring-boot-starter-web:{spring-boot-version}") + compile("org.springframework.boot:spring-boot-starter-undertow:{spring-boot-version}") + // ... + } +---- + + +[[howto-configure-undertow]] +=== Configure Undertow +Generally you can follow the advice from +_<>_ about +`@ConfigurationProperties` (`ServerProperties` and `ServerProperties.Undertow are the +main ones here), but also look at +`EmbeddedServletContainerCustomizer`. Once you have access to the +`UndertowEmbeddedServletContainerFactory` you can use an `UndertowBuilderCustomizer` to +modify Undertow's configuration to meet your needs. Or the nuclear option is to add your +own `UndertowEmbeddedServletContainerFactory`. + + + [[howto-use-tomcat-8]] === Use Tomcat 8 Tomcat 8 works with Spring Boot, but the default is to use Tomcat 7 (so we can support diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index dc873c8e437..9ad68ac2e1a 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -810,8 +810,8 @@ possible. [[boot-features-developing-web-applications]] == Developing web applications Spring Boot is well suited for web application development. You can easily create a -self-contained HTTP server using embedded Tomcat or Jetty. Most web applications will -use the `spring-boot-starter-web` module to get up and running quickly. +self-contained HTTP server using embedded Tomcat, Jetty, or Undertow. Most web +applications will use the `spring-boot-starter-web` module to get up and running quickly. If you haven't yet developed a Spring Boot web application you can follow the "Hello World!" example in the @@ -1093,9 +1093,9 @@ asks for them to be scanned in its `Filter` registration). [[boot-features-embedded-container]] === Embedded servlet container support -Spring Boot includes support for embedded Tomcat and Jetty servers. Most developers will -simply use the appropriate '`Starter POM`' to obtain a fully configured instance. By -default both Tomcat and Jetty will listen for HTTP requests on port `8080`. +Spring Boot includes support for embedded Tomcat, Jetty, and Undertow servers. Most +developers will simply use the appropriate '`Starter POM`' to obtain a fully configured +instance. By default the embedded server will listen for HTTP requests on port `8080`. @@ -1121,8 +1121,9 @@ interface. Under the hood Spring Boot uses a new type of `ApplicationContext` for embedded servlet container support. The `EmbeddedWebApplicationContext` is a special type of `WebApplicationContext` that bootstraps itself by searching for a single -`EmbeddedServletContainerFactory` bean. Usually a `TomcatEmbeddedServletContainerFactory` -or `JettyEmbeddedServletContainerFactory` will have been auto-configured. +`EmbeddedServletContainerFactory` bean. Usually a `TomcatEmbeddedServletContainerFactory`, +`JettyEmbeddedServletContainerFactory`, or `UndertowEmbeddedServletContainerFactory` will +have been auto-configured. NOTE: You usually won't need to be aware of these implementation classes. Most applications will be auto-configured and the appropriate `ApplicationContext` and @@ -1176,8 +1177,8 @@ methods. [[boot-features-customizing-configurableembeddedservletcontainerfactory-directly]] ===== Customizing ConfigurableEmbeddedServletContainer directly If the above customization techniques are too limited, you can register the -`TomcatEmbeddedServletContainerFactory` or `JettyEmbeddedServletContainerFactory` bean -yourself. +`TomcatEmbeddedServletContainerFactory`, `JettyEmbeddedServletContainerFactory` or +`UndertowEmbeddedServletContainerFactory` bean yourself. [source,java,indent=0] ---- @@ -1208,6 +1209,8 @@ packaged as an executable archive), there are some limitations in the JSP suppor * Jetty does not currently work as an embedded container with JSPs. +* Undertow does not support JSPs + There is a {github-code}/spring-boot-samples/spring-boot-sample-web-jsp[JSP sample] so you can see how to set things up. diff --git a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 0ef74468620..64f62a23c8f 100644 --- a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -348,6 +348,9 @@ swap specific technical facets. |`spring-boot-starter-tomcat` |Import Spring Boot's default HTTP engine (Tomcat). + +|`spring-boot-starter-undertow` +|Imports the Undertow HTTP engine (to be used as an alternative to Tomcat) |=== TIP: For a list of additional community contributed starter POMs, see the diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/SampleUndertowSslApplication.java b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/SampleUndertowSslApplication.java index dbeac870e5b..dbf58c94064 100644 --- a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/SampleUndertowSslApplication.java +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/SampleUndertowSslApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/service/HelloWorldService.java b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/service/HelloWorldService.java index 0865d25d2d9..5e5a3a43427 100644 --- a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/service/HelloWorldService.java +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/service/HelloWorldService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/web/SampleController.java b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/web/SampleController.java index 78b5c0c776e..3084fbd6e39 100644 --- a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/web/SampleController.java +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/main/java/sample/undertow/web/SampleController.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. diff --git a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/test/java/sample/undertow/SampleUndertowSslApplicationTests.java b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/test/java/sample/undertow/SampleUndertowSslApplicationTests.java index 3b450065d32..3d5a12a07ee 100644 --- a/spring-boot-samples/spring-boot-sample-undertow-ssl/src/test/java/sample/undertow/SampleUndertowSslApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-undertow-ssl/src/test/java/sample/undertow/SampleUndertowSslApplicationTests.java @@ -39,7 +39,7 @@ import static org.junit.Assert.assertEquals; /** * Basic integration tests for demo application. * - * @author Dave Syer + * @author Ivan Sopov */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SampleUndertowSslApplication.class) diff --git a/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/SampleUndertowApplication.java b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/SampleUndertowApplication.java index a81adcd1090..c01a048f260 100644 --- a/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/SampleUndertowApplication.java +++ b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/SampleUndertowApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. diff --git a/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/service/HelloWorldService.java b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/service/HelloWorldService.java index 0865d25d2d9..5e5a3a43427 100644 --- a/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/service/HelloWorldService.java +++ b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/service/HelloWorldService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. diff --git a/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/web/SampleController.java b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/web/SampleController.java index 78b5c0c776e..3084fbd6e39 100644 --- a/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/web/SampleController.java +++ b/spring-boot-samples/spring-boot-sample-undertow/src/main/java/sample/undertow/web/SampleController.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. diff --git a/spring-boot-starters/spring-boot-starter-undertow/pom.xml b/spring-boot-starters/spring-boot-starter-undertow/pom.xml index 0bcb12fc6b5..72f49cc8e16 100644 --- a/spring-boot-starters/spring-boot-starter-undertow/pom.xml +++ b/spring-boot-starters/spring-boot-starter-undertow/pom.xml @@ -19,9 +19,23 @@ ${basedir}/../.. + + io.undertow + undertow-core + io.undertow undertow-servlet + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.1_spec + + + + + javax.servlet + javax.servlet-api diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowBuilderCustomizer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowBuilderCustomizer.java new file mode 100644 index 00000000000..75436d2b083 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowBuilderCustomizer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2014 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.undertow; + +import io.undertow.Undertow.Builder; + +/** + * Callback interface that can be used to customize an Undertow {@link Builder}. + * + * @author Andy Wilkinson + * @since 1.2.0 + * @see UndertowEmbeddedServletContainerFactory + */ +public interface UndertowBuilderCustomizer { + + /** + * @param builder the {@code Builder} to customize + */ + void customize(Builder builder); + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java index 43fb3912f53..dfe4b99f13f 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java @@ -1,3 +1,19 @@ +/* + * Copyright 2012-2014 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.undertow; import io.undertow.Handlers; @@ -14,16 +30,29 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerExcepti import org.springframework.util.StringUtils; /** + * {@link EmbeddedServletContainer} that can be used to control an embedded Undertow + * server. Typically this class should be created using + * {@link UndertowEmbeddedServletContainerFactory} and not directly. + * * @author Ivan Sopov + * @author Andy Wilkinson + * @since 1.2.0 + * @see UndertowEmbeddedServletContainer */ public class UndertowEmbeddedServletContainer implements EmbeddedServletContainer { private final DeploymentManager manager; + private final Builder builder; + private final String contextPath; + private final int port; + private final boolean autoStart; + private Undertow undertow; + private boolean started = false; public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager, @@ -40,38 +69,39 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine if (!this.autoStart) { return; } - if (undertow == null) { + if (this.undertow == null) { try { - HttpHandler servletHandler = manager.start(); - if (StringUtils.isEmpty(contextPath)) { - builder.setHandler(servletHandler); + HttpHandler servletHandler = this.manager.start(); + if (StringUtils.isEmpty(this.contextPath)) { + this.builder.setHandler(servletHandler); } else { - PathHandler pathHandler = Handlers.path().addPrefixPath(contextPath, - servletHandler); - builder.setHandler(pathHandler); + PathHandler pathHandler = Handlers.path().addPrefixPath( + this.contextPath, servletHandler); + this.builder.setHandler(pathHandler); } - undertow = builder.build(); + this.undertow = this.builder.build(); } catch (ServletException ex) { throw new EmbeddedServletContainerException( "Unable to start embdedded Undertow", ex); } } - undertow.start(); - started = true; + this.undertow.start(); + this.started = true; } @Override public synchronized void stop() throws EmbeddedServletContainerException { - if (started) { - started = false; - undertow.stop(); + if (this.started) { + this.started = false; + this.undertow.stop(); } } @Override public int getPort() { - return port; + return this.port; } + } \ No newline at end of file diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java index ad112a65660..d3b471a994c 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java @@ -1,12 +1,21 @@ +/* + * Copyright 2012-2014 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.undertow; -import static io.undertow.servlet.Servlets.defaultContainer; -import static io.undertow.servlet.Servlets.deployment; -import static io.undertow.servlet.Servlets.servlet; -import static org.xnio.Options.SSL_CLIENT_AUTH_MODE; -import static org.xnio.SslClientAuthMode.NOT_REQUESTED; -import static org.xnio.SslClientAuthMode.REQUESTED; -import static org.xnio.SslClientAuthMode.REQUIRED; import io.undertow.Undertow; import io.undertow.Undertow.Builder; import io.undertow.UndertowMessages; @@ -27,12 +36,15 @@ import io.undertow.servlet.handlers.DefaultServlet; import io.undertow.servlet.util.ImmediateInstanceHandle; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.net.URL; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -45,6 +57,7 @@ import javax.servlet.ServletException; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.ErrorPage; import org.springframework.boot.context.embedded.MimeMappings.Mapping; import org.springframework.boot.context.embedded.ServletContextInitializer; @@ -52,176 +65,178 @@ import org.springframework.boot.context.embedded.Ssl; import org.springframework.boot.context.embedded.Ssl.ClientAuth; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; +import org.springframework.util.Assert; import org.springframework.util.ResourceUtils; import org.springframework.util.SocketUtils; +import static io.undertow.servlet.Servlets.defaultContainer; +import static io.undertow.servlet.Servlets.deployment; +import static io.undertow.servlet.Servlets.servlet; +import static org.xnio.Options.SSL_CLIENT_AUTH_MODE; +import static org.xnio.SslClientAuthMode.NOT_REQUESTED; +import static org.xnio.SslClientAuthMode.REQUESTED; +import static org.xnio.SslClientAuthMode.REQUIRED; + /** + * {@link EmbeddedServletContainerFactory} that can be used to create + * {@link UndertowEmbeddedServletContainer}s. + *

+ * Unless explicitly configured otherwise, the factory will create containers that listen + * for HTTP requests on port 8080. + * * @author Ivan Sopov + * @author Andy Wilkinson + * @since 1.2.0 + * @see UndertowEmbeddedServletContainer */ public class UndertowEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware { + private List undertowBuilderCustomizers = new ArrayList(); + private ResourceLoader resourceLoader; private Integer bufferSize; + private Integer buffersPerRegion; + private Integer ioThreads; + private Integer workerThreads; + private Boolean directBuffers; /** - * Create a new - * {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory} - * instance. + * Create a new {@link UndertowEmbeddedServletContainerFactory} instance. */ public UndertowEmbeddedServletContainerFactory() { super(); + setRegisterJspServlet(false); } /** - * Create a new - * {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory} - * that listens for requests using the specified port. - * + * Create a new {@link UndertowEmbeddedServletContainerFactory} that listens for + * requests using the specified port. * @param port the port to listen on */ public UndertowEmbeddedServletContainerFactory(int port) { super(port); + setRegisterJspServlet(false); } /** - * Create a new - * {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory} - * with the specified context path and port. - * + * Create a new {@link UndertowEmbeddedServletContainerFactory} with the specified + * context path and port. * @param contextPath root the context path * @param port the port to listen on */ public UndertowEmbeddedServletContainerFactory(String contextPath, int port) { super(contextPath, port); + setRegisterJspServlet(false); + } + + /** + * Set {@link UndertowBuilderCustomizer}s that should be applied to the Undertow + * {@link Builder}. Calling this method will replace any existing customizers. + * @param undertowBuilderCustomizers the customizers to set + */ + public void setUndertowBuilderCustomizers( + Collection undertowBuilderCustomizers) { + Assert.notNull(undertowBuilderCustomizers, + "undertowBuilderCustomizers must not be null"); + this.undertowBuilderCustomizers = new ArrayList( + undertowBuilderCustomizers); + } + + /** + * Returns a mutable collection of the {@link UndertowBuilderCustomizer}s that will be + * applied to the Undertow {@link Builder} . + * @return the customizers that will be applied + */ + public Collection getUndertowBuilderCustomizers() { + return this.undertowBuilderCustomizers; + } + + /** + * Add {@link UndertowBuilderCustomizer}s that should be used to customize the + * Undertow {@link Builder}. + * @param undertowBuilderCustomizers the customizers to add + */ + public void addUndertowBuilderCustomizers( + UndertowBuilderCustomizer... undertowBuilderCustomizers) { + Assert.notNull(undertowBuilderCustomizers, + "undertowBuilderCustomizers must not be null"); + this.undertowBuilderCustomizers.addAll(Arrays.asList(undertowBuilderCustomizers)); } @Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { - DeploymentInfo servletBuilder = deployment(); + DeploymentManager manager = createDeploymentManager(initializers); - servletBuilder.addListener(new ListenerInfo( - UndertowSpringServletContextListener.class, - new UndertowSpringServletContextListenerFactory( - new UndertowSpringServletContextListener( - mergeInitializers(initializers))))); + int port = getPort(); + if (port == 0) { + port = SocketUtils.findAvailableTcpPort(40000); + } - if (resourceLoader != null) { - servletBuilder.setClassLoader(resourceLoader.getClassLoader()); + Builder builder = createBuilder(port); + + return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(), + port, port >= 0); + } + + private Builder createBuilder(int port) { + Builder builder = Undertow.builder(); + if (this.bufferSize != null) { + builder.setBufferSize(this.bufferSize); + } + if (this.buffersPerRegion != null) { + builder.setBuffersPerRegion(this.buffersPerRegion); + } + 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 (getSsl() == null) { + builder.addHttpListener(port, "0.0.0.0"); } else { - servletBuilder.setClassLoader(getClass().getClassLoader()); + configureSsl(port, builder); } - servletBuilder.setContextPath(getContextPath()); - servletBuilder.setDeploymentName("spring-boot"); - if (isRegisterDefaultServlet()) { - servletBuilder.addServlet(servlet("default", DefaultServlet.class)); + for (UndertowBuilderCustomizer customizer : this.undertowBuilderCustomizers) { + customizer.customize(builder); } - if (isRegisterJspServlet()) { - logger.error("JSPs are not supported with Undertow"); - } - for (ErrorPage springErrorPage : getErrorPages()) { - if (springErrorPage.getStatus() != null) { - io.undertow.servlet.api.ErrorPage undertowErrorpage = new io.undertow.servlet.api.ErrorPage( - springErrorPage.getPath(), springErrorPage.getStatusCode()); - servletBuilder.addErrorPage(undertowErrorpage); + return builder; + } + + private void configureSsl(int port, Builder builder) { + try { + Ssl ssl = getSsl(); + SSLContext sslContext = SSLContext.getInstance(ssl.getProtocol()); + sslContext.init(getKeyManagers(), getTrustManagers(), null); + builder.addHttpsListener(port, "0.0.0.0", sslContext); + if (ssl.getClientAuth() == ClientAuth.NEED) { + builder.setSocketOption(SSL_CLIENT_AUTH_MODE, REQUIRED); } - else if (springErrorPage.getException() != null) { - io.undertow.servlet.api.ErrorPage undertowErrorpage = new io.undertow.servlet.api.ErrorPage( - springErrorPage.getPath(), springErrorPage.getException()); - servletBuilder.addErrorPage(undertowErrorpage); + else if (ssl.getClientAuth() == ClientAuth.WANT) { + builder.setSocketOption(SSL_CLIENT_AUTH_MODE, REQUESTED); } else { - io.undertow.servlet.api.ErrorPage undertowErrorpage = new io.undertow.servlet.api.ErrorPage( - springErrorPage.getPath()); - servletBuilder.addErrorPage(undertowErrorpage); + builder.setSocketOption(SSL_CLIENT_AUTH_MODE, NOT_REQUESTED); } } - servletBuilder.setServletStackTraces(ServletStackTraces.NONE); - - File root = getValidDocumentRoot(); - if (root != null && root.isDirectory()) { - servletBuilder.setResourceManager(new FileResourceManager(root, 0)); + catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); } - else if (root != null && root.isFile()) { - servletBuilder.setResourceManager(new JarResourcemanager(root)); + catch (KeyManagementException ex) { + throw new RuntimeException(ex); } - else if (resourceLoader != null) { - servletBuilder.setResourceManager(new ClassPathResourceManager(resourceLoader - .getClassLoader(), "")); - } - else { - servletBuilder.setResourceManager(new ClassPathResourceManager(getClass() - .getClassLoader(), "")); - } - for (Mapping mimeMapping : getMimeMappings()) { - servletBuilder.addMimeMapping(new MimeMapping(mimeMapping.getExtension(), - mimeMapping.getMimeType())); - } - - DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); - - manager.deploy(); - - manager.getDeployment().getSessionManager() - .setDefaultSessionTimeout(getSessionTimeout()); - - Builder builder = Undertow.builder(); - if (bufferSize != null) { - builder.setBufferSize(bufferSize); - } - if (buffersPerRegion != null) { - builder.setBuffersPerRegion(buffersPerRegion); - } - if (ioThreads != null) { - builder.setIoThreads(ioThreads); - } - if (workerThreads != null) { - builder.setWorkerThreads(workerThreads); - } - if (directBuffers != null) { - builder.setDirectBuffers(directBuffers); - } - - int realPort = getPort(); - if (realPort == 0) { - realPort = SocketUtils.findAvailableTcpPort(40000); - } - if (getSsl() == null) { - builder.addHttpListener(realPort, "0.0.0.0"); - } - else { - try { - Ssl ssl = getSsl(); - SSLContext sslContext = SSLContext.getInstance(ssl.getProtocol()); - sslContext.init(getKeyManagers(), getTrustManagers(), null); - builder.addHttpsListener(realPort, "0.0.0.0", sslContext); - if (ssl.getClientAuth() == ClientAuth.NEED) { - builder.setSocketOption(SSL_CLIENT_AUTH_MODE, REQUIRED); - } - else if (ssl.getClientAuth() == ClientAuth.WANT) { - builder.setSocketOption(SSL_CLIENT_AUTH_MODE, REQUESTED); - } - else { - builder.setSocketOption(SSL_CLIENT_AUTH_MODE, NOT_REQUESTED); - } - } - catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - catch (KeyManagementException e) { - throw new RuntimeException(e); - } - } - return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(), - realPort, realPort > 0); - } private KeyManager[] getKeyManagers() { @@ -244,8 +259,8 @@ public class UndertowEmbeddedServletContainerFactory extends keyManagerFactory.init(keyStore, keyPassword); return keyManagerFactory.getKeyManagers(); } - catch (Exception e) { - throw new RuntimeException(e); + catch (Exception ex) { + throw new RuntimeException(ex); } } @@ -271,10 +286,100 @@ public class UndertowEmbeddedServletContainerFactory extends trustManagerFactory.init(trustedKeyStore); return trustManagerFactory.getTrustManagers(); } - catch (Exception e) { - throw new RuntimeException(e); + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private DeploymentManager createDeploymentManager( + ServletContextInitializer... initializers) { + DeploymentInfo servletBuilder = deployment(); + + servletBuilder.addListener(new ListenerInfo( + UndertowSpringServletContextListener.class, + new UndertowSpringServletContextListenerFactory( + new UndertowSpringServletContextListener( + mergeInitializers(initializers))))); + + if (this.resourceLoader != null) { + servletBuilder.setClassLoader(this.resourceLoader.getClassLoader()); + } + else { + servletBuilder.setClassLoader(getClass().getClassLoader()); + } + servletBuilder.setContextPath(getContextPath()); + servletBuilder.setDeploymentName("spring-boot"); + if (isRegisterDefaultServlet()) { + servletBuilder.addServlet(servlet("default", DefaultServlet.class)); } + configureErrorPages(servletBuilder); + servletBuilder.setServletStackTraces(ServletStackTraces.NONE); + + File root = getValidDocumentRoot(); + if (root != null && root.isDirectory()) { + servletBuilder.setResourceManager(new FileResourceManager(root, 0)); + } + else if (root != null && root.isFile()) { + servletBuilder.setResourceManager(new JarResourcemanager(root)); + } + else if (this.resourceLoader != null) { + servletBuilder.setResourceManager(new ClassPathResourceManager( + this.resourceLoader.getClassLoader(), "")); + } + else { + servletBuilder.setResourceManager(new ClassPathResourceManager(getClass() + .getClassLoader(), "")); + } + for (Mapping mimeMapping : getMimeMappings()) { + servletBuilder.addMimeMapping(new MimeMapping(mimeMapping.getExtension(), + mimeMapping.getMimeType())); + } + + DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); + + manager.deploy(); + + manager.getDeployment().getSessionManager() + .setDefaultSessionTimeout(getSessionTimeout()); + return manager; + } + + private void configureErrorPages(DeploymentInfo servletBuilder) { + for (ErrorPage errorPage : getErrorPages()) { + if (errorPage.getStatus() != null) { + io.undertow.servlet.api.ErrorPage undertowErrorpage = new io.undertow.servlet.api.ErrorPage( + errorPage.getPath(), errorPage.getStatusCode()); + servletBuilder.addErrorPage(undertowErrorpage); + } + else if (errorPage.getException() != null) { + io.undertow.servlet.api.ErrorPage undertowErrorpage = new io.undertow.servlet.api.ErrorPage( + errorPage.getPath(), errorPage.getException()); + servletBuilder.addErrorPage(undertowErrorpage); + } + else { + io.undertow.servlet.api.ErrorPage undertowErrorpage = new io.undertow.servlet.api.ErrorPage( + errorPage.getPath()); + servletBuilder.addErrorPage(undertowErrorpage); + } + } + } + + /** + * Factory method called to create the {@link UndertowEmbeddedServletContainer}. + * Subclasses can override this method to return a different + * {@link UndertowEmbeddedServletContainer} or apply additional processing to the + * {@link Builder} and {@link DeploymentManager} used to bootstrap Undertow + * + * @param builder the builder + * @param manager the deployment manager + * @param port the port that Undertow should listen on + * @return a new {@link UndertowEmbeddedServletContainer} instance + */ + protected UndertowEmbeddedServletContainer getUndertowEmbeddedServletContainer( + Builder builder, DeploymentManager manager, int port) { + return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(), + port, port >= 0); } @Override @@ -302,6 +407,12 @@ public class UndertowEmbeddedServletContainerFactory extends this.directBuffers = directBuffers; } + @Override + public void setRegisterJspServlet(boolean registerJspServlet) { + Assert.isTrue(!registerJspServlet, "Undertow does not support JSPs"); + super.setRegisterJspServlet(registerJspServlet); + } + private static class JarResourcemanager implements ResourceManager { private final String jarPath; @@ -320,7 +431,7 @@ public class UndertowEmbeddedServletContainerFactory extends @Override public Resource getResource(String path) throws IOException { - URL url = new URL("jar:file:" + jarPath + "!" + path); + URL url = new URL("jar:file:" + this.jarPath + "!" + path); URLResource resource = new URLResource(url, url.openConnection(), path); if (resource.getContentLength() < 0) { return null; @@ -361,7 +472,7 @@ public class UndertowEmbeddedServletContainerFactory extends public InstanceHandle createInstance() throws InstantiationException { return new ImmediateInstanceHandle( - listener); + this.listener); } } @@ -376,14 +487,14 @@ public class UndertowEmbeddedServletContainerFactory extends } @Override - public void contextInitialized(ServletContextEvent sce) { + public void contextInitialized(ServletContextEvent event) { try { - for (ServletContextInitializer initializer : initializers) { - initializer.onStartup(sce.getServletContext()); + for (ServletContextInitializer initializer : this.initializers) { + initializer.onStartup(event.getServletContext()); } } - catch (ServletException e) { - throw new RuntimeException(e); + catch (ServletException ex) { + throw new RuntimeException(ex); } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/package-info.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/package-info.java new file mode 100644 index 00000000000..c0092c5f73f --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012-2014 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. + */ + +/** + * Support for Undertow {@link org.springframework.boot.context.embedded.EmbeddedServletContainer EmbeddedServletContainers}. + * + * @see org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory + */ +package org.springframework.boot.context.embedded.undertow; + diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerMvcIntegrationTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerMvcIntegrationTests.java index fd60f366a66..1a7be91bf4f 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerMvcIntegrationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/EmbeddedServletContainerMvcIntegrationTests.java @@ -49,6 +49,7 @@ import static org.junit.Assert.assertThat; * {@link EmbeddedServletContainer}s running Spring MVC. * * @author Phillip Webb + * @author Ivan Sopov */ public class EmbeddedServletContainerMvcIntegrationTests { @@ -69,7 +70,7 @@ public class EmbeddedServletContainerMvcIntegrationTests { TomcatConfig.class); doTest(this.context, "/hello"); } - + @Test public void jetty() throws Exception { this.context = new AnnotationConfigEmbeddedWebApplicationContext( @@ -83,9 +84,6 @@ public class EmbeddedServletContainerMvcIntegrationTests { UndertowConfig.class); doTest(this.context, "/hello"); } - - - @Test public void advancedConfig() throws Exception { @@ -128,7 +126,7 @@ public class EmbeddedServletContainerMvcIntegrationTests { return new JettyEmbeddedServletContainerFactory(0); } } - + @Configuration @Import(Config.class) public static class UndertowConfig { diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactoryTests.java index a25ded4dca5..d24337e8c16 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactoryTests.java @@ -1,34 +1,96 @@ +/* + * Copyright 2012-2014 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.undertow; + +import io.undertow.Undertow.Builder; + +import java.util.Arrays; + import org.junit.Test; -import org.springframework.boot.context.embedded.*; +import org.mockito.InOrder; +import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests; +import org.springframework.boot.context.embedded.ErrorPage; +import org.springframework.boot.context.embedded.ExampleServlet; +import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.http.HttpStatus; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; - +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; /** - * Tests for {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory} and - * {@link org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainer}. + * Tests for {@link UndertowEmbeddedServletContainerFactory} and + * {@link UndertowEmbeddedServletContainer} . * * @author Ivan Sopov + * @author Andy Wilkinson */ -public class UndertowEmbeddedServletContainerFactoryTests extends AbstractEmbeddedServletContainerFactoryTests -{ +public class UndertowEmbeddedServletContainerFactoryTests extends + AbstractEmbeddedServletContainerFactoryTests { - @Override - protected UndertowEmbeddedServletContainerFactory getFactory() - { - return new UndertowEmbeddedServletContainerFactory(); - } + @Override + protected UndertowEmbeddedServletContainerFactory getFactory() { + return new UndertowEmbeddedServletContainerFactory(0); + } - @Test - public void errorPage404() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); - factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/hello")); - this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean(new ExampleServlet(), "/hello")); - this.container.start(); - assertThat(getResponse("http://localhost:8080/hello"), equalTo("Hello World")); - assertThat(getResponse("http://localhost:8080/not-found"), equalTo("Hello World")); - } + @Test + public void errorPage404() throws Exception { + AbstractEmbeddedServletContainerFactory factory = getFactory(); + factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/hello")); + this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean( + new ExampleServlet(), "/hello")); + this.container.start(); + assertThat(getResponse(getLocalUrl("/hello")), equalTo("Hello World")); + assertThat(getResponse(getLocalUrl("/not-found")), equalTo("Hello World")); + } + + @Test + public void setNullUndertowBuilderCustomizersThrows() { + UndertowEmbeddedServletContainerFactory factory = getFactory(); + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("undertowBuilderCustomizers must not be null"); + factory.setUndertowBuilderCustomizers(null); + } + + @Test + public void addNullContextCustomizersThrows() { + UndertowEmbeddedServletContainerFactory factory = getFactory(); + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("undertowBuilderCustomizers must not be null"); + factory.addUndertowBuilderCustomizers((UndertowBuilderCustomizer[]) null); + } + + @Test + public void builderCustomizers() throws Exception { + UndertowEmbeddedServletContainerFactory factory = getFactory(); + UndertowBuilderCustomizer[] customizers = new UndertowBuilderCustomizer[4]; + for (int i = 0; i < customizers.length; i++) { + customizers[i] = mock(UndertowBuilderCustomizer.class); + } + factory.setUndertowBuilderCustomizers(Arrays.asList(customizers[0], + customizers[1])); + factory.addUndertowBuilderCustomizers(customizers[2], customizers[3]); + this.container = factory.getEmbeddedServletContainer(); + InOrder ordered = inOrder((Object[]) customizers); + for (UndertowBuilderCustomizer customizer : customizers) { + ordered.verify(customizer).customize((Builder) anyObject()); + } + } }