EN VI

Java - 'EOF reached while reading' with Spring RestClient and Wiremock using JdkClientHttpRequestFactory?

2024-03-13 17:30:12
How to Java - 'EOF reached while reading' with Spring RestClient and Wiremock using JdkClientHttpRequestFactory

I keep getting a 'EOF reached while reading' exception trying to write a Wiremock test in my Spring Boot application that uses RestClient.

I managed to write a small reproducer project at https://github.com/wimdeblauwe/restclient-jdk-issue

When you run the MyGatewayTest, it fails with:

org.springframework.web.client.ResourceAccessException: I/O error on PUT request for "http://localhost:57591/something": EOF reached while reading

    at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.createResourceAccessException(DefaultRestClient.java:557)
    at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:482)
    at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.retrieve(DefaultRestClient.java:444)
    at org.springframework.web.client.support.RestClientAdapter.exchangeForBody(RestClientAdapter.java:73)
    at org.springframework.web.service.invoker.HttpServiceMethod$ExchangeResponseFunction.lambda$create$4(HttpServiceMethod.java:379)
    at org.springframework.web.service.invoker.HttpServiceMethod$ExchangeResponseFunction.execute(HttpServiceMethod.java:336)
    at org.springframework.web.service.invoker.HttpServiceMethod.invoke(HttpServiceMethod.java:130)
    at org.springframework.web.service.invoker.HttpServiceProxyFactory$HttpServiceMethodInterceptor.invoke(HttpServiceProxyFactory.java:303)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:220)
    at jdk.proxy2/jdk.proxy2.$Proxy16.doSomething(Unknown Source)
    at com.example.restclientjdkissue.MyGateway.doSomething(MyGateway.java:15)
    at com.example.restclientjdkissue.MyGatewayTest.testDoSomething(MyGatewayTest.java:27)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.io.IOException: EOF reached while reading
    at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:964)
    at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:133)
    at org.springframework.http.client.JdkClientHttpRequest.executeInternal(JdkClientHttpRequest.java:102)
    at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:70)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)
    at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:476)
    ... 14 more
Caused by: java.io.EOFException: EOF reached while reading
    at java.net.http/jdk.internal.net.http.Http2Connection$Http2TubeSubscriber.onComplete(Http2Connection.java:1655)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadSubscription.signalCompletion(SocketTube.java:648)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.read(SocketTube.java:853)
    at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowTask.run(SocketTube.java:181)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:207)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:280)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:233)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.signalReadable(SocketTube.java:782)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadEvent.signalEvent(SocketTube.java:965)
    at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowEvent.handle(SocketTube.java:253)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.handleEvent(HttpClientImpl.java:1467)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.lambda$run$3(HttpClientImpl.java:1412)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:1412)

However, if you explicitly configure the requestFactory on the RestClient to use SimpleClientHttpRequestFactory, then the test is ok:

  @Bean
  public RestClient restClient() {
    return RestClient.builder()
        .requestFactory(new SimpleClientHttpRequestFactory())
        .baseUrl(properties.baseUrl())
        .build();
  }

I wonder if this is a bug in RestClient, the JdkClienthttpRequestFactory, or somewhere else?

Solution:

The JdkClient by default will use HTTP/2 and normally it will fallback to HTTP/1.1, however apparently wiremock doesn't handle this to well. This is also recorded in the WireMock issue tracker.

When the JdkClient is explicitly set to use HTTP/1.1 it works.

@Bean
public RestClient restClient() {
  var client = (HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
  var requestFactory = new JdkClientHttpRequestFactory(client);
  return RestClient.builder()
      .requestFactory(requestFactory)
      .baseUrl(properties.baseUrl())
      .build();
  }

See also this question regarding the HTTP Client configuration.

According to this issue HTTP/2 should be supported by Wiremock but it doesn't seem to fallback very good.

Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login