concurrent calls using spring webflux and hystrix - spring-boot

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.

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

Extract Mono nonblocking response and store it in a variable and use it globally

In my project I have a requirement where I need to call a third party api authentic url to get the the access token. I need to set that access token in every subsequent request header .The access token has some lifetime and when the lifetime expired I need to regenerate the access token.
application.yml
I have hardcoded the client_id,client_secret,auth_url and grant_type .
AuthController.java
here I have created an endpoint to generate the access token.
**`AuthService.java`**
#Services
#Slf4j
public class AuthService{
#Autowired
private WebClient webClient;
static String accessToken="";
public Mono<SeekResponse> getAccessToken(AuthRequest authRequest) throws InvalidTokenException{
Mono<AuthResponse> authResponse=webClient.post()
.bodyValue(authRequest)
.accept(MediaType.APPLICATION_JSON)
.retrive()
.bodyToMono(AuthResponse.class);
authResponse.doOnNext(response->{
String value=response.getAccess_token();
accessToken=accessToken+value;
})
}
}
Although I have updated the "accessToken" value but it will return me null. I understand as I have made async call this value coming as null. I can't use blocking mechanism here.
Is there any other way to generate the access token and pass it as a header for the subsequent request for authentication. Or how can I use the accessToken value globally so that I can set those token value to my subsequent api request call.
I have tried with oAuth2 by following the below article:
https://medium.com/#asce4s/oauth2-with-spring-webclient-761d16f89cdd
But when I execute I am getting the below error :
"An expected CSRF token cannot found".
I'm also learning Webflux. Here's my thought. Please correct me if I'm wrong.
We are not going to rely on doOnNext() nor doOnSuccess() nor other similar method to try to work on an pre-defined variable accessToken (That's not a way to let Mono flow). What we should focus on is converting a mono to another mono, for example converting mono response to mono access token.
The way to do that is .flatmap()/.map()/.zipwith()/...
For example,
Mono<string> tokenMono = responseMono.flatmap(
// in the map or flatmap, we get the chance to operate on variables/objects.
resp -> {
string token = response.getAccess_token();
return Mono.just(token); // with Mono.just(), we are able to convert object to Mono again.
}
) // this example is not practical, as map() is better to do the same thing. flatmap with Mono.just() is meaningless here.
Mono<string> tokenMono2 = responseMono.map(
resp -> {
string token = response.getAccess_token();
return token;
}
)
Everything starting from Mono should be always Mono until subscribed or blocked. And they provide us ways to operate on those variables inside Mono<variables>. Those are map() flatmap(), zipwith(), etc.
https://stackoverflow.com/a/60105107/18412317
Referring to a point this author said, doOnNext() is for side effect such as logging.
It's hard to understand provided sample and implementation is not really reactive. The method returns Mono but at the same time throws InvalidTokenException or usage of onNext that is a so-called side-effect operation that should be used for logging, metrics, or other similar use cases.
The way you implement oauth flow for WebClient is to create filter, Client Filters.
Spring Security provides some boilerplates for common oauth flows. Check Spring Security OAuth2 for more details.
Here is an example of simple implementation of the client credential provider
private ServerOAuth2AuthorizedClientExchangeFilterFunction oauth(String clientRegistrationId, ClientConfig config) {
var clientRegistration = ClientRegistration
.withRegistrationId(clientRegistrationId)
.tokenUri(config.getAuthUrl() + "/token")
.clientId(config.getClientId())
.clientSecret(config.getClientSecret())
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
var authRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
var authClientService = new InMemoryReactiveOAuth2AuthorizedClientService(authRepository);
var authClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
authRepository, authClientService);
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authClientManager);
oauth.setDefaultClientRegistrationId(clientRegistrationId);
return oauth;
}
then you could use it in the WebClient
WebClient.builder()
.filter(oauth)
.build()
UPDATE
Here is an example of the alternative method without filters
AuthService
#Service
public class AuthService {
private final WebClient webClient;
public AuthService() {
this.webClient = WebClient.create("<url>/token");
}
public Mono<String> getAccessToken() {
return webClient.post()
.bodyValue()
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(AuthResponse.class)
.map(res -> res.getAccessToken());
}
}
ApiService
#Service
public class ApiService {
private final WebClient webClient;
private final Mono<String> requestToken;
public ApiService(AuthService authService) {
this.webClient = WebClient.create("<url>/api");
// cache for token expiration
this.requestToken = authService.getAccessToken().cache(Duration.ofMinutes(10));
}
public Mono<String> request() {
return requestToken
.flatMap(token ->
webClient.get()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class)
);
}
}

Spring Webflux - How do I call an reactive endpoint within a WebFilter

I want to implement a WebFilter that reads a specific header of the incoming request, calls a GET request to another reactive REST endpoint with the value of this header, then mutates the original request with the value of the GET response.
I want to implement this in a WebFilter because I don't want to have to add this function call to every function in my #RestController.
Currently I have this:
#Component
class ExampleWebFilter(val webClients: WebClients) : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
println(exchange.request.headers)
println(exchange.request.path)
println(exchange.response)
val test = webClients.internalAuthServiceClient.get()
.uri("/api/authorisation/v1/test")
.header("authToken", "authToken123")
.retrieve().bodyToMono(String::class.java)
println(test)
exchange.mutate().request(
exchange.request.mutate().header("newheader", test).build()
)
return chain.filter(exchange)
}
}
#Component
class WebClients() {
val internalAuthServiceClient = WebClient.builder()
.baseUrl("lb://auth-service")
.build()
}
This obviously doesn't work right now. My WebClient is returning Mono so I can't use this directly in my mutate() call because that requires a String. I also can't really make the WebClient call a blocking operation for obvious reasons.
Does anyone know how I could solve this problem?
I don't use kotlin so you will have to convert but this is how you would do it in java. I'd imagine it will be pretty much the same though.
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
return webClients
.internalAuthServiceClient
.get()
.uri("/api/authorisation/v1/test")
.retrieve()
.bodyToMono(String.class)
//Gonna assume you tested the above and all works
//If get bad response or any error really
// then print error + return empty mono
.onErrorResume(e -> {
e.printStackTrace();
return Mono.empty();
})
//Map the header AFTER a response
//only maps if Mono is not empty
.map(header -> {
serverWebExchange
.getRequest()
.mutate()
.header("web-filter", header);
return serverWebExchange;
})
//return unchanged serverWebExchange if empty Mono
.defaultIfEmpty(serverWebExchange)
//Flatmap since filter returns Mono to prevent returning Mono<Mono<void>>
.flatMap(webFilterChain::filter);
}
The issue you are facing is due to you trying to do it in a synchronous way, whereas you need to map the header after you have received the response from the WebClient.

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

Spring WebFlux Post Issue

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.

Resources