Browse Source

Add reactive web server infrastructure

This commit adds the infrastructure for creating and customizing
reactive embedded web servers. Common configuration has been refactored
into the new `ConfigurableEmbeddedWebServer` interface.

See gh-8302
pull/8410/head
Brian Clozel 9 years ago
parent
commit
f331ac133f
  1. 1
      spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
  2. 146
      spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableReactiveWebServer.java
  3. 36
      spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactory.java
  4. 52
      spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java
  5. 80
      spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedWebServer.java
  6. 27
      spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableReactiveWebServer.java
  7. 34
      spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizer.java
  8. 86
      spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizerBeanPostProcessor.java
  9. 52
      spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerFactory.java
  10. 123
      spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactoryTests.java

1
spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

@ -112,6 +112,7 @@ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguratio @@ -112,6 +112,7 @@ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguratio
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.webflux.ReactiveWebServerAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

146
spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableReactiveWebServer.java

@ -0,0 +1,146 @@ @@ -0,0 +1,146 @@
/*
* Copyright 2012-2017 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;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.util.Assert;
/**
* Abstract base class for {@link ConfigurableReactiveWebServer} implementations.
*
* @author Brian Clozel
* @since 2.0.0
*/
public class AbstractConfigurableReactiveWebServer implements ConfigurableReactiveWebServer {
private int port = 8080;
private Set<ErrorPage> errorPages = new LinkedHashSet<ErrorPage>();
private InetAddress address;
private Ssl ssl;
private SslStoreProvider sslStoreProvider;
private Compression compression;
private String serverHeader;
/**
* Create a new {@link AbstractConfigurableReactiveWebServer} instance.
*/
public AbstractConfigurableReactiveWebServer() {
}
/**
* Create a new {@link AbstractConfigurableReactiveWebServer} instance with the
* specified port.
* @param port the port number for the reactive web server
*/
public AbstractConfigurableReactiveWebServer(int port) {
this.port = port;
}
@Override
public void setAddress(InetAddress address) {
this.address = address;
}
/**
* Return the address that the reactive web server binds to.
* @return the address
*/
public InetAddress getAddress() {
return this.address;
}
@Override
public void setPort(int port) {
this.port = port;
}
/**
* The port that the reactive web server listens on.
* @return the port
*/
public int getPort() {
return this.port;
}
@Override
public void setErrorPages(Set<? extends ErrorPage> errorPages) {
Assert.notNull(errorPages, "ErrorPages must not be null");
this.errorPages = new LinkedHashSet<ErrorPage>(errorPages);
}
@Override
public void addErrorPages(ErrorPage... errorPages) {
Assert.notNull(errorPages, "ErrorPages must not be null");
this.errorPages.addAll(Arrays.asList(errorPages));
}
/**
* Return a mutable set of {@link ErrorPage ErrorPages} that will be used when
* handling exceptions.
* @return the error pages
*/
public Set<ErrorPage> getErrorPages() {
return this.errorPages;
}
@Override
public void setSsl(Ssl ssl) {
this.ssl = ssl;
}
public Ssl getSsl() {
return this.ssl;
}
@Override
public void setSslStoreProvider(SslStoreProvider sslStoreProvider) {
this.sslStoreProvider = sslStoreProvider;
}
public SslStoreProvider getSslStoreProvider() {
return this.sslStoreProvider;
}
public Compression getCompression() {
return this.compression;
}
@Override
public void setCompression(Compression compression) {
this.compression = compression;
}
public String getServerHeader() {
return this.serverHeader;
}
@Override
public void setServerHeader(String serverHeader) {
this.serverHeader = serverHeader;
}
}

36
spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactory.java

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
/*
* Copyright 2012-2017 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;
/**
* Abstract base class for {@link ReactiveWebServerFactory} implementations.
*
* @author Brian Clozel
* @since 2.0.0
*/
public abstract class AbstractReactiveWebServerFactory
extends AbstractConfigurableReactiveWebServer
implements ReactiveWebServerFactory {
public AbstractReactiveWebServerFactory() {
}
public AbstractReactiveWebServerFactory(int port) {
super(port);
}
}

52
spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java

@ -17,16 +17,12 @@ @@ -17,16 +17,12 @@
package org.springframework.boot.context.embedded;
import java.io.File;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.boot.web.servlet.ErrorPageRegistry;
import org.springframework.boot.web.servlet.ServletContextInitializer;
/**
@ -41,7 +37,7 @@ import org.springframework.boot.web.servlet.ServletContextInitializer; @@ -41,7 +37,7 @@ import org.springframework.boot.web.servlet.ServletContextInitializer;
* @see EmbeddedServletContainerFactory
* @see EmbeddedServletContainerCustomizer
*/
public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry {
public interface ConfigurableEmbeddedServletContainer extends ConfigurableEmbeddedWebServer {
/**
* Sets the context path for the embedded servlet container. The context should start
@ -59,14 +55,6 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry @@ -59,14 +55,6 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry
*/
void setDisplayName(String displayName);
/**
* Sets the port that the embedded servlet container should listen on. If not
* specified port '8080' will be used. Use port -1 to disable auto-start (i.e start
* the web application context but not have it listen to any port).
* @param port the port to set
*/
void setPort(int port);
/**
* The session timeout in seconds (default 30 minutes). If 0 or negative then sessions
* never expire.
@ -94,12 +82,6 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry @@ -94,12 +82,6 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry
*/
void setSessionStoreDir(File sessionStoreDir);
/**
* Sets the specific network address that the server should bind to.
* @param address the address to set (defaults to {@code null})
*/
void setAddress(InetAddress address);
/**
* Set if the DefaultServlet should be registered. Defaults to {@code true} so that
* files from the {@link #setDocumentRoot(File) document root} will be served.
@ -107,12 +89,6 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry @@ -107,12 +89,6 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry
*/
void setRegisterDefaultServlet(boolean registerDefaultServlet);
/**
* Sets the error pages that will be used when handling exceptions.
* @param errorPages the error pages
*/
void setErrorPages(Set<? extends ErrorPage> errorPages);
/**
* Sets the mime-type mappings.
* @param mimeMappings the mime type mappings (defaults to
@ -146,38 +122,12 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry @@ -146,38 +122,12 @@ public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry
*/
void addInitializers(ServletContextInitializer... initializers);
/**
* Sets the SSL configuration that will be applied to the container's default
* connector.
* @param ssl the SSL configuration
*/
void setSsl(Ssl ssl);
/**
* Sets a provider that will be used to obtain SSL stores.
* @param sslStoreProvider the SSL store provider
*/
void setSslStoreProvider(SslStoreProvider sslStoreProvider);
/**
* Sets the configuration that will be applied to the container's JSP servlet.
* @param jsp the JSP servlet configuration
*/
void setJsp(Jsp jsp);
/**
* Sets the compression configuration that will be applied to the container's default
* connector.
* @param compression the compression configuration
*/
void setCompression(Compression compression);
/**
* Sets the server header value.
* @param serverHeader the server header value
*/
void setServerHeader(String serverHeader);
/**
* Sets the Locale to Charset mappings.
* @param localeCharsetMappings the Locale to Charset mappings

80
spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedWebServer.java

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
/*
* Copyright 2012-2017 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;
import java.net.InetAddress;
import java.util.Set;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.boot.web.servlet.ErrorPageRegistry;
/**
* Interface that regroups common customizations to
* embedded server factories such as {@link EmbeddedServletContainerFactory}
* and {@link ReactiveWebServerFactory}.
* @author Brian Clozel
* @since 2.0.0
*/
public interface ConfigurableEmbeddedWebServer extends ErrorPageRegistry {
/**
* Sets the port that the embedded servlet container should listen on. If not
* specified port '8080' will be used. Use port -1 to disable auto-start (i.e start
* the web application context but not have it listen to any port).
* @param port the port to set
*/
void setPort(int port);
/**
* Sets the specific network address that the server should bind to.
* @param address the address to set (defaults to {@code null})
*/
void setAddress(InetAddress address);
/**
* Sets the error pages that will be used when handling exceptions.
* @param errorPages the error pages
*/
void setErrorPages(Set<? extends ErrorPage> errorPages);
/**
* Sets the SSL configuration that will be applied to the container's default
* connector.
* @param ssl the SSL configuration
*/
void setSsl(Ssl ssl);
/**
* Sets a provider that will be used to obtain SSL stores.
* @param sslStoreProvider the SSL store provider
*/
void setSslStoreProvider(SslStoreProvider sslStoreProvider);
/**
* Sets the compression configuration that will be applied to the container's default
* connector.
* @param compression the compression configuration
*/
void setCompression(Compression compression);
/**
* Sets the server header value.
* @param serverHeader the server header value
*/
void setServerHeader(String serverHeader);
}

27
spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableReactiveWebServer.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/*
* Copyright 2012-2017 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;
/**
* Interface that represents customizations to a {@link ReactiveWebServerFactory}.
*
* @author Brian Clozel
* @since 2.0.0
*/
public interface ConfigurableReactiveWebServer extends ConfigurableEmbeddedWebServer {
}

34
spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizer.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2012-2017 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;
/**
* Strategy interface for customizing auto-configured embedded reactive servers. Any
* beans of this type will get a callback with the server factory before the server
* itself is started, so you can set the port, address, error pages etc.
* @author Brian Clozel
* @since 2.0.0
*/
@FunctionalInterface
public interface ReactiveWebServerCustomizer {
/**
* Customize the specified {@link ConfigurableReactiveWebServer}.
* @param server the server to customize
*/
void customize(ConfigurableReactiveWebServer server);
}

86
spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerCustomizerBeanPostProcessor.java

@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
/*
* Copyright 2012-2017 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
/**
* {@link BeanPostProcessor} that applies all {@link ReactiveWebServerCustomizer}s
* from the bean factory to {@link ConfigurableReactiveWebServer} beans.
*
*
* @author Brian Clozel
*/
public class ReactiveWebServerCustomizerBeanPostProcessor
implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
private List<ReactiveWebServerCustomizer> customizers;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ConfigurableReactiveWebServer) {
postProcessBeforeInitialization((ConfigurableReactiveWebServer) bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableReactiveWebServer bean) {
for (ReactiveWebServerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
private Collection<ReactiveWebServerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<ReactiveWebServerCustomizer>(
this.applicationContext
.getBeansOfType(ReactiveWebServerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
}

52
spring-boot/src/main/java/org/springframework/boot/context/embedded/ReactiveWebServerFactory.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
/*
* Copyright 2012-2017 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;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.http.server.reactive.HttpHandler;
/**
* Factory interface that can be used to create reactive {@link EmbeddedWebServer}s.
*
* @author Brian Clozel
* @since 2.0.0
* @see EmbeddedWebServer
*/
public interface ReactiveWebServerFactory {
/**
* Gets a new fully configured but paused {@link EmbeddedWebServer} instance.
* Clients should not be able to connect to the returned server until
* {@link EmbeddedWebServer#start()} is called (which happens when the
* {@link ApplicationContext} has been fully refreshed).
* @param httpHandler the HTTP handler in charge of processing requests
* @return a fully configured and started {@link EmbeddedWebServer}
* @see EmbeddedWebServer#stop()
*/
EmbeddedWebServer getReactiveHttpServer(HttpHandler httpHandler);
/**
* Register a map of {@link HttpHandler}s, each to a specific context path.
*
* @param handlerMap a map of context paths and the associated {@code HttpHandler}
* @return a fully configured and started {@link EmbeddedWebServer}
* @see EmbeddedWebServer#stop()
*/
EmbeddedWebServer getReactiveHttpServer(Map<String, HttpHandler> handlerMap);
}

123
spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractReactiveWebServerFactoryTests.java

@ -0,0 +1,123 @@ @@ -0,0 +1,123 @@
/*
* Copyright 2012-2017 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;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.boot.testutil.InternalOutputCapture;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.SocketUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Base for testing classes that extends {@link AbstractReactiveWebServerFactory}.
*
* @author Brian Clozel
*/
public abstract class AbstractReactiveWebServerFactoryTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public InternalOutputCapture output = new InternalOutputCapture();
protected EmbeddedWebServer webServer;
@After
public void tearDown() {
if (this.webServer != null) {
try {
this.webServer.stop();
}
catch (Exception ex) {
// Ignore
}
}
}
protected abstract AbstractReactiveWebServerFactory getFactory();
@Test
public void startStopServer() {
this.webServer = getFactory().getReactiveHttpServer(new EchoHandler());
this.webServer.start();
Mono<String> result = getWebClient()
.post().uri("/test")
.contentType(MediaType.TEXT_PLAIN)
.exchange(BodyInserters.fromObject("Hello World"))
.then(response -> response.bodyToMono(String.class));
assertThat(result.block()).isEqualTo("Hello World");
this.webServer.stop();
Mono<ClientResponse> response = getWebClient()
.post().uri("/test")
.contentType(MediaType.TEXT_PLAIN)
.exchange(BodyInserters.fromObject("Hello World"));
StepVerifier.create(response)
.expectError()
.verify();
}
@Test
public void specificPort() throws Exception {
AbstractReactiveWebServerFactory factory = getFactory();
int specificPort = SocketUtils.findAvailableTcpPort(41000);
factory.setPort(specificPort);
this.webServer = factory.getReactiveHttpServer(new EchoHandler());
this.webServer.start();
Mono<String> result = WebClient.create("http://localhost:" + specificPort)
.post().uri("/test")
.contentType(MediaType.TEXT_PLAIN)
.exchange(BodyInserters.fromObject("Hello World"))
.then(response -> response.bodyToMono(String.class));
assertThat(result.block()).isEqualTo("Hello World");
assertThat(this.webServer.getPort()).isEqualTo(specificPort);
}
protected WebClient getWebClient() {
return WebClient.create("http://localhost:" + this.webServer.getPort());
}
protected static class EchoHandler implements HttpHandler {
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
response.setStatusCode(HttpStatus.OK);
return response.writeWith(request.getBody());
}
}
}
Loading…
Cancel
Save