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

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());

Related

Where do I place "doOnError" in my webclient call?

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.

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

Reactive Spring WebClient calls

I am trying to understand WebFlux but having some trouble with Webclient calls. I do not see
this line System.out.println("customerId = " + customerId); executes it seems like it does not call the endpoint.
But if I subscribe to webclient with .subscribe(customer -> {}); then I can see this line System.out.println("customerId = " + customerId); works
on the endpoint side. I dont understand why I have to subscribe to Mono call, or do I have to ? Thanks
#GetMapping("/customer/{customerId}")
#ResponseStatus(HttpStatus.ACCEPTED)
public Mono<Void> getCustomer(#PathVariable("customerId") int customerId) {
WebClient webClient = WebClient.builder().baseUrl("http://localhost:8080").build();
webClient.get()
.uri("/client/customer/{customerId}",customerId)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Customer.class);//here do I have to subscribe to actually activate to call?
return null;
}
#GET
#Path("/customer/{customerId}")
#Produces(MediaType.APPLICATION_JSON)
public Customer getCustomer(#PathParam("customerId") int customerId) throws InterruptedException {
System.out.println("customerId = " + customerId); // I do not see the call comes thru if I dont subscribe to flux call.
return new Customer(customerId,"custName");
}
If you want to return the reactive type from your WebClient, you have to return it from your controller method like:
#GetMapping("/customer/{customerId}")
#ResponseStatus(HttpStatus.ACCEPTED)
public Mono<Customer> getCustomer(#PathVariable("customerId") int customerId) {
WebClient webClient = WebClient.builder().baseUrl("http://localhost:8080").build();
return webClient.get()
.uri("/client/customer/{customerId}",customerId)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Customer.class);
}
You can also return a Customer from your endpoint and block and wait for the result of your WebClient and leaving the reactive ecosystem like:
#GetMapping("/customer/{customerId}")
#ResponseStatus(HttpStatus.ACCEPTED)
public Customer getCustomer(#PathVariable("customerId") int customerId) {
WebClient webClient = WebClient.builder().baseUrl("http://localhost:8080").build();
return webClient.get()
.uri("/client/customer/{customerId}",customerId)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Customer.class)
.block()
}
If you are looking at a general introduction for Spring's WebClient, take a look at this tutorial

Resources