My Spring Boot application uses WebClient to make calls to a remote API. I do have some difficulty understanding the difference between the following modes on how to use the WebClient.
Option 1 - using block()
// WebClient
public Boolean updateUser(long id) {
return webClient.post()
.uri(uriBuilder -> uriBuilder.path(USER_PATH).build(id))
.body(Mono.just(payload), User.class)
.exchangeToMono(clientResponse -> Mono.just(clientResponse.statusCode().is2xxSuccessful()))
.block();
}
// Caller
Boolean result = updateUser(5);
Option 2 - using toFuture():
// WebClient
public CompletableFuture<Boolean> updateUser(long id) {
return webClient.post()
.uri(uriBuilder -> uriBuilder.path(USER_PATH).build(id))
.body(Mono.just(payload), User.class)
.exchangeToMono(clientResponse -> Mono.just(clientResponse.statusCode().is2xxSuccessful()))
.toFuture();
}
// Caller
CompletableFuture<Boolean> future = updateUser(5);
Boolean result = future.get();
As far as I understand, using .block() blocks the thread when the WebClient makes its request and waits for a response.
When using toFuture() instead, then the WebClient runs on a different thread, thus it does not block. But is the thread not blocked anyways using the .get() method on the CompletableFuture?
When would I choose one over the other?
In the second option, you allow the caller to decide when to wait, this looks more flexible than the first option.
TL;DR
Mono.toFuture() is not blocking but Mono.toFuture().get() is blocking. block() is technically the same as toFuture().get() and both are blocking.
Mono.toFuture() just transforms Mono into a CompletableFuture by subscribing to it and resolving immediately. But it doesn't mean that you can access result of the corresponding Mono after this. CompletableFuture is still async and you can use methods like thenApply(), thenCompose(), thenCombine(), ... to continue async processing. toFuture().get() is a blocking operation.
CompletableFuture<Double> result = getUserDetail(userId)
.toFuture()
.thenCompose(user -> getCreditRating(user));
where getUserDetail is defined as
Mono<User> getUserDetail(String userId);
Mono.toFuture is useful when you need to combine different async APIs. For example, AWS Java v2 API is async but based on CompletableFuture but we can combine APIs using Mono.toFuture or Mono.fromFuture.
Related
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.
I have this request with WebClient:
webClient
.get()
.uri(uri)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.<Optional<ByteArrayResource>>exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.NOT_FOUND)) {
return Mono.just(Optional.empty());
}
return response.bodyToMono(ByteArrayResource.class).map(Optional::of);
})
.block();
How can I test the logic inside exchangeToMono()?
I'm using Mockito for testing this way:
given(headersSpecHeadOpMock.exchangeToMono()).willReturn(Mono.just(clientResponse))
But the problem here is that this way I'm not testing the HttpStatus.NOT_FOUND.
The problem I was having is that I was mocking the ClientResponse, but I would have to Mock a Function<ClientResponse, ? extends Mono<Optional<ByteArrayResource>>>.
The solution was to use an ArgumentCaptor to get the argument and then make the assert of his value like this:
ArgumentCaptor declaration:
ArgumentCaptor<Function<ClientResponse, ? extends Mono<Optional<ByteArrayResource>>>> captorLambda = ArgumentCaptor.forClass(Function.class);
Capture the argument:
given(headersSpecGetOpMock.<Optional<ByteArrayResource>>exchangeToMono(captorLambda.capture())).willReturn(Mono.just(Optional.empty()));
Assert the value returned by the client response:
assertThat(captorLambda.getValue().apply(clientResponse).block()).isEqualTo(Optional.empty());```
Consider rewriting your code as follows:
webClient
.get()
.uri(uri)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.retrive()
.onStatus(status -> HttpStatus.NOT_FOUND == status, response -> Mono.just(Optional.empty()))
.bodyToMono(ByteArrayResource.class);
.map(Optional::of)
.block();
Now you can mock retrieve() method to test your conditions easily.
As a side note, please consider dropping block() call, this defeats the purpose of using reactive programming.
You can use okhttp to mock the server you are hitting as shown here: https://www.baeldung.com/spring-mocking-webclient
The only issue is that you have to use the .mutate() method on the webclient you are trying to test to edit the basurl to match that of the mock webserver. In my own implementation, I used getters and setters to alter the webclient baseurl and then set it back to the original at the end of the test.
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();
}
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 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...