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?
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.