Spring Flux: Properly catch exceptions on Mono.defer - spring-boot

That's my code:
Flux.fromStream(ids.stream())
.flatMap(id -> Mono.defer(() -> {
String entity = myRepository.findById(id);
return Mono.just(entity);
})
)
.collectList()
I'd like to know how to properly catch each exception could be raised into myRepository.findById.
What about:
Scheduler scheduler = Schedulers.fromExecutor(...);
Flux.fromStream(ids.stream())
.flatMap(id -> Mono.defer(() -> {
String entity = myRepository.findById(id);
return Mono.just(entity);
}).subscribeOn(scheduler),
10
)
.collectList()
.block();

Related

SOAP call by WebClient Content type 'text/xml' not supported

I tried to make a SOAP call from spring reactive webclient.
the ConfirmPayer10, ConfirmPayer10Response and ObjectFactory classes i generated from wsdl file by the maven-jaxb2-plugin plugin.
for the encoder and decoder config, i get the same from here and here
ConfirmPayer10 confirmPayer10 = new ConfirmPayer10();
confirmPayer10.setArg0(confirmPayerRq);
final JAXBElement<ConfirmPayer10> jAXBElement = new ObjectFactory().createConfirmPayer10(confirmPayer10);
webClient.post().uri( url )
.contentType(MediaType.TEXT_XML)
.body( Mono.just(jAXBElement) , new ParameterizedTypeReference<JAXBElement<ConfirmPayer10>>() {})
.retrieve()
.onStatus(
HttpStatus::isError,
clientResponse ->
clientResponse
.bodyToMono(String.class)
.flatMap(
errorResponseBody ->
Mono.error(
new ResponseStatusException(
clientResponse.statusCode(),
errorResponseBody))))
.bodyToMono(new ParameterizedTypeReference<JAXBElement<ConfirmPayer10Response>>() {})
.doOnSuccess( (res) -> {
System.out.println("success");
System.out.println("capital : " + res.getValue().getReturn().getRqUID());
})
.doOnError(ResponseStatusException.class, error -> {
System.out.println( "error : "+ error );
})
.doOnError(Exception.class, ( Exception error ) -> {
System.out.println( "error : "+ error );
error.printStackTrace();
}).subscribe();
the problem is that i have this error
org.springframework.web.reactive.function.UnsupportedMediaTypeException:
Content type 'text/xml' not supported for
bodyType=javax.xml.bind.JAXBElement< ConfirmPayer10 >

Spring WebClient: Retry with WebFlux.fn + reactor-addons

I'm trying to add a conditional Retry for WebClient with Kotlin Coroutines + WebFlux.fn + reactor-addons:
suspend fun ClientResponse.asResponse(): ServerResponse =
status(statusCode())
.headers { headerConsumer -> headerConsumer.addAll(headers().asHttpHeaders()) }
.body(bodyToMono(DataBuffer::class.java), DataBuffer::class.java)
.retryWhen {
Retry.onlyIf { ctx: RetryContext<Throwable> -> (ctx.exception() as? WebClientResponseException)?.statusCode in retryableErrorCodes }
.exponentialBackoff(ofSeconds(1), ofSeconds(5))
.retryMax(3)
.doOnRetry { log.error("Retry for {}", it.exception()) }
)
.awaitSingle()
also adding a condition before the retry
if (statusCode().isError) {
body(
BodyInserters.fromPublisher(
Mono.error(StatusCodeError(status = statusCode())),
StatusCodeException::class.java
)
)
} else {
body(bodyToMono(DataBuffer::class.java), DataBuffer::class.java)
}
Call looks like:
suspend fun request(): ServerResponse =
webClient/*...*/
.awaitExchange()
.asResponse()
This spring webclient: retry with backoff on specific error gave me the hint to answer the question:
.awaitExchange() returns the ClientResponse and not Mono<ClientReponse>
This means my retry was acting on bodyToMono instead of the operation of exchange().
The solution now looks like
suspend fun Mono<ClientResponse>.asResponse(): ServerResponse =
flatMap {
if (it.statusCode().isError) {
Mono.error(StatusCodeException(status = it.statusCode()))
} else {
it.asResponse()
}
}.retryWhen(
Retry.onlyIf { ctx: RetryContext<Throwable> ->
(ctx.exception() as? StatusCodeException)?.shouldRetry() ?: false
}
.exponentialBackoff(ofSeconds(1), ofSeconds(5))
.retryMax(3)
.doOnRetry { log.error { it.exception() } }
).awaitSingle()
private fun ClientResponse.asResponse(): Mono<ServerResponse> =
status(statusCode())
.headers { headerConsumer -> headerConsumer.addAll(headers().asHttpHeaders()) }
.body(bodyToMono(DataBuffer::class.java), DataBuffer::class.java)

Kotlin Spring Reactive Webflux - Handle WebClient Error

I am having troubles trying to handle different errors from calling Spring webflux's web client.
Below is my current code.
return request
.bodyToMono(InputMessage::class.java)
.flatMap { inputMessage ->
client
.get()
.uri { builder ->
builder.path("/message")
.queryParam("message", inputMessage.message)
.build()
}
.retrieve()
.onStatus({t: HttpStatus -> t.is5xxServerError}, {c: ClientResponse -> Mono.error(Throwable("Internal Server Error - try again later"))})
.bodyToMono(ListOfAddresses::class.java)
}
.flatMap { s -> ServerResponse.ok().syncBody(s) }
If it errors, it is still returning the full error message from the client's call.
I tried something else, like this
return request
.bodyToMono(InputMessage::class.java)
.flatMap { inputMessage ->
client
.get()
.uri { builder ->
builder.path("/message")
.queryParam("message", inputMessage.message)
.build()
}
.retrieve()
.onStatus({t: HttpStatus -> t.is5xxServerError}, {c: ClientResponse -> Mono.error(Throwable("Internal Server Error - try again later"))})
.bodyToMono(ListOfAddresses::class.java)
}
.flatMap { s -> ServerResponse.ok().syncBody(s) }
.onErrorResume { e -> Mono.just("Error " + e.message)
.flatMap { s -> ServerResponse.ok().syncBody(s) } }
It actually works but then I want to handle different Http status codes error (different messages for each Http status code).
How can I modify my code so it will return the custom message I build?
As per WebFlux documentation, you can user the exchangeToMono() or awaitExchange { } in order to have an error handling.
Mono<Object> entityMono = client.get()
.uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(Person.class);
}
else if (response.statusCode().is4xxClientError()) {
// Suppress error status code
return response.bodyToMono(ErrorContainer.class);
}
else {
// Turn to error
return response.createException().flatMap(Mono::error);
}
});
Code copied from WebFlux link above.
Take a look at 2.3. Exchange
val entity = client.get()
.uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.awaitExchange {
if (response.statusCode() == HttpStatus.OK) {
return response.awaitBody<Person>()
}
else if (response.statusCode().is4xxClientError) {
return response.awaitBody<ErrorContainer>()
}
else {
throw response.createExceptionAndAwait()
}
}

What's the correct way to get the response body from a WebClient in an error case?

I'm new to WebClient and reactive programming. I want to get the response body from a request. In case of an error the http-code, headers and body must be logged, but the body should still be returned.
After lots of digging and googling I found two solutions. But both look over complicated to me. Is there a simpler solution?
Staying with a Mono I found this solution:
public Mono<String> log(ProtocolLine protocolLine) {
return webClient.post()
.uri("/log")
.body(BodyInserters.fromObject(protocolLine))
.exchange()
.flatMap(clientResponse -> {
Mono<String> stringMono = clientResponse.bodyToMono(String.class);
CompletableFuture<String> stringCompleteFuture = new CompletableFuture<String>();
Mono<String> bodyCompletedMono = Mono.fromFuture(stringCompleteFuture);
if (clientResponse.statusCode().isError()) {
stringMono.subscribe(bodyString -> {
LOGGER.error("HttpStatusCode = {}", clientResponse.statusCode());
LOGGER.error("HttpHeaders = {}", clientResponse.headers().asHttpHeaders());
LOGGER.error("ResponseBody = {}", bodyString);
stringCompleteFuture.complete(bodyString);
});
}
return bodyCompletedMono;
});
}
Based on Flux it takes less code. But I think I should not use Flux if I know that there will be only one result.
public Flux<String> log(ProtocolLine protocolLine) {
return webClient.post()
.uri("/log")
.body(BodyInserters.fromObject(protocolLine))
.exchange()
.flux()
.flatMap(clientResponse -> {
Flux<String> stringFlux = clientResponse.bodyToFlux(String.class).share();
if (clientResponse.statusCode().isError()) {
stringFlux.subscribe(bodyString -> {
LOGGER.error("HttpStatusCode = {}", clientResponse.statusCode());
LOGGER.error("HttpHeaders = {}", clientResponse.headers().asHttpHeaders());
LOGGER.error("ResponseBody = {}", bodyString);
});
}
return stringFlux;
});
}
both solutions are ugly and wrong. You should almost never subscribe in the middle of a reactive pipeline. The subscriber is usually the calling client, not your own application.
public Mono<String> log(ProtocolLine protocolLine) {
return webClient.post()
.uri("/log")
.body(BodyInserters.fromObject(protocolLine))
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(String.class)
.doOnSuccess(body -> {
if (clientResponse.statusCode().isError()) {
log.error("HttpStatusCode = {}", clientResponse.statusCode());
log.error("HttpHeaders = {}", clientResponse.headers().asHttpHeaders());
log.error("ResponseBody = {}", body);
}
}));
}
Here you can see the way of thinking. We always take our clientResponse and map its body to a string. We then doOnSuccess when this Mono is consumed by the subscriber (our calling client) and check the status code if there is an error and if that is the case we log.
The doOnSuccess method returns void so it doesn't "consume" the mono or anything, it just triggers something when this Mono says it "has something in itself", when it's "done" so to speek.
This can be used with Flux the same way.

Java 8 Completable Futures allOf different data types

I have 3 CompletableFutures all 3 returning different data types.
I am looking to create a result object that is a composition of the result returned by all the 3 futures.
So my current working code looks like this:
public ClassD getResultClassD() {
ClassD resultClass = new ClassD();
CompletableFuture<ClassA> classAFuture = CompletableFuture.supplyAsync(() -> service.getClassA() );
CompletableFuture<ClassB> classBFuture = CompletableFuture.supplyAsync(() -> service.getClassB() );
CompletableFuture<ClassC> classCFuture = CompletableFuture.supplyAsync(() -> service.getClassC() );
CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
.thenAcceptAsync(it -> {
ClassA classA = classAFuture.join();
if (classA != null) {
resultClass.setClassA(classA);
}
ClassB classB = classBFuture.join();
if (classB != null) {
resultClass.setClassB(classB);
}
ClassC classC = classCFuture.join();
if (classC != null) {
resultClass.setClassC(classC);
}
});
return resultClass;
}
My questions are:
My assumption here is that since I am using allOf and thenAcceptAsync this call will be non blocking. Is my understanding right ?
Is this the right way to deal with multiple futures returning different result types ?
Is it right to construct ClassD object within thenAcceptAsync ?
Is it appropriate to use the join or getNow method in the thenAcceptAsync lambda ?
Your attempt is going into the right direction, but not correct. Your method getResultClassD() returns an already instantiated object of type ClassD on which an arbitrary thread will call modifying methods, without the caller of getResultClassD() noticing. This can cause race conditions, if the modifying methods are not thread safe on their own, further, the caller will never know, when the ClassD instance is actually ready for use.
A correct solution would be:
public CompletableFuture<ClassD> getResultClassD() {
CompletableFuture<ClassA> classAFuture
= CompletableFuture.supplyAsync(() -> service.getClassA() );
CompletableFuture<ClassB> classBFuture
= CompletableFuture.supplyAsync(() -> service.getClassB() );
CompletableFuture<ClassC> classCFuture
= CompletableFuture.supplyAsync(() -> service.getClassC() );
return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
.thenApplyAsync(dummy -> {
ClassD resultClass = new ClassD();
ClassA classA = classAFuture.join();
if (classA != null) {
resultClass.setClassA(classA);
}
ClassB classB = classBFuture.join();
if (classB != null) {
resultClass.setClassB(classB);
}
ClassC classC = classCFuture.join();
if (classC != null) {
resultClass.setClassC(classC);
}
return resultClass;
});
}
Now, the caller of getResultClassD() can use the returned CompletableFuture to query the progress state or chain dependent actions or use join() to retrieve the result, once the operation is completed.
To address the other questions, yes, this operation is asynchronous and the use of join() within the lambda expressions is appropriate. join was exactly created because Future.get(), which is declared to throw checked exceptions, makes the use within these lambda expressions unnecessarily hard.
Note that the null tests are only useful, if these service.getClassX() can actually return null. If one of the service calls fails with an exception, the entire operation (represented by CompletableFuture<ClassD>) will complete exceptionally.
I was going down a similar route to what #Holger was doing in his answer, but wrapping the Service Calls in an Optional, which leads to cleaner code in the thenApplyAsync stage
CompletableFuture<Optional<ClassA>> classAFuture
= CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassA())));
CompletableFuture<Optional<ClassB>> classBFuture
= CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassB()));
CompletableFuture<Optional<ClassC>> classCFuture
= CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassC()));
return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
.thenApplyAsync(dummy -> {
ClassD resultClass = new ClassD();
classAFuture.join().ifPresent(resultClass::setClassA)
classBFuture.join().ifPresent(resultClass::setClassB)
classCFuture.join().ifPresent(resultClass::setClassC)
return resultClass;
});
I ran into something similar before and created a short demo to show how I solved this issue.
Similar concept to #Holger except I used a function to combine each individual future.
https://github.com/te21wals/CompletableFuturesDemo
Essentially:
public class CombindFunctionImpl implement CombindFunction {
public ABCData combind (ClassA a, ClassB b, ClassC c) {
return new ABCData(a, b, c);
}
}
...
public class FutureProvider {
public CompletableFuture<ClassA> retrieveClassA() {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ClassA();
});
}
public CompletableFuture<ClassB> retrieveClassB() {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ClassB();
});
}
public CompletableFuture<ClassC> retrieveClassC() {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ClassC();
});
}
}
......
public static void main (String[] args){
CompletableFuture<ClassA> classAfuture = futureProvider.retrieveClassA();
CompletableFuture<ClassB> classBfuture = futureProvider.retrieveClassB();
CompletableFuture<ClassC> classCfuture = futureProvider.retrieveClassC();
System.out.println("starting completable futures ...");
long startTime = System.nanoTime();
ABCData ABCData = CompletableFuture.allOf(classAfuture, classBfuture, classCfuture)
.thenApplyAsync(ignored ->
combineFunction.combind(
classAfuture.join(),
classBfuture.join(),
classCfuture.join())
).join();
long endTime = System.nanoTime();
long duration = (endTime - startTime);
System.out.println("completable futures are complete...");
System.out.println("duration:\t" + Duration.ofNanos(duration).toString());
System.out.println("result:\t" + ABCData);
}
Another way to handle this if you don't want to declare as many variables is to use thenCombine or thenCombineAsync to chain your futures together.
public CompletableFuture<ClassD> getResultClassD()
{
return CompletableFuture.supplyAsync(ClassD::new)
.thenCombine(CompletableFuture.supplyAsync(service::getClassA), (d, a) -> {
d.setClassA(a);
return d;
})
.thenCombine(CompletableFuture.supplyAsync(service::getClassB), (d, b) -> {
d.setClassB(b);
return d;
})
.thenCombine(CompletableFuture.supplyAsync(service::getClassC), (d, c) -> {
d.setClassC(c);
return d;
});
}
The getters will still be fired off asynchronously and the results executed in order. It's basically another syntax option to get the same result.

Resources