Facing DeadlineTimeoutException while using Apache HttpClient with Spring webclient - spring-webclient

I have setup WebClient with apache httpclient 5 connector. Here my code snippet
#Bean
public WebClient webClient() {
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
final PoolingAsyncClientConnectionManager connManager =
PoolingAsyncClientConnectionManagerBuilder
.create()
.build();
clientBuilder.setConnectionManager(connManager);
CloseableHttpAsyncClient client = clientBuilder.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);
return WebClient.builder().clientConnector(connector).build();
}
I did a loading test with my rest API which internally calls remote URL using WebClient configuration as mentioned above. I see many requests failed with error
Caused by: org.apache.hc.core5.util.DeadlineTimeoutException: Deadline: 2022-02-02T14:01:23.844+0530, -451 MILLISECONDS overdue
at org.apache.hc.core5.util.DeadlineTimeoutException.from(DeadlineTimeoutException.java:49)
at org.apache.hc.core5.pool.StrictConnPool.processPendingRequest(StrictConnPool.java:318)
at org.apache.hc.core5.pool.StrictConnPool.processNextPendingRequest(StrictConnPool.java:299)
I am not able to find what configuration should be added in order to fix the issue. Can anyone please help me to solve this issue?

Related

Spring Framework WebClient not sending request when using Apache HttpComponents

I'm building an application that need to call an endpoint using NTLM authentication. My approach is that I try to use the Apache HttpComponents for the NTLM authentication and integrate the Spring WebClient with it. However, the WebClient doesn't seem to send any request at all. There's no errors but the response won't be returned.
Below is my code:
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(null, -1), new NTCredentials(username, password, computername, domain));
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
clientBuilder.setDefaultRequestConfig(RequestConfig.DEFAULT);
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);
WebClient.builder().clientConnector(connector).build();
ResponseDto response = webClient.post()
.uri("http://myhost:8080/api/notification/add")
.body(Mono.just(request), RequestDto.class)
.retrieve()
.bodyToMono(ResponseDto.class).block();

Spring Boot Proxy via HttpClient doesn't work

I'm trying to setup a WebClient connection in Spring Boot using a proxy. My implementation looks like the following:
final WebClient.Builder webclientBuilder = WebClient.builder();
final HttpClient httpClient = HttpClient.create();
httpClient.proxy(proxy -> proxy
.type(Proxy.HTTP)
.host(proxyName)
.port(Integer.parseInt(proxyPort)));
final ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
webclientBuilder.clientConnector(connector);
final WebClient webClient = webclientBuilder
.baseUrl(baseUrl)
.build();
After running it and sending an API call, I receive a "Connection timed out: no further information". I should get back a Bad Request (in case my call is wrong), but I don't.
Is the implementation wrong?
the proxyName is written like this: "proxy.blabla.de"
After some trial and error and comparing I found a solution working for me:
String baseUrl = "https://mybaseurl";
String proxyName = "proxy.blabla.de";
int proxyPort = 1234;
public InitResponse addAccount() {
// for logging purposes, nothing to do with the proxy
LOGGER.info("LOGGER.info: addAccount()");
final InitRequest request = buildRequest();
HttpClient httpClient = HttpClient.create()
.proxy(proxy -> proxy.type(Proxy.HTTP)
.host(proxyName)
.port(proxyPort));
ReactorClientHttpConnector conn = new ReactorClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder().clientConnector(conn).baseUrl(baseUrl).build();

what is wrong with below webclient config?

I am facing connection error. Log entries are
readAddress(..) failed: Connection reset by peer; nested exception is io.netty.channel.unix.Errors$NativeIoException: readAddress(..) failed: Connection reset by peer
the connection observed an error
Pending acquire queue has reached its maximum size of 1000; nested exception is reactor.netty.internal.shaded.reactor.pool.PoolAcquirePendingLimitException
Webclient config is:
#Bean
public WebClient webClient(#Autowired ObjectMapperBean objectMapperBean) {
ConnectionProvider provider =
ConnectionProvider
.builder("custom")
.maxConnections(500)
.build();
HttpClient httpClient = HttpClient.create(provider);
ExchangeStrategies exchangeStrategies =
ExchangeStrategies
.builder()
.codecs(codecConfigurer -> codecConfigurer
.defaultCodecs()
.jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapperBean.getObjectMapper(), MediaType.APPLICATION_JSON)))
.build();
return WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.exchangeStrategies(exchangeStrategies)
.build();
}
I am not sure where the problem is. Can someone help me on this?
#springboot #webclient
By default the number of pending queue requests allowed for a reactor netty server is
2 * number of connections
if not provided explicitly. You need to take care of these configuration on the basis of your throughput expectations. You can modify this property as follows :
ConnectionProvider.builder(CONNECTION_NAME)
.maxConnections(<MaxConnectionThreads>)
.pendingAcquireTimeout(Duration.ofMillis(<PendingAcquireTimeout>))
.pendingAcquireMaxCount(<maxCount>)
.maxIdleTime(Duration.ofMillis(<MaxIdleTime>))
.build();

Spring Webclient not able to create connections

I'm using Spring Webclient in a Spring Boot project to call a remote API
I've observed a strange behaviour with Webclient. Whenever the Remote API timeouts increase, Webclient goes into a state with very few active connections (less than 5, even though maxConnections is set in the config as 3200), and all the incoming connections get added to the Pending Queue, due to which after a while almost all requests are rejected with a PoolAcquirePendingLimitException exception.
The expected behaviour is that Webclient should create new connections (max upto 3200) to handle the incoming traffic
Webclient Config is as follows:
#Bean
public WebClient webClient(WebClient.Builder builder)
{
TcpClient tcpClient = TcpClient.create(getConnectionProvider())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.wiretap(true)
.doOnConnected(connection ->
connection.addHandlerLast(new ReadTimeoutHandler(10000, TimeUnit.MILLISECONDS)));
ClientHttpConnector connector = new ReactorClientHttpConnector(HttpClient.from(tcpClient));
return builder.uriBuilderFactory(initUriTemplateHandler())
.clientConnector(connector)
.build();
}
private ConnectionProvider getConnectionProvider()
{
return ConnectionProvider.builder("fixed")
.maxConnections(3200)
.pendingAcquireTimeout(Duration.ofMillis(10000))
.pendingAcquireMaxCount(10000)
.maxIdleTime(Duration.ofMinutes(10))
.metrics(true)
.build();
}
private DefaultUriBuilderFactory initUriTemplateHandler()
{
DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory();
uriFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
return uriFactory;
}
This is how I'm making the API calls
T response = webClient.get()
.uri(url)
.retrieve()
.bodyToMono(responseClass)
.timeout(Duration.ofMillis(requestTimeout)) // varies between 15-20ms
.block();
Below is a screenshot of the metrics[![enter image description here][2]][2]
This is a very high traffic application, and hence virtually it feels like the Pending Queue is stuck at 10000
Dependency Versions:
spring-boot-starter-webflux: 2.2.4.RELEASE
reactory-netty: 0.9.5.RELEASE

Proxy setting not working with Spring WebClient

My following WebClient is working fine with internet connection but not through our proxy connection.
WebClient webClient = WebClient.builder()
.baseUrl("https://targetsite.com")
.build();
webClient.post()
.uri("/service/serviceName")
.body(BodyInserters.fromObject(reqData))
.retrieve()
.bodyToMono(WebServiceResponse.class)
Event though, the same client is working through proxy, if I set it as mentioned below,
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(tcpClient -> tcpClient
.proxy(proxy -> proxy
.type(ProxyProvider.Proxy.HTTP)
.host("ourproxy.com")
.port(8080)));
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder()
.clientConnector(connector)
.baseUrl("https://targetsite.com")
.build();
webClient.post()
.uri("/service/serviceName")
.body(BodyInserters.fromObject(reqData))
.retrieve()
.bodyToMono(WebServiceResponse.class)
But if I set the same Proxy details either using System.setProperty("http.proxyHost","ourproxy.com");
System.setProperty("http.proxyPort","8080");
or
JVM run-time arguments -Dhttp.proxyHost=ourproxy.com -Dhttp.proxyPort=8080
WebClient webClient = WebClient.builder()
.baseUrl("https://targetsite.com")
.build();
System.setProperty("http.proxyHost", "ourproxy.com");
System.setProperty("http.proxyPort", "8080");
webClient.post()
.uri("/service/serviceName")
.body(BodyInserters.fromObject(reqData))
.retrieve()
.bodyToMono(WebServiceResponse.class)
The calls are getting failed with UnknownHostException like,
[04/11/2019 12:32:43.031 IST] DEBUG [reactor-http-epoll-3] [PooledConnectionProvider:254] - Creating new client pool [http] for targetsite.com:443
[04/11/2019 12:32:43.033 IST] DEBUG [reactor-http-epoll-3] [PooledConnectionProvider:254] - [id: 0xe4a0dc15] Created new pooled channel, now 0 active connections and 1 inactive connections
[04/11/2019 12:32:43.045 IST] DEBUG [reactor-http-epoll-3] [SslProvider:254] - [id: 0xe4a0dc15] SSL enabled using engine SSLEngineImpl and SNI targetsite.com:443
[04/11/2019 12:32:43.046 IST] DEBUG [reactor-http-epoll-3] [BootstrapHandlers:254] - [id: 0xe4a0dc15] Initialized pipeline DefaultChannelPipeline{(reactor.left.sslHandler = io.netty.handler.ssl.SslHandler), (reactor.left.sslReader = reactor.netty.tcp.SslProvider$SslReadHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.left.decompressor = io.netty.handler.codec.http.HttpContentDecompressor), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
[04/11/2019 12:32:43.165 IST] ERROR [reactor-http-epoll-2] [AbstractErrorWebExceptionHandler:117] - [13ebf1eb] 500 Server Error for HTTP POST "/service/serviceName"
java.net.UnknownHostException: targetsite.com: Name or service not known
Please help, why my code is not functioning if I set the proxy details via JVM run-time arguments or system properties.
Actually I want to avoid code level proxy setting. So please guide me to correct my code or approach, so that I can use JVM run-time argument option.
Later when I raised this concern with reactor-netty team(https://github.com/reactor/reactor-netty/issues/887#issuecomment-549439355), it was confirmed by them that, system properties are not supported by reactor.netty.http.client.HttpClient. The only option is setting it through tcpConfiguration, which I mentioned in my question already; so I am closing this question with this remarks.
Thank you all
You need to set Proxy Host and Port while creating HttpClient instance per bellow.
HttpClient httpClient =
HttpClient.create()
.proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP)
.host(sasConfig.getProxyHost())
.port(Integer.parseInt(sasConfig.getProxyPort())));
ReactorClientHttpConnector conn = new ReactorClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder().clientConnector(conn).build();
I don't have enough reputation to comment on other answers so i have to start my own answer here.
Since reactor-netty v1.0.8, proxy can be configured from system properties.
Example:
WebClient
.builder()
.clientConnector(
new ReactorClientHttpConnector(
HttpClient.create().proxyWithSystemProperties()))
.build()
I had similar situation and struggled to find what in the end was a stupid mistake :
I enabled wiretap :
val httpClient = HttpClient.create()
.wiretap(
MyClient::class.java.toString(), LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL)
webClient=WebClient.builder()
.clientConnector(ReactorClientHttpConnector(httpClient))
.codecs(consumerCodecForIncreasedPayloadSize)
.build()
I was then able to spot something strange in the logs :
[21119d8d] CONNECT: http://myProxy.my.company.com/<unresolved>:8080
what was that <unresolved> thing when issuing a CONNECT command to the proxy ?
then I finally realized : the https.proxyHost value I was setting at startup should not be http://myProxy.my.company.com , but simply myProxy.my.company.com, without the http://
then it worked.
Here is what worked for me
Step 1 : Define proxy environment variables
-Dhttp.proxyHost=<proxyHost>
-Dhttp.proxyPort=8080
-Dhttps.proxyHost=<proxyHost>
-Dhttps.proxyPort=8080
-Dhttps.nonProxyHosts=localhost
Configuration of proxy on webClient
#Configuration
public class WebClientConfiguration {
#Bean
public WebClient webClient() {
return WebClient.builder() //
.defaultHeader(ACCEPT, APPLICATION_JSON_VALUE) //
.clientConnector(new ReactorClientHttpConnector(httpClient())) //
.build();
}
private HttpClient httpClient() {
return HttpClient //
.create() //
.proxyWithSystemProperties();
}
}
Set the spring cloud proxy properties (In the application start)
static {
String nonProxyHosts = System.getProperty("http.nonProxyHosts");
if (nonProxyHosts != null) {
String regexProxyList = nonProxyHosts.replaceAll("\\.", "\\\\.").replaceAll("\\/", "\\\\/").replaceAll("\\*", ".\\*");
System.setProperty("spring.cloud.gateway.httpclient.proxy.non-proxy-hosts-pattern", regexProxyList);
}
String proxyHost = System.getProperty("https.proxyHost");
String proxyPort = System.getProperty("https.proxyPort");
if (proxyHost != null && proxyPort != null) {
System.setProperty("spring.cloud.gateway.httpclient.proxy.host", proxyHost);
System.setProperty("spring.cloud.gateway.httpclient.proxy.port", proxyPort);
}
}

Resources