Why is the log statement in Spring WebClient subscribe() block not invoked? - spring

I am playing around with the spring webclient to send emails via an api. The mails get send, however I am trying to do this without blocking and I want to log the successful response (or error). As I understood it so far, I could do this in the subscribe block. However, I do not manage to get the logging (or anything else) within the subscribe block to work.
MailClient.kt
fun sendMail(mail: Mail): Mono<String> {
return getWebClient()
.post()
.uri("uri")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(..)
.retrieve()
.bodyToMono(String::class.java)
//.timeout(Duration.ofSeconds(10))
}
Service function which uses MailClient:
logger.info("Sending mail due to failure with id = ${failure.id}, error: $errorMessage")
mailClient.sendMail(
Mail(..)
)
.subscribe(
{ response -> logger.info("success: $response") },
{ error -> logger.error("error: $error") })

Related

Communication between Spring boot micro services fail When trying to call from .map

I want to write a Rest API using reactive and achieve the below result. I've 2 microservices for this.
Service 1: Receives a post Object and retrieves some data from its database. The process something then communicate to the second Micro Service. Then return the Service 2 response as a Mono to the client.
Service 2: Receives the Data passed from Service 1 and save it to database and returns the Object with the generated primary key.
Here is my code look like.
#PostMapping("/submitData")
public Mono<Object> submitScore(#RequestBody SomeObject someObject){
//Some Operations
return contestGiftInfoRepository.findAllDataById(id)
.filter(c -> {
if(condition) {
return true;
} else {
return false;
}
}).reduce((item1,item2) -> {
if(condition) {
return item1;
}else {
return item2;
}
})
.defaultIfEmpty(emptyObj)
.map(resultObj->{
//DoSome OPeration
WebClient client = WebClient.create("my service Url 2");
Mono<Obj> response = client.post().uri("/ExecutionPath")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(resultObj), ResultObj.class)
.retrieve()
.bodyToMono(ResponseObj.class);
return response;
});
}
Now When I call the web service 1 everything works fine. When I call the second web service individually that works as well. However, when I call the second web service from the first service it returns a result reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError: 500 Internal Server Error from POST http://localhost:8085/path Caused by: org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError: 500 Internal Server Error from POST http://localhost:8085/path at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:218) ~[spring-webflux-5.3.16.jar:5.3.16] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s):
I don't understand the actual issue from the error message. What I'm missing here?

How to use Spring WebClient to make multiple calls simultaneously and get response separately?

I have to execute multiple API calls simultaneously which are independent of each other:
Mono<Response1> response1= this.webClient
.post()
.uri(requestURI1)
.body(Flux.just(request.getRequestBody()), ParameterizedTypeReference<T>)
.exchangeToMono(response -> {
return response.statusCode().equals(HttpStatus.OK)
? response.bodyToMono(ParameterizedTypeReference<T>)
: response.createException().flatMap(Mono::error);
});
Mono<Response2> response2= this.webClient
.post()
.uri(requestURI2)
.body(Flux.just(request.getRequestBody()), ParameterizedTypeReference<T>)
.exchangeToMono(response -> {
return response.statusCode().equals(HttpStatus.OK)
? response.bodyToMono(ParameterizedTypeReference<T>)
: response.createException().flatMap(Mono::error);
});
Mono<Response3> response3= this.webClient
.post()
.uri(requestURI3)
.body(Flux.just(request.getRequestBody()), ParameterizedTypeReference<T>)
.exchangeToMono(response -> {
return response.statusCode().equals(HttpStatus.OK)
? response.bodyToMono(ParameterizedTypeReference<T>)
: response.createException().flatMap(Mono::error);
});
How can I get the response of above api calls in separate Objects and at the same time, they should be executed in parallel? Also, after executing these above calls and putting the data in separate objects, say, Response1, Response2, Response3, I want to execute another API call which consumes these responses Response1, Response2, Response3.
I have tried to use Flux.merge but this will merge the responses in single object which is not correct. Also read about Mono.zip, but these all are used to combine the responses which I don't want.
EDIT:
Mono.zip works perfectly.
I have a follow up question which I have mentioned in comments but posting it here also.
So I have implemented like this:
Mono.zip(rs1, rs2, rs3).flatMap(tuples -> {
//do something with responses
return Mono.just(transformedData)
}).flatMap(transformedData -> {
//here another webclient.post call which consumes transformedData and return actualData in form of Mono<actualData>
Mono<actualData> data= callAPI;
return data;
});
Now this response is propagated to rest layer in form of Mono<actualData> and I am getting this in response: {
"scanAvailable": true
}
To combine publishers in parallel you can use Mono.zip that will return TupleX<...> and will be resolved when all publishers are resolved.
Mono<Tuple3<Response1, Response2, Response3>> res = Mono.zip(response1, response2, response3)
.map(tuples -> {
//do something with responses
return transformedData;
})
.flatMap(transformedData -> {
//here another webclient.post call which consumes transformedData and return actualData in form of Mono<actualData>
return callAPI(transformedData);
});
Depending on the error handling logic you could consider zipDelayError
As I mentioned in comment one of the key things with reactive API is to differentiate sync and async operations. In case of sync transformation, chain it with .map and use .flatMap or similar operator in case you want to chain another async method.

How can i handle connection and reconnect if connection got closed?

I need this client stay connected for long, How can i make sure about connection? because the issue was in connection, so i am updating my question. what should i do if server close connection? or if client close connection? how can i handle it and reconnect client to the server?
public void consumeServerSentEvent() {
WebClient client = WebClient.create("http://localhost:8080/sse-server");
ParameterizedTypeReference<ServerSentEvent<String>> type
= new ParameterizedTypeReference<ServerSentEvent<String>>() {};
Flux<ServerSentEvent<String>> eventStream = client.get()
.uri("/stream-sse")
.retrieve()
.bodyToFlux(type);
eventStream.subscribe(
content -> logger.info("Time: {} - event: name[{}], id [{}], content[{}] ",
LocalTime.now(), content.event(), content.id(), content.data()),
error -> logger.error("Error receiving SSE: {}", error),
() -> logger.info("Completed!!!"));
}
According to documentation retrieve() returns Mono of ClientResponse, but for your case you need to consume Flux of the body.
Try some thing like this:
Flux<ServerSentEvent<String>> eventStream = client.get()
.uri("/stream-sse")
.retrieve()
.flatMapMany(response -> response.bodyToFlux(type));

Spring WebClient async callback not called when http server response 404

A problem I encountered is as titled, and the code is below:
Mono<Account> accountMono = client.get()
.uri("accounturl")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(Account.class);
} else {
return Mono.empty();
}
});
accountMono.subscribe(result -> callback(result));
```
Server response 404. I try to create an empty Account, but the callback() is not called. It looks like the empty Mono is not emitted.
Server response 404, I try to create a empty Account
You're not creating an empty Account. You're returning an empty Mono, i.e. a Mono that will never emit anything.
If you want to return a Mono which emits an empty Account, then you need
return Mono.just(new Account());

Observer.error is not sending the error

I am trying to generate an intermediate service between a request service and the angular component that subscribes to it. To achieve that, I generate an observable from httpClient request that connects with the intermediate service, and sends the HTTP response.
When the response arrives to the intermediate service, I generate another observable that connects with the client component.
The problem starts here. As I get a 500 status response from request, I get that response in the intermediate service, but when I try to send to the client component with a subscribe.error(err) it starts a loop that never ends, getting the following error:
You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
I am using the same observer.error(err) in the request service and in the intermediate service, and it works correctly only in the request.
Request:
Observable.create((observer: Observer<any>) =>
this.httpClient
.request(httpRequest)
.subscribe(
...
(res: HttpErrorResponse) => {
observer.error(res);
observer.complete();
},
() => {
observer.complete();
},
}
Intermediate Service:
Observable.create((observer: Observer<any>) =>
this.requestService.request(requestServiceOptions).subscribe(
...
(err: HttpErrorResponse) => {
if (err.status === 401) {
this.callbackWhenRequestFails();
} else {
observer.error(err);
observer.complete();
}
}
It has been solutionated.
Unfortunately, when catching the error I was using return instead of throw, then that's why the console was showing the stream error!

Resources