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
This commit is contained in:
@@ -100,6 +100,11 @@
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.ipc</groupId>
|
||||
<artifactId>reactor-netty</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.cache</groupId>
|
||||
<artifactId>cache-api</artifactId>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 @@
|
||||
<artifactId>json-simple</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.ipc</groupId>
|
||||
<artifactId>reactor-netty</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.jms</groupId>
|
||||
<artifactId>javax.jms-api</artifactId>
|
||||
@@ -326,6 +331,10 @@
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.addons</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -26,6 +32,8 @@ public abstract class AbstractReactiveWebServerFactory
|
||||
extends AbstractConfigurableReactiveWebServer
|
||||
implements ReactiveWebServerFactory {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
public AbstractReactiveWebServerFactory() {
|
||||
}
|
||||
|
||||
@@ -33,4 +41,26 @@ public abstract class AbstractReactiveWebServerFactory
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.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);
|
||||
}
|
||||
}
|
||||
@@ -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.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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user