Configuring timeout on a per request basis for Spring WebClient? - spring

I'm aware of Spring 5 webflux how to set a timeout on Webclient but this configures the timeout globally for all requests. I'm looking for a way to configure the timeout on a per request basis. I.e. something like this (pseudo-code that doesn't work):
WebClient client = ...
// Call 1
client.timeout(5, TimeUnit.SECONDS).contentType(APPLICATION_JSON).syncBody(..).exchange(). ..
// Call 2
client.timeout(4, TimeUnit.SECONDS).contentType(APPLICATION_JSON).syncBody(..).exchange().
The timeout function is made-up to demonstrate what I'm after. How can I achieve this? It's also important that resources are cleaned up properly on timeout.
If it makes any difference I'm using Netty (reactor-netty 0.8.4.RELEASE):
HttpClient httpClient = HttpClient.create(). ...;
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();

These two answers clearly explains it.
set-timeout-in-spring-webflux-webclient
spring-5-webflux-how-to-set-a-timeout-on-webclient.
Additionally if you are looking to mutate the options,
you could do like below,
TcpClient tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)
.doOnConnected(connection ->
connection.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10)));
return this.webClient
.mutate()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.build()
.get()

Related

how to update reactive WebClient object to have Retry policy at a central place

I have an already existing application, and we are making calls to other application from this.
So, now, in our application we control the creation of WebClient object from a single place and then some other config are set on this like below :
ObjectMapper objectMapper = new ObjectMapper();
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs(configure ->
configure
.defaultCodecs()
.jackson2JsonDecoder(
new Jackson2JsonDecoder(objectMapper)))
.build();
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 12000)
.responseTimeout(Duration.ofMillis(60000));
return WebClient.builder()
.exchangeStrategies(exchangeStrategies)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl(modelDeployConfig.getHost())
.defaultHeaders( h -> {
h.setContentType(MediaType.APPLICATION_JSON);
h.setAccept(List.of(MediaType.APPLICATION_JSON));
})
.build();
Now, this webclient is build using this logic and used across the application. I want to impose some retry policies in our application which are common for all APIs.
But I dont find a way to do this at webclient level? any idea how to achieve this? any config settings available or any other way to suggest to achieve this where we can have some central retry policy which gets imposed on all APIs through this webclient object or by any other wrapper for webclient?

webclient set header reactive way

I am new to WebFlux and reactive way of programming. I am using WebClient to call a REST service, when making the call, I need to set a HTTP header, the value for the HTTP header comes from a reactive stream, but the header method on the WebClient only takes String value...
Mono<String> getHeader() {
...
}
webClient
.post()
.uri("/test")
.body(Mono.just(req),req.getClass())
.header("myHeader",getHeader())
...
The above line won't compile, since header method takes an String for second argument. How can I set a header if the value comes from a reactive stream?
You just need to chain getHeader and web client request using flatMap to create reactive flow
return getHeader()
.flatMap(header ->
webClient
.post()
.uri("/test")
.header("myHeader", header)
.body(BodyInserters.fromValue(req))
...
)

Spring WebFlux rate limit WebClient

Given I create my WebClient using the builder() pattern, e.g. somewhat like this:
WebClient.builder()
.uriBuilderFactory(defaultUriBuilderFactory)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
.defaultHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate")
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(clientCodecConfigurer -> clientCodecConfigurer
.defaultCodecs()
.maxInMemorySize(16 * 1024 * 1024))
.build())
.build();
Is there any possibility to do rate limiting within the WebClient itself?
I saw some answers, that I can do the rate limiting when I do the request. I would rather define it within the WebClient, because the same WebClient is used for different requests, so I need to set an overall rate limit for this WebClient.
By "rate limit" I mean: How many requests this WebClient is allowed to send per second. For example: I want to limit this WebClient to only send 5 requests per second.
If this is not possible using the WebClient, are there any alternatives that make sense?

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

Resources