From c501b889af0483b1c73c6ea0a5793c729ab4d6bd Mon Sep 17 00:00:00 2001 From: "sopov.ivan" Date: Fri, 14 Nov 2014 11:20:14 +0300 Subject: [PATCH] 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 3fa1136eb0..b4c28f3b0e 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 c040e7c133..ccab61becd 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 6ed5985c33..3461b0a94f 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 7fe8ced57e..e913718638 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 3d047230d7..bec1f66bca 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 b2ade70ece..cf684455ce 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 5d09759bff..bdeef0e508 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 b74248f595..0bda3a1d39 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 0000000000..ee20b2b9d2 --- /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 0000000000..dbeac870e5 --- /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 0000000000..0865d25d2d --- /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 0000000000..78b5c0c776 --- /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 0000000000..953abe0d6c --- /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 0000000000..3b450065d3 --- /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 0000000000..2819c6ba94 --- /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 0000000000..a81adcd109 --- /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 0000000000..0865d25d2d --- /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 0000000000..78b5c0c776 --- /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 0000000000..c786bf140f --- /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 014017a453..c93402ea3a 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 0000000000..0bcb12fc6b --- /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 2c50461ec5..bf47c8d141 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 0000000000..43fb3912f5 --- /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 0000000000..ad112a6566 --- /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 c8f9ca2969..fd60f366a6 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 0000000000..a25ded4dca --- /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")); + } +}