Spring 5 webflux inter-dependent webclient calls are not getting timeout - java

I have two interdependent webclient calls to different api's , when the first webclient call response is delayed then readtimeout excpetion is coming . but whenever the first call is success and the second call response is delayed then it is waiting for response indefinitely.
i tried creating seperate instances of webclient for each call . still issue persists.
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10))));
return WebClient.builder().baseUrl(url).clientConnector(new ReactorClientHttpConnector(httpClient))
.exchangeStrategies(ExchangeStrategies.withDefaults())
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
.filter(ExchangeFilterFunctions
.basicAuthentication(", "))
.build();
Two subsequent calls are below where , when the second call is delayed response then the readtimeout exception is not thrown
request = request.flatMap(req ->
tempService.getId(loggedInUser, token)
.map(response -> {
req.setRetrieveClientIdentifier(response.getId());
return seRequest;
}))
.zipWhen(request -> tempService.getIdFor(request.getIdentifier(), accountToken)).map(tuple -> {
tuple.getT1().setID(tuple.getT2().getId());
return tuple.getT1();
});

Related

Spring Boot - Do a new WebClient call with result of another call

I'm trying to call an api with 2 call using webclient.
The first call return a token.
The second call use the token.
public Mono<GetToken> getToken{
return webClient
.get()
.uri(uriBuilder ->
uriBuilder
.path("api/getToken")
.build()
)
.retrieve()
.bodyToMono(Object.class);
}
public Mono<GetToken> getData{
return webClient
.get()
.uri(uriBuilder ->
uriBuilder
.path("api/getData/"+tokenID)
.build()
)
.retrieve()
.bodyToMono(Object2.class);
}
How can I use the data from the first request in the second without using the block() function
Use Mono#flatMap.
Mono<Object2> res = getToken().flatMap(token -> getData(token));

Apply retry to each and every endpoint in the spring webflux application

I have created below webclient and using it inside of my service to make HTTP third party calls.
#Configuration
public class WebclientConfig {
#Bean
public WebClient webClient() {
// custom client connector with connection pool disabled is being used as by default the connection pooling is done and connection reset happens after some idle time.
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.newConnection()))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
and in my service, I am calling the third party service like below.
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class) //code below this, do I have to copy for each webclient request to configure the retry, even if the values are same
.retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.doBeforeRetry((value) -> log.info("Retrying request " + value))
.filter(error -> error instanceof WebClientRequestException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new RuntimeException(retrySignal.failure().getMessage())));
}
My question is as in comment in above code.
I have multiple webclient calls, but I want to configure the retry backoff configuration at a single place. How can I do that? so that my code should look like below
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class)
.somCommonRetryCodeWrappingTheRetryLogic();
}
You can use transform operator for this purpose:
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class)
.transform(originalFlux -> wrapWithRetry(originalFlux));
}
private <T> Flux<T> wrapWithRetry(Flux<T> originalFlux) {
return originalFlux
.retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.doBeforeRetry((value) -> log.info("Retrying request " + value))
.filter(error -> error instanceof WebClientRequestException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new RuntimeException(retrySignal.failure().getMessage())));
}
Only drawback is that if you have Mono use cases as well then you need to implement it twice.
If that is still too much copy-paste, you can consider defining an ExchangeFilterFunction to apply retry for every WebClient call automatically. Something like this:
WebClient.builder()
// ...
.filter((request, next) -> next.exchange(request).retry()) // add full retry config here
.build();

Access response status code for successful requests using Spring WebClient

I need to be able to access the status code of a request. The call can be successful in either of two ways 200 or 201. This is obvious when calling via postman but using the web client, so far I haven't been able to determine which has occurred.
webClient.post()
.uri(url)
.header(HttpHeaders.ACCEPT, MediaType.ALL_VALUE)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header(HttpHeaders.AUTHORIZATION, bearerToken)
.bodyValue(bodyMap)
.retrieve()
.onStatus(
HttpStatus.BAD_REQUEST::equals,
response -> response.bodyToMono(String.class).map(Exception::new))
.bodyToMono(Map.class)
I was thinking maybe I could set an integer variable using within the onStatus() lambda function. Is it even possible to access external variables within a lambda function?
int responseStatus;
// post call
.onStatus(
HttpStatus.CREATED::equals,
response -> ... // do something to set responseStatus
You could use .toEntity(Class<T> bodyClass) method to get entity wrapped in response object ResponseEntity<T>
var response = webClient.post()
.uri(uri)
.retrieve()
.toEntity(Map.class)
.map(res -> {
if (res.getStatusCode().equals(HttpStatus.OK)) {
...
}
return res.getBody();
});

Spring WebClient - how to log sucessfull and failed response?

I'm trying to create a REST call using WebClient
statusWebClient.post()
.uri(url)
.bodyValue(createBody(state, number))
.retrieve()
.bodyToFlux(String.class)
.doOnEach(response -> log.debug("Notification was sent to {}, response {}", url, response.get()))
.doOnError(exception -> log.warn("Failed to send notification to {}, cause {}", url, exception.getMessage()))
.subscribe();
I only want to log the call result. On success - log successful message with response body, on 5XX or timeout or other - log the error message. The log should be created in the background (not by the thread that created the call)
But doOnEach is executed each time, doOnError works fine, but there is also
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError: 500 Internal Server Error from POST
logged in the logfile.
I've I also seen in few tutorials onSuccess method, but in my setup there is such method.
How can I log success and fail messages?
This is what I created. Seems that works fine
statusWebClient.post()
.uri(url)
.bodyValue(createTerminalStatusBody(state, msisdn))
.retrieve()
.bodyToMono(String.class)
.subscribeOn(Schedulers.boundedElastic())
.doOnSuccess(response -> log.debug("Notification was sent to {}, response {}", url, response))
.doOnError(exception -> log.warn("Failed to send notification to {}, cause {}", url, exception.getMessage()))
.onErrorResume(throwable -> Mono.empty())
.subscribe();
You could setup custom filters for the WebClient
WebClient.builder()
.filter(logRequest())
.filter(logResponse());
private ExchangeFilterFunction logRequest() {
return (clientRequest, next) -> {
log.debug("Request: {} {}", clientRequest.method(), clientRequest.url());
return next.exchange(clientRequest);
};
}
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
log.debug("Response Status: {}", clientResponse.statusCode());
return Mono.just(clientResponse);
});
}

How to convert Spring WebClient Response to ResponseEntity?

I'm able to return ResponseEntity using toEntity() method like below:
#GetMapping("/uri")
public Mono<ResponseEntity<Data[]>> methodName() {
return webClient
.get()
.uri("http://localhost:8088/externalService")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Data[].class);
}
But I want to process response headers before returning.
The above code converts WebClient response to ResponseEntity and returns immediately but I want to store it in a ResponseEntity variable, process it, and then return the ResponseEntity back.
I referred this -> Spring WebClient Documentation
WHen I tried to store it in a varibale, I get this error -> "block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3"
You can simply use the Reactor's map operator to modify the headers:
return webClient
.get()
.uri("http://localhost:8088/externalService")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Data[].class)
.map(responseEntity -> responseEntity.getHeaders().add("header", "header-value");
Alternatively, you can use .handle operator in order to provide response processing:
.handle((responseEntity, sink) -> {
if(!isValid(responseEntity)){
sink.error(new InvalidResponseException());
} else if (isOk(responseEntity))
sink.next(responseEntity);
}
else {
//just ignore element
}
})
Spring Starter Web dependency was missing in my pom.xml. Found it and added it back.
Now able to get WebClient response in ResponseEntity format.

Categories

Resources