diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java index 4b84a97e00..c81e4fb5a4 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java @@ -38,4 +38,6 @@ public @interface AutoConfigureWireMock { int port() default 8080; + int httpsPort() default -1; + } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/SpringBootHttpServerFactory.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/SpringBootHttpServerFactory.java index 78c5a41208..2d1c3a3a05 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/SpringBootHttpServerFactory.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/SpringBootHttpServerFactory.java @@ -40,6 +40,7 @@ import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.Ssl; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; @@ -57,7 +58,9 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.event.EventListener; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.stereotype.Component; import org.springframework.web.context.ServletContextAware; import com.github.tomakehurst.wiremock.common.HttpsSettings; @@ -126,17 +129,19 @@ class SpringBootHttpServer @Override public int port() { - if (options.httpsSettings().enabled()) { - return options.portNumber(); - } - EmbeddedWebApplicationContext embedded = (EmbeddedWebApplicationContext) context; - return embedded.getEmbeddedServletContainer().getPort(); + return container().port(); } @Override public int httpsPort() { - // TODO HTTPS on random port - return this.options.httpsSettings().port(); + return container().httpsPort(); + } + + private ContainerProperties container() { + if (this.context != null) { + return context.getBean(ContainerProperties.class); + } + return new ContainerProperties(options); } @Override @@ -207,7 +212,7 @@ class SpringBootHttpServer UndertowContainerConfiguration.class, ServerPropertiesAutoConfiguration.class, BeanPostProcessorsRegistrar.class, ConfigurationPropertiesAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + PropertyPlaceholderAutoConfiguration.class, ContainerProperties.class }) class WiremockServerConfiguration { @Autowired @@ -255,6 +260,51 @@ class WiremockServerConfiguration { } +@Component +class ContainerProperties { + + private Options options; + + private Integer localPort; + + private Integer localHttpsPort; + + @Autowired + private ConfigurableApplicationContext context; + + @Autowired + public ContainerProperties(Options options) { + this.options = options; + } + + public int port() { + if (options.httpsSettings().enabled()) { + return options.portNumber(); + } + if (this.localPort != null) { + return this.localPort; + } + EmbeddedWebApplicationContext embedded = (EmbeddedWebApplicationContext) context; + return embedded.getEmbeddedServletContainer().getPort(); + } + + public int httpsPort() { + if (this.localHttpsPort != null) { + return this.localHttpsPort; + } + return this.options.httpsSettings().port(); + } + + public void setLocalPort(int localPort) { + this.localPort = localPort; + } + + public void setLocalHttpsPort(int localHttpsPort) { + this.localHttpsPort = localHttpsPort; + } + +} + class ContainerConfiguration { @Configuration @@ -264,6 +314,11 @@ class ContainerConfiguration { @Autowired private Options options; + @Autowired + private ContainerProperties container; + + private Connector connector; + @Bean public EmbeddedServletContainerFactory servletContainer() { TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory(); @@ -273,10 +328,20 @@ class ContainerConfiguration { return tomcat; } + @EventListener + public void serverUp(EmbeddedServletContainerInitializedEvent event) { + if (connector != null) { + container.setLocalPort(connector.getLocalPort()); + container + .setLocalHttpsPort(event.getEmbeddedServletContainer().getPort()); + } + } + private Connector createStandardConnector() { Connector connector = new Connector( "org.apache.coyote.http11.Http11NioProtocol"); connector.setPort(this.options.portNumber()); + this.connector = connector; return connector; } @@ -286,9 +351,15 @@ class ContainerConfiguration { @ConditionalOnMissingBean(EmbeddedServletContainerFactory.class) @ConditionalOnClass({ UndertowEmbeddedServletContainerFactory.class, Builder.class }) static class UndertowContainerConfiguration { + @Autowired private Options options; + @Autowired + private ContainerProperties container; + + private Integer port; + @Bean public EmbeddedServletContainerFactory servletContainer() { UndertowEmbeddedServletContainerFactory undertow = new UndertowEmbeddedServletContainerFactory(); @@ -297,11 +368,23 @@ class ContainerConfiguration { @Override public void customize(Builder builder) { builder.addHttpListener(options.portNumber(), "localhost"); + UndertowContainerConfiguration.this.port = options.portNumber(); } }); } return undertow; } + + @EventListener + public void serverUp(EmbeddedServletContainerInitializedEvent event) { + if (port != null) { + // TODO: make it dynamic as well + container.setLocalPort(port); + container + .setLocalHttpsPort(event.getEmbeddedServletContainer().getPort()); + } + } + } @Configuration @@ -309,9 +392,15 @@ class ContainerConfiguration { @ConditionalOnClass({ JettyEmbeddedServletContainerFactory.class, ServerConnector.class }) static class JettyContainerConfiguration { + + @Autowired + private ContainerProperties container; + @Autowired private Options options; + private ServerConnector connector; + @Bean public EmbeddedServletContainerFactory servletContainer() { final JettyEmbeddedServletContainerFactory jetty = new JettyEmbeddedServletContainerFactory(); @@ -338,8 +427,18 @@ class ContainerConfiguration { .getHttpConfiguration().setSendServerVersion(false); } } + this.connector = connector; return connector; } + + @EventListener + public void serverUp(EmbeddedServletContainerInitializedEvent event) { + if (connector != null) { + container.setLocalPort(connector.getLocalPort()); + container + .setLocalHttpsPort(event.getEmbeddedServletContainer().getPort()); + } + } } } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockApplicationListener.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockApplicationListener.java index 3861c75325..43bff5ef69 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockApplicationListener.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockApplicationListener.java @@ -49,7 +49,17 @@ public class WireMockApplicationListener implements ApplicationListener source = ((MapPropertySource) propertySources .get("wiremock")).getSource(); - source.put("wiremock.server.port", SocketUtils.findAvailableTcpPort(10000, 15000)); + source.put("wiremock.server.port", SocketUtils.findAvailableTcpPort(10000, 12500)); + } + if (environment.getProperty("wiremock.server.https-port", Integer.class, 0) == 0) { + MutablePropertySources propertySources = environment.getPropertySources(); + if (!propertySources.contains("wiremock")) { + propertySources.addFirst( + new MapPropertySource("wiremock", new HashMap())); + } + Map source = ((MapPropertySource) propertySources + .get("wiremock")).getSource(); + source.put("wiremock.server.https-port", SocketUtils.findAvailableTcpPort(12500, 15000)); } } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java index 460a9b700f..34e7e61a56 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java @@ -43,30 +43,37 @@ public class WireMockConfiguration implements SmartLifecycle, ImportAware { @Autowired(required = false) private Options options; - + @Value("${wiremock.server.port:8080}") private int port = 8080; + @Value("${wiremock.server.https-port:-1}") + private int httpsPort = -1; + @Override public void setImportMetadata(AnnotationMetadata metadata) { - int port = AnnotationAttributes - .fromMap(metadata - .getAnnotationAttributes(AutoConfigureWireMock.class.getName())) - .getNumber("port").intValue(); - if (port>0) { + AnnotationAttributes map = AnnotationAttributes.fromMap( + metadata.getAnnotationAttributes(AutoConfigureWireMock.class.getName())); + int port = map.getNumber("port").intValue(); + if (port > 0) { this.port = port; } + int httpsPort = map.getNumber("httpsPort").intValue(); + if (httpsPort > 0) { + this.httpsPort = httpsPort; + } } @PostConstruct public void init() { if (options == null) { - com.github.tomakehurst.wiremock.core.WireMockConfiguration factory = com.github.tomakehurst.wiremock.core.WireMockConfiguration - .wireMockConfig() - .httpServerFactory(new SpringBootHttpServerFactory()); + com.github.tomakehurst.wiremock.core.WireMockConfiguration factory = WireMockSpring.options(); if (port != 8080) { factory.port(port); } + if (httpsPort != -1) { + factory.httpsPort(httpsPort); + } this.options = factory; } server = new WireMockServer(options); diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockHttpsPortApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockHttpsPortApplicationTests.java new file mode 100644 index 0000000000..49ba82b61c --- /dev/null +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockHttpsPortApplicationTests.java @@ -0,0 +1,33 @@ +package org.springframework.cloud.contract.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = WiremockTestsApplication.class, properties = "app.baseUrl=https://localhost:${wiremock.server.https-port}", webEnvironment = WebEnvironment.NONE) +@DirtiesContext +@AutoConfigureWireMock(httpsPort = 9999) +public class AutoConfigureWireMockHttpsPortApplicationTests { + + @Autowired + private Service service; + + @Test + public void contextLoads() throws Exception { + stubFor(get(urlEqualTo("/resource")).willReturn(aResponse() + .withHeader("Content-Type", "text/plain").withBody("Hello World!"))); + assertThat(this.service.go()).isEqualTo("Hello World!"); + } + +} diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockRandomPortHttpsApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockRandomPortHttpsApplicationTests.java new file mode 100644 index 0000000000..9ef87efe02 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockRandomPortHttpsApplicationTests.java @@ -0,0 +1,33 @@ +package org.springframework.cloud.contract.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=https://localhost:${wiremock.server.https-port}", webEnvironment=WebEnvironment.NONE) +@DirtiesContext +@AutoConfigureWireMock(port=0, httpsPort=0) +public class AutoConfigureWireMockRandomPortHttpsApplicationTests { + + @Autowired + private Service service; + + @Test + public void contextLoads() throws Exception { + stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); + assertThat(this.service.go()).isEqualTo("Hello World!"); + } + +}