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.
Related
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).
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"));
}
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.
I can't find how to pass a Map of query parameters to uriBuilder even with a method found on Google which explains that we need to pass a LinkedMultiValueMap.
This is my method :
public <T> Mono<T> get(String uri, LinkedMultiValueMap params) {
return this.webClient
.get()
.uri(builder -> builder
.path(uri)
.queryParam(params)
.build())
.retrieve()
.bodyToMono(new ParameterizedTypeReference<T>() {
});
}
But I always have the following error :
queryParam
(java.lang.String,
Object...)
in UriBuilder cannot be applied
to
(org.springframework.util.LinkedMultiValueMap)
It's probably really simple but I can't figured it out :/
Thanks for your help.
UPDATE :
Finally found the solution a few minutes after sending my request here ! In order to use a map for queryParam I must use .queryParams instead of .queryParam ^^
I am working in a Spring #Component class and I am trying to get the HTTP status code of a particular URL for further processing. I have a function as follows:
fun getStatus() : String
{
val webClient = WebClient.create("https://stackoverflow.com")
val result = webClient.get()
.exchange().map { res -> res.rawStatusCode() }
println(result)
return "statusGotten"
}
However, rather than getting the Int value of the status code (e.g. 200, or 401), I am simply getting: "MonoMap".
I am new to both Spring and Web Programming in general, so I'm a little confused how to proceed from here. I'm aware that "result" is being returned as a "Mono", but I'm less clear about what a "Mono" is, or how I might transform it into something with more scrutable properties, as even looking at "result" in the debugger doesn't shed any light as to whether the HTTP request was actually sent or was successful:
Am I calling the webclient incorrectly? Or merely failing to parse the resultant data in a meaningful way? Any suggestions on how or where I might learn more about the underlying topics would be much appreciated as well.
If you need a blocking way to do this is easy just
#Test
public void myTest(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
ClientResponse resp = client
.get()
.uri("questions/")
.exchange()
.block();
System.out.println("Status code response is: "+resp.statusCode());
}
But for this you can use directly the RestTemplate instead the webclient... the recomended way to do this is non blocking what means you should return a Mono with the status and consume outside your method like for example:
public Mono<HttpStatus> myMethod(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
return client
.get()
.uri("questions/")
.exchange()
.map( clientResp -> clientResp.statusCode());
}
The way of consume this Mono depends of your code...