Reactive Spring WebClient calls - spring-boot

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

Related

Spring WebFlux and WebClient Call not working

I'm a newbie in reactive programming, I have the following rest resource that calls a service that use WebClient to call an existing Rest API :
#PostMapping
public Mono<String> createUser(#RequestBody UserResourceDTO userResourceDto) {
return userService.createUser(userResourceDto);
}
in my service I have the following methode which works fine :
// Working method
public Mono<String> createUser(UserResourceDTO userResourceDTO){
return httpCall(userResourceDTO);
}
when I use this method the HTTP call is triggered and I see it in my log, but since I don't want to return the value from this method I tried the following method :
// Not Working method
public Mono<String> createUser(UserResourceDTO userResourceDTO){
Mono<String> rs = httpCall(userResourceDTO);
return Mono.just("just a value");
// i plan to trigger another call to the DB this is why i don't want to return the result from httpCall
// return userRepository.createUser(userResourceDTO);
}
this method is not working and the HTTP request is not triggered ( i don't see it in the console like the first method), can anyone explain to me why the second method is not triggering the WebClient call and why I'm obliged to return the result from mhttpCall(userResourceDTO);
this is httpCall code :
public Mono<String> httpCall(UserResourceDTO userResourceDTO) {
WebClient webClient = WebClient.create();
KeyCloakUser keyCloakUser = new KeyCloakUser();
// setting values...
return webClient.post()
.uri(new URI("myurl"))
.header("Authorization", "Bearer "+toremove)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(keyCloakUser), KeyCloakUser.class)
.retrieve()
.bodyToMono(String.class);
}
// method with a second call not triggerd
public Mono<UserResourceDTO> twocalls(UserResourceDTO userResourceDTO){
return httpCall(userResourceDTO)
.flatMap(res -> {
// Sysout not executed
System.out.println(res);
// my API return empty body with 201
if("".equals(res)
return userRepository.createUser(userResourceDTO)
}
);
}
This is one of the common mistakes after switching to reactive programming. In reactive nothing happens until you subscribe. You need to chain async/sync functions using reactive operators like flatMap, map, etc.
Good starting point to understand the concept is Flight of the Flux 1 - Assembly vs Subscription.
Incorrect
public Mono<String> createUser(UserResourceDTO userResourceDTO){
Mono<String> rs = httpCall(userResourceDTO);
return Mono.just("just a value");
}
httpCall(userResourceDTO) will not be executed as a part of the reactive flow.
Correct
public Mono<String> createUser(UserResourceDTO userResourceDTO){
return httpCall(userResourceDTO)
.then(Mono.just("just a value"));
}
to continue with another async call
public Mono<String> createUser(UserResourceDTO userResourceDTO){
return httpCall(userResourceDTO)
.then(userRepository.createUser(userResourceDTO));
}
or, in case you need result of the HTTP call
public Mono<String> createUser(UserResourceDTO userResourceDTO){
return httpCall(userResourceDTO)
.flatMap(res -> userRepository.createUser(userResourceDTO));
}

Spring boot - WebFlux - WebTestClient - convert response to responseEntity

I have a Reactive controller which returns:
#ResponseBody
#GetMapping("/data")
public Mono<ResponseEntity<Data>> getData() {
//service call which returns Mono<Data> dataMono
Mono<ResponseEntity<Data>> responseEntityMono = dataMono
.map(data -> ResponseEntity.ok(data))
.doFinally(signalType -> {});
return responseEntityMono;
}
I am using WebTestClient to test this endpoint, but I want to extract the response entity for cucumber to validate further.
I tried this:
#Autowired private WebTestClient webTestClient;
public ResponseEntity get() {
EntityExchangeResult < ResponseEntity > response = webTestClient.get()
.uri(uriBuilder ->
uriBuilder
.path(VISUALIZATION_URL)
.build())
.header("Accepts", "application/json")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_VALUE)
.expectBody(ResponseEntity.class)
.returnResult();
return response.getResponseBody();
}
but I am getting an error. I can get the JSON by doing:
public String get() {
BodyContentSpec bodyContentSpec = webTestClient.get()
.uri(uriBuilder ->
uriBuilder
.path(URL)
.build())
.header("Accepts", "application/json")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_VALUE)
.expectBody();
return new String(bodyContentSpec.returnResult().getResponseBody());
}
But I am trying to see if I can get the whole ResponseEntity so that I can validate headers, caching headers, and body.
You will never be able to get a ResponseEntity in your test. WebTestClient.ResponseSpec returned from exchange() is the only way to check the answer from your Controller. ResponseEntity is just the object you return in your method but once Jackson serializes it, it is converted to a regular HTTP response (in your case with JSON in his body and regular HTTP headers).

How to call a microservice to fetch data in spring webflux

I want to call a microservice from another service using webclient in spring flux. But, I am not able to write the code properly. Can you please suggest how to call another service. Please find my code as below.
I need to call the below service
public Mono<ServerResponse> load(ServerRequest res){
String c1name = res.pathVariable("cust");
String c2name = res.queryParam("cl").orElse("");
String oname = res.queryParam("ol").orElse("");
return res.body()
}
public Mono<ResponseEntity<Void>> ftpFileSend(MultipartFile fileData, String cust, MultiValueMap<String,String) qpar {
MultiValueMap<String,String> qpar=new LinkedMultiValueMap<String,String>();
qpar.add("name","spring");
MultiValueMap<String,Object> body=new LinkedMultiValueMap<String,Object>();
String url="http://localhost:8088/"+ cust+"/load";
try {
body.add("file", fileData.getBytes());
} catch (IOException e) {
return Mono.error(e); // <-- note how to create an error signal
}
return webClient
.post()
.uri(uriBuilder -> uriBuilder.path(url).queryParams(qpar).build() )
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(body))
.retrieve()
.toBodilessEntity();
}
Hmm it would be great if you have provided some error logs or so. Anyway if you want to create a multipart body there is a builder, MultipartBodyBuilder (in org.springframework.http.client.MultipartBodyBuilder).
Example usage is as follows,
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("file", new MultipartFileResource(fileData));
MultiValueMap<String, HttpEntity<?>> multipartBody = builder.build();
Then use this multipartBody in webClient call.
return webClient
...
.body(BodyInserters.fromMultipartData(multipartBody))
.retrieve()
.toBodilessEntity();

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.

Multiple timeouts for request spring WebFLux

Hi im using webClient from spring webflux. i have some code like:
#Configuration
class WebClientConfig(
#Value("\${url}")
private val url: String
) {
#Bean
#Primary
fun webClient(): WebClient {
return createWebClient(700)
}
#Bean("more_timeout")
fun webClientMoreTimeout(): WebClient {
return createWebClient(3000)
}
private fun createWebClient(timeout: Int): WebClient{
val httpClient = HttpClient.create()
.tcpConfiguration { client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout) }
return WebClient.builder()
.baseUrl(url)
.clientConnector(ReactorClientHttpConnector(httpClient))
.build()
}
}
This configuration is because i need calls with different timeout. Supose i have one service A which is very importart for my response so i want to wait for the response maximum 3 seconds, and supose y have another services B, C, etc. which are not very important for my response, i will only wait 700ms to generate the response. Who can i archive this?
The previous config is not working because webClient is inmutable.
I think you can't do it at webClient level, but you can do it at Reactor level, something like:
return webClient.post()
.uri { uriBuilder ->
uriBuilder.path(PATH)
.build()
}
.body(BodyInserters.fromObject(Request()))
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.timeout(Duration.ofMillis(1000L))

Resources