Restrict Spring WebClient call at application level - spring

I am using Spring WebFlux and WebClient for my web application.
My application potentially can call a 'N' number of other micro services which is also hosted by us.
Now the problem is that i want to restrict my WebClient to invoke a limited number of simultaneous calls to the existing micro services.
Also, I don't want to do it at individual call level, but at application level.
I have already gone through "How to limit the number of active Spring WebClient calls?" and "How to limit the request/second with WebClient?", to no avail.

You can create a WebClient instance like this:
ConnectionProvider fixedPool = ConnectionProvider.fixed("fixedPool", maxConnections, acquireTimeout);
HttpClient httpClient = HttpClient.create(fixedPool);
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();

Since reactor-netty 0.9.5, the method described by Brian Clozel is now deprecated, use instead:
ConnectionProvider fixedPool = ConnectionProvider.builder("fixedPool")
.maxConnections(200)
.pendingAcquireTimeout(Duration.ofMinutes(3))
.build();
HttpClient httpClient = HttpClient.create(fixedPool);
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
max connectiond and pending acquire timeout are random values, change them according to your needs.

Related

Exception Handling When Using Spring OAuth2 Client Enabled WebClient

I have an OAuth2 enabled WebClient configured using Spring's very convenient spring-boot-starter-oauth2-client library. It works perfectly in terms of getting and managing tokens when I make requests to my OAuth2 secured resource server API
I'd like to add global exception handling to the WebClient to handle exceptions arising from requests to my resource server. An ExchangeFilterFunction following something like the approach outlined in this article looks good.
I'm just wondering, as the WebClient already has a ServletOAuth2AuthorizedClientExchangeFilterFunction applied, would adding additional filters have any impact on the interactions with the OAuth2 authorization server?
Here is my current WebClient config:
#Bean
public WebClient edmsDataIntegrationServiceWebClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("my-auth-server");
return WebClient.builder()
.baseUrl(baeUrl)
.apply(oauth2Client.oauth2Configuration())
.build();
}
So the additional filter would be something like the approach outlined here, where handleResourceServerError() would return an ExchangeFilterFunction that handles exceptions caused by my resource server response:
return WebClient.builder()
.baseUrl(baeUrl)
.apply(oauth2Client.oauth2Configuration())
.filter(WebClientFilter.handleResourceServerError()) //handles exceptions caused by resource server response
.build();
Is the above approach ok without breaking any of the ServletOAuth2AuthorizedClientExchangeFilterFunction functionality?

Calling a non-reactive legacy service from reactive spring boot app?

I am working heavily with a webflux based spring boot application.
the problem I am facing, is that there is one service I have to call to, which is a traditional spring boot app, and is not reactive!
Here is an example endpoint which is close to the idea of said legacy system :
#RequestMapping(value = "/people/**", method = RequestMethod.GET)
public ResponseEntity<InputStreamResource> getPerson(HttpServletRequest request) {
String pattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
String key = new AntPathMatcher().extractPathWithinPattern(pattern, request.getRequestURI());
return personService.getPersonByKey(key);
}
I KNOW I can't achieve true reactive goodness with this, is there a happy medium of non blocking and blocking I can achieve here?
Thanks
When you use WebClient to call the service from your Spring WebFlux application, then it will work in Reactive non blocking way. Meaning you can achieve true reactive goodness on your application. The thread will not be blocked until the upstream service returns the response.
Below is an example code for calling a service using WebClient:
WebClient webClient = WebClient.create("http://localhost:8080");
Mono<Person> result = webClient.get()
.uri("/people/{id}")
.retrieve()
.bodyToMono(Person.class);

How should I use the Spring WebClient to non-interactively access an OAuth protected resource on behalf of another user?

I have a Spring (not Boot) application which has to access non-interactively (in a scheduled task) some 3rd-party resources on behalf of our users. These resources use OAuth 2.0 for authorization. We already have a workflow that gets us the required tokens and are accessing the resources using either Spring Social or our own implementation neither of which is optimal (Spring Social seems to be not maintained, we'd rather use a library than maintain our OAuth "framework").
I'm trying to use the WebClient from Spring Security 5.1, but I'm not sure I'm using it correctly.
The WebClient is created this way:
final ClientRegistration 3rdParty = 3rdParty();
final ReactiveClientRegistrationRepository clientRegistrationRepository =
new InMemoryReactiveClientRegistrationRepository(3rdParty);
final ReactiveOAuth2AuthorizedClientService authorizedClientService =
new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
final ServerOAuth2AuthorizedClientRepository authorizedClientRepository =
new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
final ServerOAuth2AuthorizedClientExchangeFilterFunction autorizedClientExchangeFilterFunction =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);
return WebClient.builder()
.filter(autorizedClientExchangeFilterFunction)
.build();
and accessing the resource this way works:
final OAuth2AuthorizedClient oAuth2AuthorizedClient = ... // (OAuth2AuthorizedClient with OAuth2AccessToken)
final Mono<SomeResource> someResourceMono = webClient().get()
.uri(3rdpartyUrl)
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient(oAuth2AuthorizedClient))
.retrieve()
.bodyToMono(SomeResource.class);
The problem is I don't see how the ReactiveClientRegistrationRepository and ServerOAuth2AuthorizedClientRepository are used in this approach. If I have to create a fully populated OAuth2AuthorizedClient to access the resource, why are these repositories needed?
I expected, that I have to pass the clientRegistrationId, some "principalName", implement our ReactiveOAuth2AuthorizedClientService loading OAuth2AuthorizedClient's by "principalName" and let the ServerOAuth2AuthorizedClientRepository do its work, but the only way I see to pass a principal to the WebClient is by using ServerOAuth2AuthorizedClientExchangeFilterFunction#oauth2AuthorizedClient which requires a complete OAuth2AuthorizedClient. Which is the part I'm doing it wrong?
Instead of supplying the OAuth2AuthorizedClient via oauth2AuthorizedClient(), you can also provide the clientRegistrationId via clientRegistrationId() and ServerWebExchange via serverWebExchange(). The combination of the latter 2 options will resolve the Principal from the ServerWebExchange and use both ReactiveClientRegistrationRepository and ServerOAuth2AuthorizedClientRepository to resolve the OAuth2AuthorizedClient. I understand your use-case is a bit different given you are running outside of a request context - this is just a FYI.
...The problem is I don't see how the
ReactiveClientRegistrationRepository and
ServerOAuth2AuthorizedClientRepository are used in this approach
You still need to provide ReactiveClientRegistrationRepository and ServerOAuth2AuthorizedClientRepository as the ServerOAuth2AuthorizedClientExchangeFilterFunction supports the refreshing (authorization_code client) and renewing (client_credentials client) of an expired access token.
For your specific use case, take a look at UnAuthenticatedServerOAuth2AuthorizedClientRepository as this implementation supports WebClient running outside of a request context, e.g. background thread. Here is a sample for your reference.

Right way to use Spring WebClient in multi-thread environment

I have one question regarding Spring WebClient
In my application I need to do many similar API calls, sometimes I need change headers in the calls (Authentication token). So the question arises, what would be better of the two options:
To create one WebClient for all incoming requests to MyService.class, by making it private final field, like code below:
private final WebClient webClient = WebClient.builder()
.baseUrl("https://another_host.com/api/get_inf")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
Here arises another question: is WebClient thread-safe? (because service is used by many threads)
To create new WebClient for each new request incoming to service class.
I want to provide maximum performance, and to use it in right way, but I don't know how WebClient works inside it, and how it expects to be used.
Thank you.
Two key things here about WebClient:
Its HTTP resources (connections, caches, etc) are managed by the underlying library, referenced by the ClientHttpConnector that you can configure on the WebClient
WebClient is immutable
With that in mind, you should try to reuse the same ClientHttpConnector across your application, because this will share the connection pool - this is arguably the most important thing for performance. This means you should try to derive all WebClient instances from the same WebClient.create() call. Spring Boot helps you with that by creating and configuring for you a WebClient.Builder bean that you can inject anywhere in your app.
Because WebClient is immutable it is thread-safe. WebClient is meant to be used in a reactive environment, where nothing is tied to a particular thread (this doesn't mean you cannot use in a traditional Servlet application).
If you'd like to change the way requests are made, there are several ways to achieve that:
configure things in the builder phase
WebClient baseClient = WebClient.create().baseUrl("https://example.org");
configure things on a per-request basis
Mono<ClientResponse> response = baseClient.get().uri("/resource")
.header("token", "secret").exchange();
create a new client instance out of an existing one
// mutate() will *copy* the builder state and create a new one out of it
WebClient authClient = baseClient.mutate()
.defaultHeaders(headers -> {headers.add("token", "secret");})
.build();
From my experience, if you are calling an external API on a server you have no control over, don't use WebClient at all, or use it with the pooling mechanism turned off. Any performance gains from connection pooling are greatly overweighed by the assumptions built into the (default reactor-netty) library that will cause random errors on one API call when another was abruptly terminated by the remote host, etc. In some cases, you don't even know where the error occurred because the calls are all made from a shared worker thread.
I made the mistake of using WebClient because the doc for RestTemplate said it would be deprecated in the future. In hindsight, I would go with regular HttpClient or Apache Commons HttpClient, but if you are like me and already implemented with WebClient, you can turn off the pooling by creating your WebClient as follows:
private WebClient createWebClient(int timeout) {
TcpClient tcpClient = TcpClient.newConnection();
HttpClient httpClient = HttpClient.from(tcpClient)
.tcpConfiguration(client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout * 1000)
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(timeout))));
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
*** Creating a separate WebClient does not mean that WebClient will have a separate connection pool. Just look at the code for HttpClient.create - it calls HttpResources.get() to get the global resources. You could provide the pool settings manually but considering the errors that occur even with the default setup, I don't consider it worth the risk.

Springs RestTemplate default connection pool

Just wondering if RestTemplate out of the box uses connection pooling or does it simply establish a new connection each time ?
Yes, Spring RestTemplateBuilder uses Apache HttpClient for pooling (usage).
RestTemplateBuilder creates HttpComponentsClientHttpRequestFactory and uses HttpClientBuilder.
HttpClientBuilder, by default, sets pool size per route (host) to 5 and total pool size to 10 (source):
s = System.getProperty("http.maxConnections", "5");
int max = Integer.parseInt(s);
poolingmgr.setDefaultMaxPerRoute(max);
poolingmgr.setMaxTotal(2 * max);
To check connection pool logging set logging level as follows:
org.apache.http.impl.conn.PoolingHttpClientConnectionManager=TRACE
I believe RestTemplate doesn’t use a connection pool to send requests, it uses a SimpleClientHttpRequestFactory that wraps a standard JDK’s HttpURLConnection opening and closing the connection.
Indeed you can configure RestTemplate to use a pooled implementation such as HttpComponentsClientHttpRequestFactory but most-likely you might also need to configure some settings to prevent requests from timing out.
I have blogged about this issue at Troubleshooting Spring's RestTemplate Requests Timeout
By default RestTemplate creates new Httpconnection every time and closes the connection once done.
If you need to have a connection pooling under rest template then you may use different implementation of the ClientHttpRequestFactory that pools the connections.
new RestTemplate(new HttpComponentsClientHttpRequestFactory())
You can create a Bean for RestTemplate and config there :
#Bean
public RestTemplate restTemplate() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(20);
RequestConfig requestConfig = RequestConfig
.custom()
.setConnectionRequestTimeout(5000) // timeout to get connection from pool
.setSocketTimeout(5000) // standard connection timeout
.setConnectTimeout(5000) // standard connection timeout
.build();
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(requestFactory);
}
And there are a lot config you can do. Refer to https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/HttpClientBuilder.html
EDIT
If you want to use micrometer metrics you should also use a RestTemplateBuilder for constructing the RestTemplate.
In case of using Spring Boot configured with Apache HttpClient (having org.apache.httpcomponents:httpclient library in dependencies)
There is a default connection pool configured by PoolingHttpClientConnectionManager
Default concurrent settings for connections (you can find more about default settings here https://hc.apache.org/httpcomponents-client-4.5.x/current/tutorial/html/connmgmt.html):
PoolingHttpClientConnectionManager maintains a maximum limit of connections on a per route basis and in total. Per default this implementation will create no more than 2 concurrent connections per given route and no more 20 connections in total. For many real-world applications these limits may prove too constraining, especially if they use HTTP as a transport protocol for their services.
We can use okhttpclient underneath spring's rest template to use connection pooling. A detailed blog on this below
https://www.bytesville.com/changing-httpclient-in-spring-resttemplate/

Resources