Skip to content

WebClient|RestTemplate|WireMock : Mixing async httpClient create some blocking process #34435

Open
@alexisgayte

Description

While I am not sure this bug is directly linked to spring framework.

If you use the test framework with WebTestClient and restTemplate connecting to a mockServer. It creates a blocking process at the restTemplate level.

I first wanted to check if you guys were aware of this issue?

It seems to be linked to the mock server as well. As hitting an external server directly will work.

So WebClient(async) used in the test + RestTemplate in the app + MockServer --> return a concurrent issue.
So RestTemplate used in the test + RestTemplate in the app + MockServer --> works fine
So WebClient(async) used in the test + RestTemplate in the app + Direct server --> work fine

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {

    RestTemplate restTemplate;

    TestController() {
        restTemplate = new RestTemplateBuilder()
                .build();
    }

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<TestResponse> postTransaction(@Valid @RequestBody TestRequest testRequest,
                                                               @RequestHeader HttpHeaders headers) {
        
        HttpEntity<testRequest> entity = new HttpEntity<>(testRequest, headers);
        restTemplate.postForObject("http://localhost:6666/myTest", entity, TestResponse.class);

        return null;
    }

}

@ActiveProfiles("integration-test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@AutoConfigureWireMock(files = "file:./happy-path", port = 6666)
public class TestControllerIT {
    @Autowired
    private WebTestClient client;
    
    @Test
    public void testWithWebClient() throws Exception {

        client.post().uri("/test")
                .bodyValue(testRequest)
                .header("Content-Type", "application/json")
                .exchange()
                .expectStatus().isEqualTo(200)
                .expectBody();

    }
}

Same test using RestTemplate will work fine.

@ActiveProfiles("integration-test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@AutoConfigureWireMock(files = "file:./happy-path", port = 6666)
public class TestControllerIT {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate testRestTemplate;

    @Test
    public void testWithRestTemplate() throws Exception {
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        ResponseEntity<String> entity = testRestTemplate.exchange("http://localhost:" + port + "/test",
                HttpMethod.POST,
                new HttpEntity<>(testRequest, headers),
                String.class);

        assertEquals(HttpStatusCode.valueOf(200), entity.getStatusCode());
    }
}

The error generated is :

10:24:54.004 [Test worker] WARN  i.o.t.c.ExceptionAdviceHandler - Generic exception thrown: 500 Server Error: "{<EOL>"cause2":"java.util.concurrent.TimeoutException: Idle timeout expired: 30004/30000 ms",<EOL>"cause1":"java.io.IOException: java.util.concurrent.TimeoutException: Idle timeout expired: 30004/30000 ms",<EOL>"servlet":"com.github.tomakehurst.wiremock.servlet.WireMockHandlerDispatchingServlet-3b84359c",<EOL>"cause0":"java.lang.RuntimeException: java.io.IOException: java.util.concurrent.TimeoutException: Idle timeout expired: 30004/30000 ms",<EOL>"message":"java.lang.RuntimeException: java.io.IOException: java.util.concurrent.TimeoutException: Idle timeout expired: 30004/30000 ms",<EOL>"url":"/test",<EOL>"status":"500"<EOL>}"
org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 Server Error: "{<EOL>"cause2":"java.util.concurrent.TimeoutException: Idle timeout expired: 30004/30000 ms",<EOL>"cause1":"java.io.IOException: java.util.concurrent.TimeoutException: Idle timeout expired: 30004/30000 ms",<EOL>"servlet":"com.github.tomakehurst.wiremock.servlet.WireMockHandlerDispatchingServlet-3b84359c",<EOL>"cause0":"java.lang.RuntimeException: java.io.IOException: java.util.concurrent.TimeoutException: Idle timeout expired: 30004/30000 ms",<EOL>"message":"java.lang.RuntimeException: java.io.IOException: java.util.concurrent.TimeoutException: Idle timeout expired: 30004/30000 ms",<EOL>"url":"/test",<EOL>"status":"500"<EOL>}"
	at org.springframework.web.client.HttpServerErrorException.create(HttpServerErrorException.java:102)
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:186)
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:137)
	at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
	at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:942)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:891)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:790)
	at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:507)
	at xxxTestController.postTransaction(TestController.java:64)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:547)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
	at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
	at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:165)
	at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
	at org.springframework.test.web.servlet.setup.MockMvcFilterDecorator.doFilter(MockMvcFilterDecorator.java:151)
	at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.springframework.test.web.servlet.setup.MockMvcFilterDecorator.doFilter(MockMvcFilterDecorator.java:151)
	at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.springframework.test.web.servlet.setup.MockMvcFilterDecorator.doFilter(MockMvcFilterDecorator.java:151)
	at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
	at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:113)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.springframework.test.web.servlet.setup.MockMvcFilterDecorator.doFilter(MockMvcFilterDecorator.java:151)
	at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.springframework.test.web.servlet.setup.MockMvcFilterDecorator.doFilter(MockMvcFilterDecorator.java:151)
	at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
	at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:201)
	at org.springframework.test.web.servlet.client.MockMvcHttpConnector.connect(MockMvcHttpConnector.java:106)
	at org.springframework.test.web.reactive.server.WiretapConnector.connect(WiretapConnector.java:71)
	at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.exchange(ExchangeFunctions.java:102)
	at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultRequestBodyUriSpec.exchange(DefaultWebTestClient.java:359)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    in: webIssues in web modules (web, webmvc, webflux, websocket)status: feedback-providedFeedback has been providedstatus: waiting-for-triageAn issue we've not yet triaged or decided on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions