Commit 656b509f authored by Brian Clozel's avatar Brian Clozel

Add support for reactive web servers auto-configuration

This commit adds the auto-configuration for creating reactive
`EmbeddedWebServer` instances. This adds support for the
following servers: Reactor Netty, Tomcat, Jetty and Undertow.

Fixes gh-8302
Fixes gh-8117
parent dc98d909
...@@ -100,6 +100,11 @@ ...@@ -100,6 +100,11 @@
<artifactId>de.flapdoodle.embed.mongo</artifactId> <artifactId>de.flapdoodle.embed.mongo</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>javax.cache</groupId> <groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId> <artifactId>cache-api</artifactId>
......
/*
* 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.autoconfigure.webflux;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.embedded.ConfigurableReactiveWebServer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor;
import org.springframework.boot.context.embedded.ReactiveWebServerCustomizer;
import org.springframework.boot.context.embedded.ReactiveWebServerFactory;
import org.springframework.core.Ordered;
/**
* Customizer used by an {@link ReactiveWebServerFactory} when an
* {@link EmbeddedServletContainerCustomizerBeanPostProcessor} is active.
*
* @author Brian Clozel
*/
public class DefaultReactiveWebServerCustomizer
implements ReactiveWebServerCustomizer, Ordered {
private final ServerProperties serverProperties;
public DefaultReactiveWebServerCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void customize(ConfigurableReactiveWebServer server) {
if (this.serverProperties.getPort() != null) {
server.setPort(this.serverProperties.getPort());
}
if (this.serverProperties.getAddress() != null) {
server.setAddress(this.serverProperties.getAddress());
}
if (this.serverProperties.getSsl() != null) {
server.setSsl(this.serverProperties.getSsl());
}
if (this.serverProperties.getCompression() != null) {
server.setCompression(this.serverProperties.getCompression());
}
}
}
/*
* 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.autoconfigure.webflux;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.embedded.ReactiveWebServerCustomizerBeanPostProcessor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ObjectUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for a reactive web server.
*
* @author Brian Clozel
*/
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ReactiveWebServerAutoConfiguration.BeanPostProcessorsRegistrar.class,
ReactiveWebServerConfiguration.ReactorNettyAutoConfiguration.class})
public class ReactiveWebServerAutoConfiguration {
@ConditionalOnMissingBean
@Bean
public DefaultReactiveWebServerCustomizer defaultReactiveWebServerCustomizer(
ServerProperties serverProperties) {
return new DefaultReactiveWebServerCustomizer(serverProperties);
}
/**
* Registers a {@link ReactiveWebServerCustomizerBeanPostProcessor}. Registered
* via {@link ImportBeanDefinitionRegistrar} for early registration.
*/
public static class BeanPostProcessorsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(
ReactiveWebServerCustomizerBeanPostProcessor.class, true, false))) {
registry.registerBeanDefinition(
"reactiveWebServerCustomizerBeanPostProcessor",
new RootBeanDefinition(ReactiveWebServerCustomizerBeanPostProcessor.class));
}
}
}
}
/*
* 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.autoconfigure.webflux;
import reactor.ipc.netty.http.server.HttpServer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.embedded.ReactiveWebServerFactory;
import org.springframework.boot.context.embedded.reactor.ReactorNettyReactiveWebServerFactory;
import org.springframework.context.annotation.Bean;
/**
* Configuration classes for reactive web servers
* <p>Those should be {@code @Import} in a regular auto-configuration class
* to guarantee their order of execution.
*
* @author Brian Clozel
*/
abstract class ReactiveWebServerConfiguration {
@ConditionalOnMissingBean(ReactiveWebServerFactory.class)
@ConditionalOnClass({HttpServer.class})
static class ReactorNettyAutoConfiguration {
@Bean
public ReactorNettyReactiveWebServerFactory reactorNettyReactiveWebServerFactory() {
return new ReactorNettyReactiveWebServerFactory();
}
}
}
/*
* 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.autoconfigure.webflux;
import java.net.InetAddress;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.embedded.ConfigurableReactiveWebServer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link DefaultReactiveWebServerCustomizer}.
*
* @author Brian Clozel
*/
public class DefaultReactiveWebServerCustomizerTests {
private final ServerProperties properties = new ServerProperties();
private DefaultReactiveWebServerCustomizer customizer;
@Before
public void setup() throws Exception {
this.customizer = new DefaultReactiveWebServerCustomizer(this.properties);
}
@Test
public void testCustomizeServerPort() throws Exception {
ConfigurableReactiveWebServer factory = mock(ConfigurableReactiveWebServer.class);
this.properties.setPort(9000);
this.customizer.customize(factory);
verify(factory).setPort(9000);
}
@Test
public void testCustomizeServerAddress() throws Exception {
ConfigurableReactiveWebServer factory = mock(ConfigurableReactiveWebServer.class);
InetAddress address = mock(InetAddress.class);
this.properties.setAddress(address);
this.customizer.customize(factory);
verify(factory).setAddress(address);
}
}
/*
* 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.autoconfigure.webflux;
import java.util.Map;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactory;
import org.springframework.boot.context.embedded.EmbeddedWebServer;
import org.springframework.boot.context.embedded.EmbeddedWebServerException;
import org.springframework.boot.context.embedded.ReactiveWebServerFactory;
import org.springframework.http.server.reactive.HttpHandler;
import static org.mockito.Mockito.spy;
/**
* Mock {@link ReactiveWebServerFactory}.
*
* @author Brian Clozel
*/
public class MockReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
private MockReactiveWebServer webServer;
@Override
public EmbeddedWebServer getReactiveHttpServer(HttpHandler httpHandler) {
this.webServer = spy(new MockReactiveWebServer(httpHandler, getPort()));
return this.webServer;
}
@Override
public EmbeddedWebServer getReactiveHttpServer(Map<String, HttpHandler> handlerMap) {
this.webServer = spy(new MockReactiveWebServer(handlerMap, getPort()));
return this.webServer;
}
public MockReactiveWebServer getWebServer() {
return this.webServer;
}
public static class MockReactiveWebServer implements EmbeddedWebServer {
private final int port;
private HttpHandler httpHandler;
private Map<String, HttpHandler> httpHandlerMap;
public MockReactiveWebServer(HttpHandler httpHandler, int port) {
this.httpHandler = httpHandler;
this.port = port;
}
public MockReactiveWebServer(Map<String, HttpHandler> httpHandlerMap, int port) {
this.httpHandlerMap = httpHandlerMap;
this.port = port;
}
public HttpHandler getHttpHandler() {
return this.httpHandler;
}
public Map<String, HttpHandler> getHttpHandlerMap() {
return this.httpHandlerMap;
}
@Override
public void start() throws EmbeddedWebServerException {
}
@Override
public void stop() throws EmbeddedWebServerException {
}
@Override
public int getPort() {
return this.port;
}
}
}
/*
* 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.autoconfigure.webflux;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.springframework.boot.context.embedded.ReactiveWebApplicationContext;
import org.springframework.boot.context.embedded.ReactiveWebServerCustomizer;
import org.springframework.boot.context.embedded.ReactiveWebServerFactory;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.server.reactive.HttpHandler;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReactiveWebServerAutoConfiguration}.
*
* @author Brian Clozel
*/
public class ReactiveWebServerAutoConfigurationTests {
private ReactiveWebApplicationContext context;
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createFromConfigClass() {
this.context = new ReactiveWebApplicationContext(BaseConfiguration.class);
assertThat(this.context.getBeansOfType(ReactiveWebServerFactory.class)).hasSize(1);
assertThat(this.context.getBeansOfType(ReactiveWebServerCustomizer.class)).hasSize(1);
assertThat(this.context.getBeansOfType(DefaultReactiveWebServerCustomizer.class)).hasSize(1);
}
@Test
public void missingHttpHandler() {
this.thrown.expect(ApplicationContextException.class);
this.thrown.expectMessage(Matchers.containsString("missing HttpHandler bean"));
this.context = new ReactiveWebApplicationContext(MissingHttpHandlerConfiguration.class);
}
@Test
public void multipleHttpHandler() {
this.thrown.expect(ApplicationContextException.class);
this.thrown.expectMessage(Matchers
.containsString("multiple HttpHandler beans : httpHandler,additionalHttpHandler"));
this.context = new ReactiveWebApplicationContext(BaseConfiguration.class, TooManyHttpHandlers.class);
}
@Test
public void customizeReactiveWebServer() {
this.context = new ReactiveWebApplicationContext(BaseConfiguration.class,
ReactiveWebServerCustomization.class);
MockReactiveWebServerFactory factory = this.context.getBean(MockReactiveWebServerFactory.class);
assertThat(factory.getPort()).isEqualTo(9000);
}
@Configuration
@Import({MockWebServerAutoConfiguration.class, ReactiveWebServerAutoConfiguration.class})
protected static class BaseConfiguration {
@Bean
public HttpHandler httpHandler() {
return Mockito.mock(HttpHandler.class);
}
}
@Configuration
@Import({MockWebServerAutoConfiguration.class, ReactiveWebServerAutoConfiguration.class})
protected static class MissingHttpHandlerConfiguration {
}
@Configuration
protected static class TooManyHttpHandlers {
@Bean
public HttpHandler additionalHttpHandler() {
return Mockito.mock(HttpHandler.class);
}
}
@Configuration
protected static class ReactiveWebServerCustomization {
@Bean
public ReactiveWebServerCustomizer reactiveWebServerCustomizer() {
return (server) -> server.setPort(9000);
}
}
@Configuration
public static class MockWebServerAutoConfiguration {
@Bean
public MockReactiveWebServerFactory mockReactiveWebServerFactory() {
return new MockReactiveWebServerFactory();
}
}
}
...@@ -69,6 +69,11 @@ ...@@ -69,6 +69,11 @@
<artifactId>json-simple</artifactId> <artifactId>json-simple</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>javax.jms</groupId> <groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId> <artifactId>javax.jms-api</artifactId>
...@@ -326,6 +331,10 @@ ...@@ -326,6 +331,10 @@
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.projectreactor.addons</groupId>
<artifactId>reactor-test</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId> <artifactId>jcl-over-slf4j</artifactId>
......
...@@ -16,6 +16,12 @@ ...@@ -16,6 +16,12 @@
package org.springframework.boot.context.embedded; package org.springframework.boot.context.embedded;
import java.io.File;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** /**
* Abstract base class for {@link ReactiveWebServerFactory} implementations. * Abstract base class for {@link ReactiveWebServerFactory} implementations.
* *
...@@ -26,6 +32,8 @@ public abstract class AbstractReactiveWebServerFactory ...@@ -26,6 +32,8 @@ public abstract class AbstractReactiveWebServerFactory
extends AbstractConfigurableReactiveWebServer extends AbstractConfigurableReactiveWebServer
implements ReactiveWebServerFactory { implements ReactiveWebServerFactory {
protected final Log logger = LogFactory.getLog(getClass());
public AbstractReactiveWebServerFactory() { public AbstractReactiveWebServerFactory() {
} }
...@@ -33,4 +41,26 @@ public abstract class AbstractReactiveWebServerFactory ...@@ -33,4 +41,26 @@ public abstract class AbstractReactiveWebServerFactory
super(port); super(port);
} }
/**
* Return the absolute temp dir for given web server.
* @param prefix servlet container name
* @return The temp dir for given servlet container.
*/
protected File createTempDir(String prefix) {
try {
File tempDir = File.createTempFile(prefix + ".", "." + getPort());
tempDir.delete();
tempDir.mkdir();
tempDir.deleteOnExit();
return tempDir;
}
catch (IOException ex) {
throw new EmbeddedWebServerException(
"Unable to create tempDir. java.io.tmpdir is set to "
+ System.getProperty("java.io.tmpdir"),
ex);
}
}
} }
/*
* 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.jetty;
import java.net.InetSocketAddress;
import java.util.Map;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactory;
import org.springframework.boot.context.embedded.EmbeddedWebServer;
import org.springframework.boot.context.embedded.ReactiveWebServerFactory;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.JettyHttpHandlerAdapter;
/**
* {@link ReactiveWebServerFactory} that can be used to create
* {@link JettyWebServer}s.
*
* @author Brian Clozel
*/
public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
/**
* The number of acceptor threads to use.
*/
private int acceptors = -1;
/**
* The number of selector threads to use.
*/
private int selectors = -1;
private ThreadPool threadPool;
/**
* Create a new {@link JettyEmbeddedServletContainerFactory} instance.
*/
public JettyReactiveWebServerFactory() {
super();
}
/**
* Create a new {@link JettyEmbeddedServletContainerFactory} that listens for requests
* using the specified port.
* @param port the port to listen on
*/
public JettyReactiveWebServerFactory(int port) {
super(port);
}
@Override
public EmbeddedWebServer getReactiveHttpServer(HttpHandler httpHandler) {
JettyHttpHandlerAdapter servlet = new JettyHttpHandlerAdapter(httpHandler);
Server server = createJettyServer(servlet);
return new JettyWebServer(server, getPort() >= 0);
}
@Override
public EmbeddedWebServer getReactiveHttpServer(Map<String, HttpHandler> handlerMap) {
JettyHttpHandlerAdapter servlet = new JettyHttpHandlerAdapter(handlerMap);
Server server = createJettyServer(servlet);
return new JettyWebServer(server, getPort() >= 0);
}
protected Server createJettyServer(JettyHttpHandlerAdapter servlet) {
int port = (getPort() >= 0 ? getPort() : 0);
InetSocketAddress address = new InetSocketAddress(getAddress(), port);
Server server = new Server(getThreadPool());
server.addConnector(createConnector(address, server));
ServletHolder servletHolder = new ServletHolder(servlet);
ServletContextHandler contextHandler = new ServletContextHandler(server,
"", false, false);
contextHandler.addServlet(servletHolder, "/");
this.logger.info("Server initialized with port: " + port);
return server;
}
private AbstractConnector createConnector(InetSocketAddress address, Server server) {
ServerConnector connector = new ServerConnector(server, this.acceptors,
this.selectors);
connector.setHost(address.getHostName());
connector.setPort(address.getPort());
for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) {
((HttpConfiguration.ConnectionFactory) connectionFactory)
.getHttpConfiguration().setSendServerVersion(false);
}
}
return connector;
}
/**
* Returns a Jetty {@link ThreadPool} that should be used by the {@link Server}.
* @return a Jetty {@link ThreadPool} or {@code null}
*/
public ThreadPool getThreadPool() {
return this.threadPool;
}
/**
* Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
* @param threadPool a Jetty ThreadPool to be used
*/
public void setThreadPool(ThreadPool threadPool) {
this.threadPool = threadPool;
}
/**
* Set the number of acceptor threads to use.
* @param acceptors the number of acceptor threads to use
*/
public void setAcceptors(int acceptors) {
this.acceptors = acceptors;
}
/**
* Set the number of selector threads to use.
* @param selectors the number of selector threads to use
*/
public void setSelectors(int selectors) {
this.selectors = selectors;
}
}
/*
* 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.jetty;
import java.net.BindException;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.springframework.boot.context.embedded.EmbeddedWebServer;
import org.springframework.boot.context.embedded.EmbeddedWebServerException;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link EmbeddedWebServer} that can be used to control a Jetty web server.
* Usually this class should be created using the
* {@link JettyReactiveWebServerFactory} and not directly.
*
* @author Phillip Webb
* @author Dave Syer
* @author David Liu
* @author Eddú Meléndez
* @author Brian Clozel
* @see JettyReactiveWebServerFactory
*/
public class JettyWebServer implements EmbeddedWebServer {
private static final Log logger = LogFactory
.getLog(JettyWebServer.class);
private final Object monitor = new Object();
private final Server server;
private final boolean autoStart;
private Connector[] connectors;
private volatile boolean started;
/**
* Create a new {@link JettyWebServer} instance.
* @param server the underlying Jetty server
*/
public JettyWebServer(Server server) {
this(server, true);
}
/**
* Create a new {@link JettyWebServer} instance.
* @param server the underlying Jetty server
* @param autoStart if auto-starting the container
*/
public JettyWebServer(Server server, boolean autoStart) {
this.autoStart = autoStart;
Assert.notNull(server, "Jetty Server must not be null");
this.server = server;
initialize();
}
private void initialize() {
synchronized (this.monitor) {
try {
// Cache and clear the connectors to prevent requests being handled before
// the application context is ready
this.connectors = this.server.getConnectors();
this.server.setConnectors(null);
// Start the server so that the ServletContext is available
this.server.start();
this.server.setStopAtShutdown(false);
}
catch (Exception ex) {
// Ensure process isn't left running
stopSilently();
throw new EmbeddedWebServerException(
"Unable to start embedded Jetty web server", ex);
}
}
}
private void stopSilently() {
try {
this.server.stop();
}
catch (Exception ex) {
// Ignore
}
}
@Override
public void start() throws EmbeddedWebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
this.server.setConnectors(this.connectors);
if (!this.autoStart) {
return;
}
try {
this.server.start();
for (Handler handler : this.server.getHandlers()) {
handleDeferredInitialize(handler);
}
Connector[] connectors = this.server.getConnectors();
for (Connector connector : connectors) {
try {
connector.start();
}
catch (BindException ex) {
if (connector instanceof NetworkConnector) {
throw new PortInUseException(
((NetworkConnector) connector).getPort());
}
throw ex;
}
}
this.started = true;
JettyWebServer.logger
.info("Jetty started on port(s) " + getActualPortsDescription());
}
catch (EmbeddedWebServerException ex) {
throw ex;
}
catch (Exception ex) {
throw new EmbeddedWebServerException(
"Unable to start embedded Jetty servlet container", ex);
}
}
}
private String getActualPortsDescription() {
StringBuilder ports = new StringBuilder();
for (Connector connector : this.server.getConnectors()) {
ports.append(ports.length() == 0 ? "" : ", ");
ports.append(getLocalPort(connector) + getProtocols(connector));
}
return ports.toString();
}
private Integer getLocalPort(Connector connector) {
try {
// Jetty 9 internals are different, but the method name is the same
return (Integer) ReflectionUtils.invokeMethod(
ReflectionUtils.findMethod(connector.getClass(), "getLocalPort"),
connector);
}
catch (Exception ex) {
JettyWebServer.logger
.info("could not determine port ( " + ex.getMessage() + ")");
return 0;
}
}
private String getProtocols(Connector connector) {
List<String> protocols = connector.getProtocols();
return " (" + StringUtils.collectionToDelimitedString(protocols, ", ") + ")";
}
private void handleDeferredInitialize(Handler... handlers) throws Exception {
for (Handler handler : handlers) {
if (handler instanceof JettyEmbeddedWebAppContext) {
((JettyEmbeddedWebAppContext) handler).deferredInitialize();
}
else if (handler instanceof HandlerWrapper) {
handleDeferredInitialize(((HandlerWrapper) handler).getHandler());
}
else if (handler instanceof HandlerCollection) {
handleDeferredInitialize(((HandlerCollection) handler).getHandlers());
}
}
}
@Override
public void stop() {
synchronized (this.monitor) {
if (!this.started) {
return;
}
this.started = false;
try {
this.server.stop();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (Exception ex) {
throw new EmbeddedWebServerException(
"Unable to stop embedded Jetty web server", ex);
}
}
}
@Override
public int getPort() {
Connector[] connectors = this.server.getConnectors();
for (Connector connector : connectors) {
// Probably only one...
return getLocalPort(connector);
}
return 0;
}
/**
* Returns access to the underlying Jetty Server.
* @return the Jetty server
*/
public Server getServer() {
return this.server;
}
}
/*
* 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.reactor;
import java.util.Map;
import reactor.ipc.netty.http.server.HttpServer;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactory;
import org.springframework.boot.context.embedded.EmbeddedWebServer;
import org.springframework.boot.context.embedded.ReactiveWebServerFactory;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
/**
* {@link ReactiveWebServerFactory} that can be used to create
* {@link ReactorNettyWebServer}s.
*
* @author Brian Clozel
*/
public class ReactorNettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
public ReactorNettyReactiveWebServerFactory() {
}
public ReactorNettyReactiveWebServerFactory(int port) {
super(port);
}
@Override
public EmbeddedWebServer getReactiveHttpServer(HttpHandler httpHandler) {
HttpServer server = createHttpServer();
ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);
return new ReactorNettyWebServer(server, handlerAdapter);
}
@Override
public EmbeddedWebServer getReactiveHttpServer(Map<String, HttpHandler> handlerMap) {
HttpServer server = createHttpServer();
ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(handlerMap);
return new ReactorNettyWebServer(server, handlerAdapter);
}
private HttpServer createHttpServer() {
HttpServer server;
if (getAddress() != null) {
server = HttpServer.create(getAddress().getHostAddress(), getPort());
}
else {
server = HttpServer.create(getPort());
}
return server;
}
}
/*
* 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.reactor;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import reactor.core.Loopback;
import reactor.ipc.netty.NettyContext;
import reactor.ipc.netty.http.server.HttpServer;
import org.springframework.boot.context.embedded.EmbeddedWebServer;
import org.springframework.boot.context.embedded.EmbeddedWebServerException;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
/**
* {@link EmbeddedWebServer} that can be used to control a Reactor Netty web server.
* Usually this class should be created using the
* {@link ReactorNettyReactiveWebServerFactory} and not directly.
*
* @author Brian Clozel
*/
public class ReactorNettyWebServer implements EmbeddedWebServer, Loopback {
private static CountDownLatch latch = new CountDownLatch(1);
private final ReactorHttpHandlerAdapter handlerAdapter;
private final HttpServer reactorServer;
private AtomicReference<NettyContext> nettyContext = new AtomicReference<>();
public ReactorNettyWebServer(HttpServer reactorServer, ReactorHttpHandlerAdapter handlerAdapter) {
this.reactorServer = reactorServer;
this.handlerAdapter = handlerAdapter;
}
@Override
public Object connectedInput() {
return this.reactorServer;
}
@Override
public Object connectedOutput() {
return this.reactorServer;
}
@Override
public void start() throws EmbeddedWebServerException {
if (this.nettyContext.get() == null) {
this.nettyContext.set(this.reactorServer.newHandler(this.handlerAdapter).block());
startDaemonAwaitThread();
}
}
private void startDaemonAwaitThread() {
Thread awaitThread = new Thread("server") {
@Override
public void run() {
try {
ReactorNettyWebServer.latch.await();
}
catch (InterruptedException e) { }
}
};
awaitThread.setContextClassLoader(getClass().getClassLoader());
awaitThread.setDaemon(false);
awaitThread.start();
}
@Override
public void stop() throws EmbeddedWebServerException {
NettyContext context = this.nettyContext.getAndSet(null);
if (context != null) {
context.dispose();
}
latch.countDown();
}
@Override
public int getPort() {
if (this.nettyContext.get() != null) {
return this.nettyContext.get().address().getPort();
}
return 0;
}
}
/*
* 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.tomcat;
import java.io.File;
import java.util.Map;
import org.apache.catalina.Host;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.AbstractProtocol;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactory;
import org.springframework.boot.context.embedded.EmbeddedWebServer;
import org.springframework.boot.context.embedded.ReactiveWebServerFactory;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.TomcatHttpHandlerAdapter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link ReactiveWebServerFactory} that can be used to create
* {@link TomcatWebServer}s.
*
* @author Brian Clozel
*/
public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
/**
* The class name of default protocol used.
*/
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
private String protocol = DEFAULT_PROTOCOL;
/**
* Create a new {@link TomcatEmbeddedServletContainerFactory} instance.
*/
public TomcatReactiveWebServerFactory() {
super();
}
/**
* Create a new {@link TomcatEmbeddedServletContainerFactory} that listens for
* requests using the specified port.
* @param port the port to listen on
*/
public TomcatReactiveWebServerFactory(int port) {
super(port);
}
@Override
public EmbeddedWebServer getReactiveHttpServer(HttpHandler httpHandler) {
Tomcat tomcatServer = createTomcatServer();
TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
prepareContext(tomcatServer.getHost(), servlet);
return new TomcatWebServer(tomcatServer, getPort() >= 0);
}
@Override
public EmbeddedWebServer getReactiveHttpServer(Map<String, HttpHandler> handlerMap) {
Tomcat tomcatServer = createTomcatServer();
TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(handlerMap);
prepareContext(tomcatServer.getHost(), servlet);
return new TomcatWebServer(tomcatServer, getPort() >= 0);
}
private Tomcat createTomcatServer() {
Tomcat tomcat = new Tomcat();
File baseDir = createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
return tomcat;
}
protected void prepareContext(Host host, TomcatHttpHandlerAdapter servlet) {
File docBase = createTempDir("tomcat-docbase");
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
context.setPath("");
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new Tomcat.FixContextListener());
context.setParentClassLoader(ClassUtils.getDefaultClassLoader());
WebappLoader loader = new WebappLoader(context.getParentClassLoader());
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
context.setLoader(loader);
Tomcat.addServlet(context, "httpHandlerServlet", servlet);
context.addServletMappingDecoded("/", "httpHandlerServlet");
host.addChild(context);
}
// Needs to be protected so it can be used by subclasses
protected void customizeConnector(Connector connector) {
int port = (getPort() >= 0 ? getPort() : 0);
connector.setPort(port);
if (StringUtils.hasText(this.getServerHeader())) {
connector.setAttribute("server", this.getServerHeader());
}
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
}
// If ApplicationContext is slow to start we want Tomcat not to bind to the socket
// prematurely...
connector.setProperty("bindOnInit", "false");
}
private void customizeProtocol(AbstractProtocol<?> protocol) {
if (getAddress() != null) {
protocol.setAddress(getAddress());
}
}
/**
* Factory method called to create the {@link TomcatEmbeddedServletContainer}.
* Subclasses can override this method to return a different
* {@link TomcatEmbeddedServletContainer} or apply additional processing to the Tomcat
* server.
* @param tomcat the Tomcat server.
* @return a new {@link TomcatEmbeddedServletContainer} instance
*/
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}
/**
* The Tomcat protocol to use when create the {@link Connector}.
* @param protocol the protocol
* @see Connector#Connector(String)
*/
public void setProtocol(String protocol) {
Assert.hasLength(protocol, "Protocol must not be empty");
this.protocol = protocol;
}
}
/*
* 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.tomcat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.naming.NamingException;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Service;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.naming.ContextBindings;
import org.springframework.boot.context.embedded.EmbeddedWebServer;
import org.springframework.boot.context.embedded.EmbeddedWebServerException;
import org.springframework.util.Assert;
/**
* {@link EmbeddedWebServer} that can be used to control a Tomcat web server.
* Usually this class should be created using the
* {@link TomcatReactiveWebServerFactory} and not directly.
*
* @author Brian Clozel
*/
public class TomcatWebServer implements EmbeddedWebServer {
private static final Log logger = LogFactory
.getLog(TomcatEmbeddedServletContainer.class);
private static final AtomicInteger containerCounter = new AtomicInteger(-1);
private final Object monitor = new Object();
private final Map<Service, Connector[]> serviceConnectors = new HashMap<Service, Connector[]>();
private final Tomcat tomcat;
private final boolean autoStart;
private volatile boolean started;
/**
* Create a new {@link TomcatWebServer} instance.
* @param tomcat the underlying Tomcat server
*/
public TomcatWebServer(Tomcat tomcat) {
this(tomcat, true);
}
/**
* Create a new {@link TomcatEmbeddedServletContainer} instance.
* @param tomcat the underlying Tomcat server
* @param autoStart if the server should be started
*/
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
private void initialize() throws EmbeddedWebServerException {
TomcatWebServer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
// Remove service connectors so that protocol binding doesn't happen yet
removeServiceConnectors();
// Start the server to trigger initialization listeners
this.tomcat.start();
Context context = findContext();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
throw new EmbeddedWebServerException(
"Unable to start embedded Tomcat", ex);
}
}
}
private Context findContext() {
for (Container child : this.tomcat.getHost().findChildren()) {
if (child instanceof Context) {
return (Context) child;
}
}
throw new IllegalStateException("The host does not contain a Context");
}
private void addInstanceIdToEngineName() {
int instanceId = containerCounter.incrementAndGet();
if (instanceId > 0) {
Engine engine = this.tomcat.getEngine();
engine.setName(engine.getName() + "-" + instanceId);
}
}
private void removeServiceConnectors() {
for (Service service : this.tomcat.getServer().findServices()) {
Connector[] connectors = service.findConnectors().clone();
this.serviceConnectors.put(service, connectors);
for (Connector connector : connectors) {
service.removeConnector(connector);
}
}
}
private void startDaemonAwaitThread() {
Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
@Override
public void run() {
TomcatWebServer.this.tomcat.getServer().await();
}
};
awaitThread.setContextClassLoader(getClass().getClassLoader());
awaitThread.setDaemon(false);
awaitThread.start();
}
@Override
public void start() throws EmbeddedWebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
startConnector(connector);
}
checkThatConnectorsHaveStarted();
this.started = true;
TomcatWebServer.logger
.info("Tomcat started on port(s): " + getPortsDescription(true));
}
catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
throw new EmbeddedWebServerException(
"Unable to start embedded Tomcat server", ex);
}
finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
}
}
private void checkThatConnectorsHaveStarted() {
for (Connector connector : this.tomcat.getService().findConnectors()) {
if (LifecycleState.FAILED.equals(connector.getState())) {
throw new ConnectorStartFailedException(connector.getPort());
}
}
}
private void stopSilently() {
try {
stopTomcat();
}
catch (LifecycleException ex) {
// Ignore
}
}
private void stopTomcat() throws LifecycleException {
if (Thread.currentThread()
.getContextClassLoader() instanceof TomcatEmbeddedWebappClassLoader) {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
}
this.tomcat.stop();
}
private void addPreviouslyRemovedConnectors() {
Service[] services = this.tomcat.getServer().findServices();
for (Service service : services) {
Connector[] connectors = this.serviceConnectors.get(service);
if (connectors != null) {
for (Connector connector : connectors) {
service.addConnector(connector);
if (!this.autoStart) {
stopProtocolHandler(connector);
}
}
this.serviceConnectors.remove(service);
}
}
}
private void stopProtocolHandler(Connector connector) {
try {
connector.getProtocolHandler().stop();
}
catch (Exception ex) {
TomcatWebServer.logger.error("Cannot pause connector: ", ex);
}
}
private void startConnector(Connector connector) {
try {
for (Container child : this.tomcat.getHost().findChildren()) {
if (child instanceof TomcatEmbeddedContext) {
((TomcatEmbeddedContext) child).deferredLoadOnStartup();
}
}
}
catch (Exception ex) {
TomcatWebServer.logger.error("Cannot start connector: ", ex);
throw new EmbeddedWebServerException(
"Unable to start embedded Tomcat connectors", ex);
}
}
Map<Service, Connector[]> getServiceConnectors() {
return this.serviceConnectors;
}
@Override
public void stop() throws EmbeddedWebServerException {
synchronized (this.monitor) {
if (!this.started) {
return;
}
try {
this.started = false;
try {
stopTomcat();
this.tomcat.destroy();
}
catch (LifecycleException ex) {
// swallow and continue
}
}
catch (Exception ex) {
throw new EmbeddedWebServerException(
"Unable to stop embedded Tomcat", ex);
}
finally {
containerCounter.decrementAndGet();
}
}
}
private String getPortsDescription(boolean localPort) {
StringBuilder ports = new StringBuilder();
for (Connector connector : this.tomcat.getService().findConnectors()) {
ports.append(ports.length() == 0 ? "" : " ");
int port = (localPort ? connector.getLocalPort() : connector.getPort());
ports.append(port + " (" + connector.getScheme() + ")");
}
return ports.toString();
}
@Override
public int getPort() {
Connector connector = this.tomcat.getConnector();
if (connector != null) {
return connector.getLocalPort();
}
return 0;
}
/**
* Returns access to the underlying Tomcat server.
* @return the Tomcat server
*/
public Tomcat getTomcat() {
return this.tomcat;
}
}
/*
* 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.undertow;
import java.util.Map;
import io.undertow.Undertow;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactory;
import org.springframework.boot.context.embedded.EmbeddedWebServer;
import org.springframework.boot.context.embedded.ReactiveWebServerFactory;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
/**
* {@link ReactiveWebServerFactory} that can be used to create
* {@link UndertowWebServer}s.
*
* @author Brian Clozel
*/
public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
private Integer bufferSize;
private Integer ioThreads;
private Integer workerThreads;
private Boolean directBuffers;
/**
* Create a new {@link UndertowReactiveWebServerFactory} instance.
*/
public UndertowReactiveWebServerFactory() {
}
/**
* Create a new {@link UndertowReactiveWebServerFactory} that listens for
* requests using the specified port.
* @param port the port to listen on
*/
public UndertowReactiveWebServerFactory(int port) {
super(port);
}
@Override
public EmbeddedWebServer getReactiveHttpServer(HttpHandler httpHandler) {
Undertow.Builder builder = createBuilder(getPort());
UndertowHttpHandlerAdapter handler = new UndertowHttpHandlerAdapter(httpHandler);
builder.setHandler(handler);
return new UndertowWebServer(builder, getPort() >= 0);
}
@Override
public EmbeddedWebServer getReactiveHttpServer(Map<String, HttpHandler> handlerMap) {
Undertow.Builder builder = createBuilder(getPort());
UndertowHttpHandlerAdapter handler = new UndertowHttpHandlerAdapter(handlerMap);
builder.setHandler(handler);
return new UndertowWebServer(builder, getPort() >= 0);
}
private Undertow.Builder createBuilder(int port) {
Undertow.Builder builder = Undertow.builder();
if (this.bufferSize != null) {
builder.setBufferSize(this.bufferSize);
}
if (this.ioThreads != null) {
builder.setIoThreads(this.ioThreads);
}
if (this.workerThreads != null) {
builder.setWorkerThreads(this.workerThreads);
}
if (this.directBuffers != null) {
builder.setDirectBuffers(this.directBuffers);
}
builder.addHttpListener(port, getListenAddress());
return builder;
}
private String getListenAddress() {
if (getAddress() == null) {
return "0.0.0.0";
}
return getAddress().getHostAddress();
}
}
/*
* 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.undertow;
import java.lang.reflect.Field;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import io.undertow.Undertow;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xnio.channels.BoundChannel;
import org.springframework.boot.context.embedded.EmbeddedWebServer;
import org.springframework.boot.context.embedded.EmbeddedWebServerException;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link EmbeddedWebServer} that can be used to control a Jetty web server.
* Usually this class should be created using the
* {@link UndertowReactiveWebServerFactory} and not directly.
*
* @author Ivan Sopov
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Christoph Dreis
* @author Brian Clozel
*/
public class UndertowWebServer implements EmbeddedWebServer {
private static final Log logger = LogFactory
.getLog(UndertowEmbeddedServletContainer.class);
private final Object monitor = new Object();
private final Undertow.Builder builder;
private final boolean autoStart;
private Undertow undertow;
private volatile boolean started = false;
/**
* Create a new {@link UndertowWebServer} instance.
* @param builder the builder
* @param autoStart if the server should be started
*/
public UndertowWebServer(Undertow.Builder builder, boolean autoStart) {
this.builder = builder;
this.autoStart = autoStart;
}
@Override
public void start() throws EmbeddedWebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
if (!this.autoStart) {
return;
}
if (this.undertow == null) {
this.undertow = this.builder.build();
}
this.undertow.start();
this.started = true;
UndertowWebServer.logger
.info("Undertow started on port(s) " + getPortsDescription());
}
catch (Exception ex) {
if (findBindException(ex) != null) {
List<UndertowWebServer.Port> failedPorts = getConfiguredPorts();
List<UndertowWebServer.Port> actualPorts = getActualPorts();
failedPorts.removeAll(actualPorts);
if (failedPorts.size() == 1) {
throw new PortInUseException(
failedPorts.iterator().next().getNumber());
}
}
throw new EmbeddedWebServerException(
"Unable to start embedded Undertow", ex);
}
}
}
private BindException findBindException(Exception ex) {
Throwable candidate = ex;
while (candidate != null) {
if (candidate instanceof BindException) {
return (BindException) candidate;
}
candidate = candidate.getCause();
}
return null;
}
private String getPortsDescription() {
List<UndertowWebServer.Port> ports = getActualPorts();
if (!ports.isEmpty()) {
return StringUtils.collectionToDelimitedString(ports, " ");
}
return "unknown";
}
private List<UndertowWebServer.Port> getActualPorts() {
List<UndertowWebServer.Port> ports = new ArrayList<UndertowWebServer.Port>();
try {
if (!this.autoStart) {
ports.add(new UndertowWebServer.Port(-1, "unknown"));
}
else {
for (BoundChannel channel : extractChannels()) {
ports.add(getPortFromChannel(channel));
}
}
}
catch (Exception ex) {
// Continue
}
return ports;
}
@SuppressWarnings("unchecked")
private List<BoundChannel> extractChannels() {
Field channelsField = ReflectionUtils.findField(Undertow.class, "channels");
ReflectionUtils.makeAccessible(channelsField);
return (List<BoundChannel>) ReflectionUtils.getField(channelsField,
this.undertow);
}
private UndertowWebServer.Port getPortFromChannel(BoundChannel channel) {
SocketAddress socketAddress = channel.getLocalAddress();
if (socketAddress instanceof InetSocketAddress) {
String protocol = ReflectionUtils.findField(channel.getClass(), "ssl") != null
? "https" : "http";
return new UndertowWebServer.Port(((InetSocketAddress) socketAddress).getPort(), protocol);
}
return null;
}
private List<UndertowWebServer.Port> getConfiguredPorts() {
List<UndertowWebServer.Port> ports = new ArrayList<UndertowWebServer.Port>();
for (Object listener : extractListeners()) {
try {
ports.add(getPortFromListener(listener));
}
catch (Exception ex) {
// Continue
}
}
return ports;
}
@SuppressWarnings("unchecked")
private List<Object> extractListeners() {
Field listenersField = ReflectionUtils.findField(Undertow.class, "listeners");
ReflectionUtils.makeAccessible(listenersField);
return (List<Object>) ReflectionUtils.getField(listenersField, this.undertow);
}
private UndertowWebServer.Port getPortFromListener(Object listener) {
Field typeField = ReflectionUtils.findField(listener.getClass(), "type");
ReflectionUtils.makeAccessible(typeField);
String protocol = ReflectionUtils.getField(typeField, listener).toString();
Field portField = ReflectionUtils.findField(listener.getClass(), "port");
ReflectionUtils.makeAccessible(portField);
int port = (Integer) ReflectionUtils.getField(portField, listener);
return new UndertowWebServer.Port(port, protocol);
}
@Override
public void stop() throws EmbeddedWebServerException {
synchronized (this.monitor) {
if (!this.started) {
return;
}
this.started = false;
try {
this.undertow.stop();
}
catch (Exception ex) {
throw new EmbeddedWebServerException("Unable to stop undertow",
ex);
}
}
}
@Override
public int getPort() {
List<UndertowWebServer.Port> ports = getActualPorts();
if (ports.isEmpty()) {
return 0;
}
return ports.get(0).getNumber();
}
/**
* An active Undertow port.
*/
private final static class Port {
private final int number;
private final String protocol;
private Port(int number, String protocol) {
this.number = number;
this.protocol = protocol;
}
public int getNumber() {
return this.number;
}
@Override
public String toString() {
return this.number + " (" + this.protocol + ")";
}
@Override
public int hashCode() {
return this.number;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
UndertowWebServer.Port other = (UndertowWebServer.Port) obj;
if (this.number != other.number) {
return false;
}
return true;
}
}
}
/*
* 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.jetty;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactory;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactoryTests;
/**
* Tests for {@link JettyReactiveWebServerFactory} and
* {@link JettyWebServer}.
*
* @author Brian Clozel
*/
public class JettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactoryTests {
@Override
protected AbstractReactiveWebServerFactory getFactory() {
return new JettyReactiveWebServerFactory(0);
}
}
/*
* 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.reactor;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactory;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactoryTests;
/**
* Tests for {@link ReactorNettyReactiveWebServerFactory} and
* {@link ReactorNettyWebServer}.
*
* @author Brian Clozel
*/
public class ReactorNettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactoryTests {
@Override
protected AbstractReactiveWebServerFactory getFactory() {
return new ReactorNettyReactiveWebServerFactory(0);
}
}
/*
* 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.tomcat;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactory;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactoryTests;
/**
* Tests for {@link TomcatReactiveWebServerFactory} and
* {@link TomcatWebServer}.
*
* @author Brian Clozel
*/
public class TomcatReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactoryTests {
@Override
protected AbstractReactiveWebServerFactory getFactory() {
return new TomcatReactiveWebServerFactory(0);
}
}
/*
* 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.undertow;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactory;
import org.springframework.boot.context.embedded.AbstractReactiveWebServerFactoryTests;
/**
* Tests for {@link UndertowReactiveWebServerFactory} and
* {@link UndertowWebServer}.
*
* @author Brian Clozel
*/
public class UndertowReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactoryTests {
@Override
protected AbstractReactiveWebServerFactory getFactory() {
return new UndertowReactiveWebServerFactory(0);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment