Spring WebFlux rate limit WebClient - spring

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?

Related

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 WebClient ClientResponse times out on bodyToMono

This is the API call (blocking) I am making to external application concurrently (max of 100 calls at the same time)
ClientResponse response = webclient.get()
.uri("https-url-here-with-query-params-here")
.header(HttpHeaders.AUTHORIZATION, "abcdefg")
.exchange()
.timeout(Duration.ofSeconds(300))
.block();
if (response.statusCode().is2xxSuccessful()) {
MyPojoEntity myPojo = response.bodyToMono(MyPojoEntity.class)
.timeout(Duration.ofSeconds(300))
.block();
}
What I observe is the call to response.bodyToMono(MyPojoEntity.class).timeout(Duration.ofSeconds(300)) is timing out after 5 minutes. My understanding is ClientResponse already has response body from the server and response.bodyToMono method just unmarshalling to pojo entity class. The payload is very small and shouldn't take more than few seconds to unmarshall it. May be it is still reading the response from server and timing out due to API issue on the server? If that is the case, then what does if (response.statusCode().is2xxSuccessful()) mean? I expect when response status is success, payload also to be there especially when i retrieve ClientResponse in a blocking way. Please help me to understand what is going on here.

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

How to expose webClient metrics in prometheus?

I want to expose the metrics of a WebClient call to a downstream system from the service, metrics like count of request, min, max time for the response is needed.
I want to know how I can write a gauge for a reactive webclient.
Here is a sample MeterBinder that I'm interested to use with webclient.
class Metrics : MeterBinder {
override fun bindTo(registry: MeterRegistry) {
Gauge.builder("metrics", Supplier { Math.random() })
.baseUnit("status")
.register(registry)
}
}
If you want to get the metrics of the WebClient call you can use ExchangeFilterFunction which is used as an interceptor. By default, there is one implementation of ExchangeFilterFunction i.e MetricsWebClientFilterFunction which can be added as a filter with your WebClient to give metrics like Number of request count, response time and total response time.
val metricsWebClientFilterFunction = MetricsWebClientFilterFunction(meterRegistry, DefaultWebClientExchangeTagsProvider(), "webClientMetrics")
WebClient.builder()
.baseUrl("http://localhost:8080/test")
.filter(metricsWebClientFilterFunction)
.build()
This will expose all the metrics of this WebClient Call in prometheus.
Sample Prometheus Output:
webClientMetrics_seconds_count{clientName="localhost",method="GET",status="200",uri="/test",} 2.0
webClientMetrics_seconds_sum{clientName="localhost",method="GET",status="200",uri="/test",} 2.05474855
webClientMetrics_seconds_max{clientName="localhost",method="GET",status="200",uri="/test",} 1.048698171
To write custom metrics you can implement ExchangeFilterFunction and write your custom implementation for getting the metrics and add it in the WebClient Filter.

Configuring timeout on a per request basis for Spring WebClient?

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()

Resources