Polish Undertow contribution

Closes gh-1779
This commit is contained in:
Andy Wilkinson 2014-11-18 21:00:28 +00:00
parent c501b889af
commit 1864d79077
24 changed files with 608 additions and 268 deletions

3
.gitignore vendored
View File

@ -28,5 +28,4 @@ overridedb.*
*.ipr
*.iws
.idea
*.jar
.DS_Store
*.jar

View File

@ -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

View File

@ -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<String, String> contextParameters = new HashMap<String, String>();
@ -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);
}
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -127,10 +127,10 @@
<thymeleaf-layout-dialect.version>1.2.7</thymeleaf-layout-dialect.version>
<thymeleaf-extras-data-attribute.version>1.3</thymeleaf-extras-data-attribute.version>
<tomcat.version>8.0.15</tomcat.version>
<undertow.version>1.1.0.Final</undertow.version>
<velocity.version>1.7</velocity.version>
<velocity-tools.version>2.0</velocity-tools.version>
<wsdl4j.version>1.6.3</wsdl4j.version>
<undertow.version>1.1.0.Final</undertow.version>
</properties>
<prerequisites>
<maven>3.0.2</maven>
@ -540,6 +540,16 @@
<artifactId>metrics-servlets</artifactId>
<version>${dropwizard-metrics.version}</version>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
<version>${undertow.version}</version>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<version>${undertow.version}</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
@ -707,11 +717,6 @@
<artifactId>tomcat-jsp-api</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<version>${undertow.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>

View File

@ -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

View File

@ -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 <<howto-use-jetty-instead-of-tomcat,
using Jetty instead of Tomcat>>. You need to exclude the Tomcat dependencies and include
the Undertow starter instead.
Example in Maven:
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
----
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
----
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
_<<howto-discover-build-in-options-for-external-properties>>_ 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

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -19,9 +19,23 @@
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<exclusions>
<exclusion>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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.
* <p>
* 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<UndertowBuilderCustomizer> undertowBuilderCustomizers = new ArrayList<UndertowBuilderCustomizer>();
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<? extends UndertowBuilderCustomizer> undertowBuilderCustomizers) {
Assert.notNull(undertowBuilderCustomizers,
"undertowBuilderCustomizers must not be null");
this.undertowBuilderCustomizers = new ArrayList<UndertowBuilderCustomizer>(
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<UndertowBuilderCustomizer> 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<UndertowSpringServletContextListener> createInstance()
throws InstantiationException {
return new ImmediateInstanceHandle<UndertowSpringServletContextListener>(
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);
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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());
}
}
}