Nested Webclient calls is giving an error - spring-webclient

Scenario: need to get an access token from a service and pass it to a webclient call as below.
return someservice
.getToken() //returns token as Mono<String>,this itself is another webclient call
.flatMap(token -> {
return customWebclient.delete() //observe the delete method here
.uri(uri -> uri.path(/users)
.queryParam("id", id)
.build())
.headers(headers -> headers.setBearerAuth(token))
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.header("Accept", MediaType.APPLICATION_JSON_VALUE)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
}).log();
}); // this return a Mono<Map<String, Object>>
I then need to block this final result and so I am using .toFuture().get() to get Map<String, Object>.
Now the issue is .get() call here is waiting indefinitely and the call to customWebClient call is never happening and if I get use .get(3000, TimeUnit.SECONDS), get() is throwing a TimedOutException and then calling the customWebClient call.
From what I understand, get() method should wait for Mono<Map<String, Object>> to resolve i.e, customWebclient call to happen and then return the result.
Using spring-boot-starter-webflux
Please help me with a solution.
I have also tried not nesting these calls and used toFuture().get() for both token and the Map, get() for token is waiting forever.
Other important point is that the same customWebclient call for get() method in same way as example is working fine.

Try with adding ".subscribeOn(Schedulers.boundedElastic())" before .toFuture().get(30L, TimeUnit.SECONDS).

Related

Java Spring WebFlux WebClient pass parameters to response

I want to use webflux to return a single result async. The response doesn't have an id of the object. So when I get the response async back from the remote reply, then I don't have a way to fetch that object from the database to get further information. So is there a way to pass my object id to the async response handler? I couldn't find any way. Here is my sample code
var monoReply = webClient.post().uri(url)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(myRequestObject), MyRequest.class)
.retrieve()
.bodyToMono(MyResponse.class);
monoReply.subscribe(BanlawApiServiceImpl::handleLoginResponse);
private static String handleLoginResponse(MyResponse myResponse) {
String token = myResponse.getToken();
//now I want to know the id of the database object I am dealing with. Response doesn't
have that id
}
You need to continue async flow using flatMap and fetch object from the database. As result handleLoginResponse should return Mono<T>
webClient.post().uri(url)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(myRequestObject), MyRequest.class)
.retrieve()
.bodyToMono(MyResponse.class)
.flatMap(response -> handleLoginResponse(response))
private static Mono<String> handleLoginResponse(MyResponse myResponse) {
...
}
Not sure why you are subscribing to the flow explicitly that usually is anti-pattern and should be avoided. In WebFlux subscription happens behind the scene.

Spring WebFlux - How to print response as String instead of object using WebClient

I have a Mono like below:
private void getNodeDetail() {
Mono<String> mono = webClient.get()
.uri("/alfresco/api/-default-/public/alfresco/versions/1/nodes/f37b52a8-de40-414b-b64d-a958137e89e2")
.retrieve().bodyToMono(String.class);
System.out.println(mono.subscribe());
System.out.println(mono.block());
}
Questions: The first sysout shows me reactor.core.publisher.LambdaSubscriber#77114efe while using block() it shows me what I need (json String). But I want to use Aysnc approach. So, given above, does it mean my target system (Alfresco in this case) DO NOT support async calls? If that is not the case, how can I print the response on the console in String format using subscribe(), just like block() ?
The subscribe() method returns a Disposable object:
public final Disposable subscribe()
The expected way to print the response on the console is to actually use the doOnNext operator, like this:
private void getNodeDetail() {
webClient.get()
.uri("/alfresco/api/-default-/public/alfresco/versions/1/nodes/f37b52a8-de40-414b-b64d-a958137e89e2")
.retrieve()
.bodyToMono(String.class)
.doOnNext(response -> System.out.println(response))
.subscribe();
}

Spring Reactive API response blank body while transforming Mono<Void> to Mono<Response>

I have a service call that returns Mono. Now while giving API response to user I want to send some response. I tried with flatMap as well as a map but it won't work. It gives me an empty body in response.
Here the code sample
//Service Call
public Mono<Void> updateUser(....) {
.......... return Mono<Void>...
}
//Rest Controller
#PostMapping(value = "/user/{id})
public Mono<Response> update(......) {
return service.updateUser(....)
.map(r -> new Response(200, "User updated successfully"));
}
When I hit the above API it gives me an empty body in response. Can anyone please help me to get body in API response?
Thank you
if you wish to ignore the return value from a Mono or Flux and just trigger something next in the chain you can use either the then operator, or the thenReturn operator.
The difference is that then takes a Mono while thenReturn takes a concrete value (witch will get wrapped in a Mono.
#PostMapping(value = "/user/{id})
public Mono<Response> update(......) {
return service.updateUser(....)
.thenReturn(new Response(200, "User updated successfully"));
}

concurrent calls using spring webflux and hystrix

hoping someone can steer me in the right direction in turning my code into a more reactive service call. for background I have a preceding function that will generate a list of users, will then use that list to call this getUserDetails function for each user in the list, and return a map or list of user + details.
#HystrixCommand(commandKey = "getUserDetails")
public getUserResponse getUserDetails(userRequest request) {
getUserResponse response = webClient.post()
.uri(uri)
.body(BodyInserters.fromObject(request))
.retrieve()
.onStatus(HttpStatus::isError, resp -> resp.bodyToMono(getUserError.class).map(errorHandler::mapRequestErrors))
.bodyToMono(getUserResponse.class).block();
return response;
}
Ideally I would also replace/remove the error mapping as only concerned with logging the returned error response and continuing.
so far I have thought something along the lines of this but I'm not sure the webflux/hystrix will play nice?
#HystrixCommand(commandKey = "getUserDetails", fallbackMethod = "getUserFallback")
public Mono<getUserResponse> getUserDetails(userRequest request) {
return = webClient.post()
.uri(uri)
.body(BodyInserters.fromObject(request))
.retrieve()
.bodyToMono(getUserResponse.class);
}
#HystrixCommand
public Mono<getUserResponse> getUserFallback(userRequest request, Throwable throwable) {
log.error(//contents of error message returned)
return mono.empty();
}
public Flux<UserMap> fetchUserDetails(List<Integer> userIds) {
return Flux.fromIterable(userIds)
.parallel()
.runOn(Schedulers.elastic())
.flatMap(userDetailsRepository::getUserDetails);
}
Hystrix is deprecated. If you have a chance, move to resilience4j which has support for Webflux/Reactor.
Spring also has dedicated support for resilience4j.
Regarding error handling you can leverage the rich set of operators from the Mono/Flux API like onErrorReturn or onErrorResume.

Spring WebFlux Post Issue

I am using WebClient to do an API post but it is not returning anything. I'm assuming that the thread is staying open and not completing since I can use a block to get what I want but I'm still pretty new to WebClient and asynchronous stuff so I'm not 100% sure.
Specifically I have this method:
public Mono<AppDto> applicationPost(AppDto dto){
return webClient.post()
.uri("/api/doStuff")
.contentType(MediaType.APPLICATION_JSON)
.body(MonoConverter.appDtoToMono(dto), String.class)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
.map(MonoConverter::mapValueToAppDto);
}
Where MonoConverter does some conversion for mapping values so this should be irrelevant. The above returns a 202 Accepted but it does not return a value or hit my mapValueToAppDto method. The below however, does work:
public Mono<AppDto> applicationPost(AppDto dto){
Map map = webClient.post()
.uri("/api/doStuff")
.contentType(MediaType.APPLICATION_JSON)
.body(MonoConverter.appDtoToMono(dto), String.class)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
.block();
return Mono.just(MonoConverter.mapValueToAppDto(map));
}
I'm assuming that this works since it uses block but then a get method I have that is in a similar fashion works:
public Mono<AppDto> applicationGetOne(String appId){
return webClient.get()
.uri("/api/getOne/{0}",appId)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
.map(MonoConverter::mapValueToAppDto);
}
I would prefer to use the first snippet since it does not use block and it's simpler and in the same format as my other methods.
Does anyone have any idea why the first one isn't working or know how I could get it to work?
I found the reason why I was having this issue. It actual had to do with my controller
(D'oh!). For the post method, I have validation that binds errors so I was just returning a ResponseEntity without giving it a type. So I added a typing to the ResponseEntity and that fixed the issue.
e.g.:
#PostMapping(value="/post")
public ResponseEntity addThing(#Validated #RequestBody AppDto dto, BindingResult result){
...
}
And what fixed it:
#PostMapping(value="/post")
public ResponseEntity<Mono<?>> addThing(#Validated #RequestBody AppDto dto, BindingResult result){
...
}
I'm assuming that since at first the typing wasn't specified it wasn't using the thread the mono response was on and so I was never getting a response but by declaring the type, Spring knows to use the Mono thus allowing it to complete.

Resources