[Spring Boot] How to solve WebClient Connection reset by peer error
I had a requirement to fetch user data from an external system. It was implemented using WebClient
as part of declarative http client
. Interestingly, the very first request after opening the application on a new browser tab returned Connection reset by peer
error.
This issue was solved by disabling keep-alive
for outgoing requests.
Before:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class HttpProxyConfiguration {
@Value("${tracker.url}")
private String trackerUrl;
@Bean
TrackerClient trackerClient(WebClient.Builder builder) {
var wc = builder.baseUrl(trackerUrl)
.build();
var wca = WebClientAdapter.forClient(wc);
return HttpServiceProxyFactory.builder()
.clientAdapter(wca)
.build()
.createClient(TrackerClient.class);
}
}
After:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import reactor.netty.http.client.HttpClient;
@Configuration
public class HttpProxyConfiguration {
@Value("${tracker.url}")
private String trackerUrl;
@Bean
TrackerClient trackerClient(WebClient.Builder builder) {
var httpClient = HttpClient.newConnection().keepAlive(false);
var reactorClientHttpConnector = new ReactorClientHttpConnector(httpClient);
var wc = builder.baseUrl(trackerUrl)
.clientConnector(reactorClientHttpConnector)
.build();
var wca = WebClientAdapter.forClient(wc);
return HttpServiceProxyFactory.builder()
.clientAdapter(wca)
.build()
.createClient(TrackerClient.class);
}
Update (2023–09–29)
After looking this github issue, it seemed that disabling connection pool or disabling keep alive is not recommended. So I applied the solution mentioned in this github issue to set up some connection timeouts.
@Configuration
public class HttpProxyConfiguration {
@Value("${tracker.url}")
private String trackerUrl;
@Bean
TrackerClient trackerClient(WebClient.Builder builder) {
ConnectionProvider provider = ConnectionProvider.builder("fixed")
.maxConnections(500)
.maxIdleTime(Duration.ofSeconds(20))
.maxLifeTime(Duration.ofSeconds(60))
.pendingAcquireTimeout(Duration.ofSeconds(60))
.evictInBackground(Duration.ofSeconds(120)).build();
HttpClient httpClient = HttpClient.create(provider);
httpClient.warmup().block();
var reactorClientHttpConnector = new ReactorClientHttpConnector(httpClient);
var wc = builder.baseUrl(trackerUrl)
.clientConnector(reactorClientHttpConnector)
.build();
var wca = WebClientAdapter.forClient(wc);
return HttpServiceProxyFactory.builder()
.clientAdapter(wca)
.build()
.createClient(TrackerClient.class);
}
}
The code mentioned above has been in production for at least two weeks now. So far I haven’t encountered any errors yet. By setting up the connection pool, I had hoped to improve the application’s performance, particularly because it often makes numerous requests to another service to acquire all necessary data for the frontend. I plan to continue monitoring this solution over time to assess its impact and determine whether it indeed enhances the application’s performance.