From 51f7ef6f671da08eadb950581d34fb07000114e4 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sun, 16 Dec 2018 13:36:40 +0100 Subject: [PATCH] `web` dependency is optional, `StubRunner` listener conditional without this change we always include `spring-boot-starter-web` to Stub Runner. With this change the dependency is optional. Thanks to this reactive applications will be able to work fine with contract on the classpath. without this change we always run Stub Runner test execution listener, even if the test class is not annotated. With this change we run the listener only, if the test class was annotated with `@AutoConfigureStubRunner`. Also we add information about the `java.net.SocketException: Unexpected end of file from server` exception to the docs and how to override that issue. Fixes gh-798, gh-809, gh-799 --- README.adoc | 24 ++++++- docs/src/main/asciidoc/README.adoc | 1 + docs/src/main/asciidoc/verifier_faq.adoc | 38 +++++++++- .../standalone/restdocs/http-client/pom.xml | 24 +++++++ .../example/loan/LoanApplicationService.java | 38 +++++++--- .../com/example/loan/XmlServiceTests.java | 18 +++++ .../pom.xml | 18 +++++ ...ubRunnerWireMockTestExecutionListener.java | 69 +++++++++++++------ .../wiremock/WireMockHttpServerStub.java | 22 +++++- .../wiremock/WireMockConfiguration.java | 32 ++++----- .../WireMockTestExecutionListener.java | 20 +++++- .../contract/wiremock/WireMockUtils.java | 65 +++++++++++++++++ 12 files changed, 317 insertions(+), 52 deletions(-) create mode 100644 spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockUtils.java diff --git a/README.adoc b/README.adoc index bf075d806d..46633f445c 100644 --- a/README.adoc +++ b/README.adoc @@ -5,6 +5,7 @@ image::https://badges.gitter.im/Join%20Chat.svg[Gitter, link="https://gitter.im/ image::https://codecov.io/gh/spring-cloud/spring-cloud-contract/branch/{branch}/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-contract"] image::https://circleci.com/gh/spring-cloud/spring-cloud-contract.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-contract"] :introduction_url: ../../../.. +:verifier_core_path: {introduction_url}/spring-cloud-contract-verifier == Spring Cloud Contract @@ -331,14 +332,33 @@ The following example shows a Camel messaging contract expressed in Groovy DSL: [source,groovy] ---- -Unresolved directive in verifier_introduction.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_dsl] +def contractDsl = Contract.make { + label 'some_label' + input { + messageFrom('jms:delete') + messageBody([ + bookName: 'foo' + ]) + messageHeaders { + header('sample', 'header') + } + assertThat('bookWasDeleted()') + } +} ---- The following example shows the same contract expressed in YAML: [source,yml,indent=0] ---- -Unresolved directive in verifier_introduction.adoc - include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario3.yml[indent=0] +label: some_label +input: + messageFrom: jms:delete + messageBody: + bookName: 'foo' + messageHeaders: + sample: header + assertThat: bookWasDeleted() ---- Then you can add Spring Cloud Contract Verifier dependency and plugin to your build file, diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index cebcc2184f..554865b748 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -3,6 +3,7 @@ image::https://badges.gitter.im/Join%20Chat.svg[Gitter, link="https://gitter.im/ image::https://codecov.io/gh/spring-cloud/spring-cloud-contract/branch/{branch}/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-contract"] image::https://circleci.com/gh/spring-cloud/spring-cloud-contract.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-contract"] :introduction_url: ../../../.. +:verifier_core_path: {introduction_url}/spring-cloud-contract-verifier == Spring Cloud Contract diff --git a/docs/src/main/asciidoc/verifier_faq.adoc b/docs/src/main/asciidoc/verifier_faq.adoc index 10a5ccc3ae..13d24a3bc5 100644 --- a/docs/src/main/asciidoc/verifier_faq.adoc +++ b/docs/src/main/asciidoc/verifier_faq.adoc @@ -936,4 +936,40 @@ started will be attached. Yes! With version 1.2.0 we've added such a possibility. It's enough to call `file(...)` method in the DSL and provide a path relative to where the contract lays. -If you're using YAML just use the `bodyFromFile` property. \ No newline at end of file +If you're using YAML just use the `bodyFromFile` property. + +==== Why do I sometimes get `SocketException` + +When using an HTTP client (e.g. `RestTemplate`) and running several tests that share a Spring context, you might sometimes get the following exception: + +``` +java.net.SocketException: Unexpected end of file from server +``` + +Tom Akehurst, the creator of WireMock did the following analysis of this issue. + +> I looked at tcpdump while running the failing test. `HttpUrlConnection` is doing something weird - it's creating a connection in a previous test case, which works fine, then the usual `fin` -> `fin ack` etc. ending handshake happens. But it seems it isn't discarded, but reused after that. Because the server thinks (rightly) that the connection is closed, it just sends a RST packet. Calling the `/__admin` endpoint just happened to remove the dead connection from the pool. This also fixes the problem (which using the Java HTTP client): System.setProperty("http.keepAlive", "false"); + +There are the ways to solve this problem. + +First, just use a different HTTP client for `RestTemplate`. Example for using Apache HTTP client: + +.pom.xml +[source,xml,indent=0] +---- +include::{standalone_restdocs_path}/http-client/pom.xml[tags=httpclient,indent=0] +---- + +[source,java,indent=0] +---- +include::{standalone_restdocs_path}/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=custom_request_factory,indent=0] +---- + +Second option is to set the system property. You can set it either in code or pass it to your tests via a plugin. + +[source,java,indent=0] +---- +static { + System.setProperty("http.keepAlive", "false"); +} +---- \ No newline at end of file diff --git a/samples/standalone/restdocs/http-client/pom.xml b/samples/standalone/restdocs/http-client/pom.xml index 7ff7dd6969..09680c9f50 100644 --- a/samples/standalone/restdocs/http-client/pom.xml +++ b/samples/standalone/restdocs/http-client/pom.xml @@ -1,4 +1,21 @@ + + 4.0.0 @@ -53,6 +70,13 @@ spring-cloud-contract-stub-runner test + + + org.apache.httpcomponents + httpclient + compile + + com.example http-server-restdocs diff --git a/samples/standalone/restdocs/http-client/src/main/java/com/example/loan/LoanApplicationService.java b/samples/standalone/restdocs/http-client/src/main/java/com/example/loan/LoanApplicationService.java index 6c5d0861f5..9f3c759b9e 100644 --- a/samples/standalone/restdocs/http-client/src/main/java/com/example/loan/LoanApplicationService.java +++ b/samples/standalone/restdocs/http-client/src/main/java/com/example/loan/LoanApplicationService.java @@ -1,12 +1,21 @@ -package com.example.loan; +/* + * Copyright 2013-2018 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. + * + */ -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; +package com.example.loan; import com.example.loan.model.FraudCheckStatus; import com.example.loan.model.FraudServiceRequest; @@ -15,6 +24,15 @@ import com.example.loan.model.LoanApplication; import com.example.loan.model.LoanApplicationResult; import com.example.loan.model.LoanApplicationStatus; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + @Service @ConfigurationProperties("service") public class LoanApplicationService { @@ -28,6 +46,10 @@ public class LoanApplicationService { public LoanApplicationService() { this.restTemplate = new RestTemplate(); + // tag::custom_request_factory[] + this.restTemplate + .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + // end::custom_request_factory[] } public LoanApplicationResult loanApplication(LoanApplication loanApplication) { diff --git a/samples/standalone/restdocs/http-client/src/test/java/com/example/loan/XmlServiceTests.java b/samples/standalone/restdocs/http-client/src/test/java/com/example/loan/XmlServiceTests.java index 351ce62021..77e0b2682b 100644 --- a/samples/standalone/restdocs/http-client/src/test/java/com/example/loan/XmlServiceTests.java +++ b/samples/standalone/restdocs/http-client/src/test/java/com/example/loan/XmlServiceTests.java @@ -1,3 +1,20 @@ +/* + * Copyright 2013-2018 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 com.example.loan; import java.net.URI; @@ -8,6 +25,7 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping; import org.assertj.core.api.BDDAssertions; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; diff --git a/spring-cloud-contract-starters/spring-cloud-starter-contract-stub-runner/pom.xml b/spring-cloud-contract-starters/spring-cloud-starter-contract-stub-runner/pom.xml index c071f6ac50..9d1ef620ff 100644 --- a/spring-cloud-contract-starters/spring-cloud-starter-contract-stub-runner/pom.xml +++ b/spring-cloud-contract-starters/spring-cloud-starter-contract-stub-runner/pom.xml @@ -1,4 +1,21 @@ + + 4.0.0 @@ -20,6 +37,7 @@ org.springframework.boot spring-boot-starter-web + true org.springframework.boot diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/StubRunnerWireMockTestExecutionListener.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/StubRunnerWireMockTestExecutionListener.java index 7bd3005ffa..8b777c471d 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/StubRunnerWireMockTestExecutionListener.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/StubRunnerWireMockTestExecutionListener.java @@ -1,3 +1,20 @@ +/* + * Copyright 2013-2018 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.cloud.contract.stubrunner.provider.wiremock; import java.util.List; @@ -7,12 +24,13 @@ import java.util.concurrent.ConcurrentHashMap; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.cloud.contract.stubrunner.HttpServerStub; +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; +import org.springframework.cloud.contract.wiremock.WireMockUtils; import org.springframework.context.ApplicationContext; import org.springframework.test.context.TestContext; import org.springframework.test.context.support.AbstractTestExecutionListener; -import org.springframework.util.Assert; -import org.springframework.web.client.RestTemplate; /** * Stops the {@link HttpServerStub} after each test class @@ -20,46 +38,55 @@ import org.springframework.web.client.RestTemplate; * @author Marcin Grzejszczak * @since 1.2.6 */ -public final class StubRunnerWireMockTestExecutionListener extends AbstractTestExecutionListener { +public final class StubRunnerWireMockTestExecutionListener + extends AbstractTestExecutionListener { - private static final Log log = LogFactory.getLog(StubRunnerWireMockTestExecutionListener.class); + private static final Log log = LogFactory + .getLog(StubRunnerWireMockTestExecutionListener.class); private static Map> STUBS = new ConcurrentHashMap<>(); - @Override public void beforeTestClass(TestContext testContext) { + @Override + public void beforeTestClass(TestContext testContext) { + if (testContext.getTestClass().getAnnotationsByType(AutoConfigureStubRunner.class).length == 0) { + if (log.isTraceEnabled()) { + log.trace("No @AutoConfigureStubRunner annotation found on [" + testContext.getTestClass() + "]. Skipping"); + } + return; + } Map stubs = STUBS .get(testContext.getApplicationContext()); if (stubs != null) { if (log.isDebugEnabled()) { - log.debug("Found a matching application context from [" + testContext.getTestClass().getName() + "]"); + log.debug("Found a matching application context from [" + + testContext.getTestClass().getName() + "]"); } - for (Map.Entry entry : stubs.entrySet()) { + for (Map.Entry entry : stubs + .entrySet()) { while (entry.getKey().isRunning()) { entry.getKey().stop(); } List mappings = entry.getValue().mappings; if (log.isDebugEnabled()) { - log.debug("Stopped a running WireMock instance at " - + "port [" + entry.getValue().port + "] with stub mappings " - + "size [" + mappings.size() + "]. Restarting the stub."); + log.debug("Stopped a running WireMock instance at " + "port [" + + entry.getValue().port + "] with stub mappings size [" + + mappings.size() + "]. Restarting the stub."); } entry.getKey().start(entry.getValue().port); entry.getKey().registerDescriptors(mappings); - /* - Thanks to Tom Akehurst: - I looked at tcpdump while running the failing test. HttpUrlConnection is doing something weird - it's creating a connection in a - previous test case, which works fine, then the usual fin -> fin ack etc. etc. ending handshake happens. But it seems it - isn't discarded, but reused after that. Because the server thinks (rightly) that the connection is closed, it just sends a RST packet. - Calling the admin endpoint just happened to remove the dead connection from the pool. - This also fixes the problem (which using the Java HTTP client): System.setProperty("http.keepAlive", "false"); - */ - Assert.isTrue(new RestTemplate().getForEntity("http://localhost:" + entry.getValue().port + "/__admin/mappings", String.class) - .getStatusCode().is2xxSuccessful(), "__admin/mappings endpoint wasn't accessible"); + WireMockUtils.getMappingsEndpoint(entry.getValue().port); } } } - @Override public void afterTestClass(TestContext testContext) { + @Override + public void afterTestClass(TestContext testContext) { + if (testContext.getTestClass().getAnnotationsByType(AutoConfigureStubRunner.class).length == 0) { + if (log.isTraceEnabled()) { + log.trace("No @AutoConfigureStubRunner annotation found on [" + testContext.getTestClass() + "]. Skipping"); + } + return; + } STUBS.put(testContext.getApplicationContext(), WireMockHttpServerStub.SERVERS); if (log.isDebugEnabled()) { log.debug("Stopping servers " + WireMockHttpServerStub.SERVERS); diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java index e08a310f52..747e34b503 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java @@ -1,3 +1,20 @@ +/* + * Copyright 2013-2018 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.cloud.contract.stubrunner.provider.wiremock; import java.io.File; @@ -20,6 +37,8 @@ import com.github.tomakehurst.wiremock.extension.Extension; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import wiremock.com.github.jknack.handlebars.Helper; + import org.springframework.cloud.contract.stubrunner.HttpServerStub; import org.springframework.cloud.contract.verifier.builder.handlebars.HandlebarsEscapeHelper; import org.springframework.cloud.contract.verifier.builder.handlebars.HandlebarsJsonPathHelper; @@ -31,7 +50,6 @@ import org.springframework.util.ClassUtils; import org.springframework.util.SocketUtils; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; -import wiremock.com.github.jknack.handlebars.Helper; /** * Abstraction over WireMock as a HTTP Server Stub @@ -115,7 +133,7 @@ public class WireMockHttpServerStub implements HttpServerStub { log.debug("Started WireMock at port [" + port + "]"); } if (!SERVERS.containsKey(this)) { - SERVERS.put(this, new PortAndMappings(port, new ArrayList())); + SERVERS.put(this, new PortAndMappings(port, new ArrayList<>())); } return this; } 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 b75f391fee..8ac5ea3a3e 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 @@ -1,17 +1,18 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2013-2018 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 + * 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.cloud.contract.wiremock; @@ -20,6 +21,7 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; + import javax.annotation.PostConstruct; import com.github.tomakehurst.wiremock.WireMockServer; @@ -28,6 +30,7 @@ import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.github.tomakehurst.wiremock.core.Options; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -38,10 +41,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.util.Assert; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; -import org.springframework.web.client.RestTemplate; /** * Configuration and lifecycle for a Spring Application context that wants to run a @@ -50,7 +51,7 @@ import org.springframework.web.client.RestTemplate; * configure the properties of the wiremock server you can use the AutoConfigureWireMock * annotation, or add a bean of type {@link Options} (via * {@link WireMockSpring#options()}) to your test context. - * + * * @author Dave Syer * */ @@ -103,7 +104,8 @@ public class WireMockConfiguration implements SmartLifecycle { } registerStubs(); if (log.isDebugEnabled()) { - log.debug("WireMock server has [" + this.server.getStubMappings().size() + "] registered"); + log.debug("WireMock server has [" + this.server.getStubMappings().size() + + "] stubs registered"); } if (!this.beanFactory.containsBean(WIREMOCK_SERVER_BEAN_NAME)) { this.beanFactory.registerSingleton(WIREMOCK_SERVER_BEAN_NAME, this.server); @@ -130,6 +132,10 @@ public class WireMockConfiguration implements SmartLifecycle { } } + int port() { + return this.server.port(); + } + void reset() { this.server.resetAll(); } @@ -161,22 +167,14 @@ public class WireMockConfiguration implements SmartLifecycle { if (log.isDebugEnabled()) { log.debug("Started WireMock at port [" + this.server.port() + "]. It has [" + this.server.getStubMappings().size() + "] mappings registered"); } - /* - Thanks to Tom Akehurst: - I looked at tcpdump while running the failing test. HttpUrlConnection is doing something weird - it's creating a connection in a - previous test case, which works fine, then the usual fin -> fin ack etc. etc. ending handshake happens. But it seems it - isn't discarded, but reused after that. Because the server thinks (rightly) that the connection is closed, it just sends a RST packet. - Calling the admin endpoint just happened to remove the dead connection from the pool. - This also fixes the problem (which using the Java HTTP client): System.setProperty("http.keepAlive", "false"); - */ - Assert.isTrue(new RestTemplate().getForEntity("http://localhost:" + this.server.port() + "/__admin/mappings", String.class) - .getStatusCode().is2xxSuccessful(), "__admin/mappings endpoint wasn't accessible"); + WireMockUtils.getMappingsEndpoint(this.port()); } @Override public void stop() { if (this.running) { - this.server.stop(); + reset(); + this.server.shutdownServer(); this.running = false; if (log.isDebugEnabled()) { log.debug("Stopped WireMock instance"); diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java index 77947f65bf..fcddd23755 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java @@ -1,7 +1,25 @@ +/* + * Copyright 2013-2018 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.cloud.contract.wiremock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.test.context.TestContext; import org.springframework.test.context.support.AbstractTestExecutionListener; @@ -25,9 +43,9 @@ public final class WireMockTestExecutionListener extends AbstractTestExecutionLi log.debug("WireMock configuration is running [" + wireMockConfiguration.isRunning() + "]"); } if (!wireMockConfiguration.isRunning()) { - wireMockConfiguration.reset(); wireMockConfiguration.init(); wireMockConfiguration.start(); + WireMockUtils.getMappingsEndpoint(wireMockConfiguration.port()); } } catch (Exception e) { if (log.isDebugEnabled()) { diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockUtils.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockUtils.java new file mode 100644 index 0000000000..5b700ff9c8 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2018 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.cloud.contract.wiremock; + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; + +import org.springframework.util.Assert; + +/** + * Utility class to work with WireMock. + * + * @author Marcin Grzejszczak + * @since 2.0.3 + */ +public final class WireMockUtils { + + private WireMockUtils() { + throw new IllegalStateException("Don't instantiate"); + } + + /** + * Thanks to Tom Akehurst: I looked at tcpdump while running the failing + * test. HttpUrlConnection is doing something weird - it's creating a + * connection in a previous test case, which works fine, then the usual + * fin -> fin ack etc. etc. ending handshake happens. But it seems it + * isn't discarded, but reused after that. Because the server thinks + * (rightly) that the connection is closed, it just sends a RST packet. + * Calling the admin endpoint just happened to remove the dead connection + * from the pool. This also fixes the problem (which using the Java HTTP + * client): System.setProperty("http.keepAlive", "false"); + **/ + public static CloseableHttpResponse getMappingsEndpoint(int port) { + CloseableHttpClient client = HttpClientBuilder.create().build(); + try { + CloseableHttpResponse response = client + .execute(new HttpHost("localhost", port), new HttpGet("/__admin/mappings")); + Assert.isTrue(response.getStatusLine().getStatusCode() == 200, "Status code must be 200"); + return response; + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } +}