Where do I place "doOnError" in my webclient call? - spring-boot

I have a webclient call that looks like :-
return this.webClient.post()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(request))
.retrieve()
.bodyToMono(Reponse.class)
.doOnError(err -> {
throw new UserDefinedException();
})
.block();
Would it make a difference if i placed "doOnError()" before bodyToMono/retreive/ and so on..

The expected way to transform an error signal to a custom exception is to use onErrorMap operator:
return this.webClient.post()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(request))
.retrieve()
.bodyToMono(Reponse.class)
.onErrorMap(err -> new UserDefinedException())
.block();
onErrorMap operator catch any Mono errors and map them to a custom exception.
bodyToMono, transform the chain to a Mono in the success case, so it could be after onErrorMap.

Related

Call REST endpoint using webclient which has both PathVariable and RequestParam

I have the following endpoint, which needs to be called from webclient. date is red using #PathVariable and name is red using #RequestParam annotations.
/api/v1/names/officialNameByDate/{date}?name=empName
Is the following usage correct?
String response = webclient.get()
.uri(uriBuilder -> uriBuilder
.path("/api/v1/names/officialNameByDate/{date}")
.queryParam("name", empName)
.build(date))
.retrieve()
.bodyToMono(String.class).block();
Yes, you can use both Path variable and Request Param.
webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/{id}/attributes/{attributeId}")
.queryParam("name", "AndroidPhone")
.queryParam("color", "black")
.build(2, 13))
.retrieve()
.bodyToMono(String.class)
.block();
verifyCalledUrl("/products/2/attributes/13?color=black");
See above code as an example.

How to convert webclient response to ResponseEntity?

I am able to convert WebClient response to Response Entity with exchange() method which is deprecated now.
Please suggest other way of achieving the same result. Below is my code.
public ResponseEntity<TestClass> getTestDetails() {
ClientResponse clientResponse = webClientBuilder.build()
.get()
.uri("http://localhost:9090/test")
.headers(httpHeaders -> {
httpHeaders.add(Constants.ACCEPT, Constants.APPLICATION_JSON);
})
.exchange()
.block();
return clientResponse.toEntity(TestClass.class).block();
}
I did it in the following way:
public ResponseEntity<TestClass> getTestDetails() {
return webClientBuilder.build()
.get()
.uri("http://localhost:9090/test")
.headers(httpHeaders -> {
httpHeaders.add(Constants.ACCEPT, Constants.APPLICATION_JSON);
})
.retrieve()
.toEntity(TestClass.class)
.block();
}

Spring WebClient - chaining async calls

I'm building a plain old Java desktop application with Swing, but needs to make Rest calls to a backend. Since we're in a modern async world, and the tools exists, i want to make async calls to the backend, display a progressbar, and hide it when the calls are done.
Using Spring WebClient seems the way to go, and it works well, until i need to chain multiple async Rest calls. In that case, the second call never respond... I see the backend really receives the 2 calls, but the client side never resumes.
public void notWorking(CurrentTool toolDto, Consumer<ToolDto> successCallback) {
webClient.post()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(toolDto.getTool().getToolId());
})
.body(BodyInserters.fromObject(toolDto.getTool()))
.retrieve()
.bodyToMono(Long.class)
.subscribe(id -> {
webClient.get()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(id);
})
.retrieve()
.bodyToMono(ToolDto.class)
.subscribe((response) -> {
successCallback.accept(response);
});
});
}
However, if I make the same calls but in a blocking way, everything works fine. (Except it's sync, so my loading bar won't work...)
public void working(CurrentTool toolDto, Consumer<ToolDto> successCallback) {
Long id = webClient.post()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(toolDto.getTool().getToolId());
})
.body(BodyInserters.fromObject(toolDto.getTool()))
.retrieve()
.bodyToMono(Long.class)
.block();
webClient.get()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(id);
})
.retrieve()
.bodyToMono(ToolDto.class)
.subscribe((response) -> {
successCallback.accept(response);
});
}
You should try to avoid nesting subscribes. You can use flatmap instead.
public void shouldBeWorking(CurrentTool toolDto, Consumer<ToolDto> successCallback) {
webClient.post()
.uri(uriBuilder -> uriBuilder
.path("/tools/{id}")
.build(toolDto.getTool().getToolId()))
.body(BodyInserters.fromObject(toolDto.getTool()))
.retrieve()
.bodyToMono(Long.class)
.flatmap(id -> webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/tools/{id}")
.build(id))
.retrieve()
.bodyToMono(ToolDto.class))
.subscribe(successCallback::accept);
}

How to set base url and query parameters for WebClient?

In my service, I have to get response from some different urls with parameters.
get from http://a.com:8080/path1?param1=v1
get from http://b.com:8080/path2?param2=v2
get from http://c.com:8080/path3?param3=v3
I am using WebClient to do the job as following.
public class WebClientTest {
private WebClient webClient = WebClient.builder().build();
#Test
public void webClientTest() {
Mono<String> a = webClient.get()
.uri(uriBuilder -> uriBuilder.scheme("http").host("a.com").port(8080).path("/path1")
.queryParam("param1", "v1")
.build())
.retrieve()
.bodyToMono(String.class);
Mono<String> b = webClient.get()
.uri(uriBuilder -> uriBuilder.scheme("http").host("b.com").port(8080).path("/path2")
.queryParam("param2", "v2")
.build())
.retrieve()
.bodyToMono(String.class);
Mono<String> c = webClient.get()
.uri(uriBuilder -> uriBuilder.scheme("http").host("c.com").port(8080).path("/path3")
.queryParam("param3", "v3")
.build())
.retrieve()
.bodyToMono(String.class);
//zip the result
}
}
As you can see, I have to set scheme, host, port separately again and again.
So my questions are:
1. Am I using WebClient in a right way?
2. Is it possible to set scheme, host, port in a method together? I know that webClient.get().uri("http://a.com:8080/path1?param1=v1").retrieve() works, but what I am expecting is something like:
webClient.get()
.uri(uriBuilder -> uriBuilder/*.url("http://a.com:8080/path1")*/
.queryParam("param1", "v1")
.build())
.retrieve()
.bodyToMono(String.class);
As of Spring Framework 5.2, there is an additional method that can help with your specific situation:
Mono<String> response = this.webClient
.get()
.uri("http://a.com:8080/path1", uri -> uri.queryParam("param1", "v1").build())
.retrieve()
.bodyToMono(String.class);
I wouldn't advise creating one WebClient per host as a general rule. It really depends on your use case. Here it seems your client might send requests to many hosts, and creating many HTTP clients can be a bit wasteful here.
The way I solved this was to have a WebClient for each different url.
So you would have
private WebClient aClient = WebClient.create("a.com")
private WebClient bClient = WebClient.create("b.com")
private WebClient cClient = WebClient.create("c.com")
Then interact with each WebClient depending on what you're calling.
https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web-reactive.html#webflux-client-retrieve

Spring Boot 2 - Transforming a Mono to a rx.Observable?

I'm trying to use the HystrixObservableCommand with the Spring WebFlux WebClient and I wonder if there is a "clean" to transform a Mono to an rx.Observable. My initial code looks like this:
public Observable<Comment> getComment() {
return webClient.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Comment.class)
// stuff missing here :(.
}
Is there an easy to do this ?
Regards
The recommended approach is to use RxJavaReactiveStreams, more specifically:
public Observable<Comment> getComment() {
Mono<Comment> mono = webClient.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Comment.class);
return RxReactiveStreams.toObservable(mono); // <-- convert any Publisher to RxJava 1
}
You can use
Observable.fromFuture(webClient.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Comment.class).toFuture());

Resources