Reactive - Improve Mono syntax - java

I have this block of code that works fine:
return isBlacklistedToken(refreshToken, Boolean.TRUE).flatMap(isBlacklisted -> {
if (isBlacklisted)
return Mono.error(new UnauthorizedException(format("The user %s has already logged out.", username)));
else
return isBlacklistedToken(accessToken, Boolean.FALSE).flatMap(isABlacklisted -> {
if (isABlacklisted)
return Mono.error(new UnauthorizedException(format("The user %s has already logged out.", username)));
else
return blacklistTokens(username, refreshToken, accessToken);
});
});
To summarize it:
calls the isBlacklistedToken function (returns a Mono<Boolean> with the result of the refresh token)
If the refresh token is blacklisted, throws an UnauthorizedException
If the refresh token is not blacklisted, does the same process for the access token
If both tokens are not blacklisted, finally blacklists them.
This syntax, while it works, seems a bit sloppy. Is there a way to improve that? I wrote this piece of code, and while it throws an exception, the last part (blacklisting the tokens) always executes - peraphs my knowledge of reactive programming is a bit off.
return isBlacklistedToken(refreshToken, Boolean.TRUE)
.flatMap(isBlacklisted -> isBlacklisted ? Mono.error(new UnauthorizedException(format("The user %s has already logged out.", username))) : Mono.empty())
.then(isBlacklistedToken(accessToken, Boolean.FALSE))
.flatMap(isBlacklisted -> isBlacklisted ? Mono.error(new UnauthorizedException(format("The user %s has already logged out.", username))) : Mono.empty())
.then(blacklistTokens(username, refreshToken, accessToken));
Edit: adding the isBlacklistedToken method
private Mono<Boolean> isBlacklistedToken(final String token, final Boolean type) {
return blacklistService.isBlacklisted(token, type);
}
and the respective blacklistService call (just a repository call, really simple)
public Mono<Boolean> isBlacklisted(final String token, final Boolean isRefresh) {
return Mono.just(this.blacklistRepository.existsBlacklistByTokenAndIsRefresh(token, isRefresh));
}

I would suggest the following:
return isBlacklistedToken(refreshToken, Boolean.TRUE)
.filter(isBlacklisted -> !isBlacklisted)
.flatMap(isBlacklisted -> isBlacklistedToken(accessToken, Boolean.FALSE))
.filter(isBlacklisted -> !isBlacklisted)
.flatMap(isBlacklisted -> blacklistTokens(username, refreshToken, accessToken))
.switchIfEmpty(Mono.error(new UnauthorizedException(format("The user %s has already logged out.", username))));
Sorry if there is some compile error but I tried this in Kotlin and needed to translate it to Java, which is become less and less easy.

Related

Avoid refreshing and saving token multiple times in a cache during parallel https calls to service

I am currently in the process of replacing the blocking HTTP calls from one of my projects with Asynchronous/reactive calls. The saveOrderToExternalAPI is an example of the refactor I have done that replaced the blocking HTTP call and now using reactive WebClient.
The function saveOrderToExternalAPI calls an endpoint and returns an empty MONO in case of success. It also retries when the API returns 401 Unauthorized error which happens when the token is expired. If you notice the retryWith logic, you will see the code is renewing the token (tokenProvider.renewToken()) before actually retrying.
The tokenProvider.renewToken() for now is a blocking call that fetches a new token from another endpoint and saves it to a Cache so that we don't have to renew the token again and subsequent calls can just reuse it. Whereas the tokenProvider.loadCachedToken() function check and return token if it is not Null, otherwise renew and then return it.
public Mono<ResponseEntity<Void>> saveOrderToExternalAPI(Order order) {
log.info("Sending request to save data.");
return webclient.post()
.uri("/order")
.headers(httpHeaders -> httpHeaders.setBearerAuth(tokenProvider.loadCachedToken()))
.accept(MediaType.APPLICATION_JSON)
.bodyValue(order)
.retrieve()
.toBodilessEntity()
.doOnError(WebClientResponseException.class, logHttp4xx)
.retryWhen(unAuthorizedRetrySpec())
.doOnNext(responseEntity -> log.debug("Response status code: {}",
responseEntity.getStatusCodeValue()));
}
private RetryBackoffSpec unAuthorizedRetrySpec() {
return Retry.fixedDelay(1, Duration.ZERO)
.doBeforeRetry(retrySignal -> log.info("Renew token before retrying."))
.doBeforeRetry(retrySignal -> tokenProvider.renewToken())
.filter(throwable -> throwable instanceof WebClientResponseException.Unauthorized)
.onRetryExhaustedThrow(propagateException);
}
private final Consumer<WebClientResponseException> logHttp4xx = e -> {
if (e.getStatusCode().is4xxClientError() && !e.getStatusCode().equals(HttpStatus.UNAUTHORIZED)) {
log.error("Request failed with message: {}", e.getMessage());
log.error("Body: {}", e.getResponseBodyAsString());
}
}
// -------- tokenProvider service
public String loadCachedToken() {
if (token == null) {
renewToken();
}
return token;
}
Everything is working fine because for now, the saveOrderToExternalAPI is eventually doing a blocking call in the Service layer.
for (ShipmentRequest order: listOfOrders) {
orderHttpClient.saveOrderToExternalAPI(order).block();
}
But, I would like this to be changed and refactor the service layer. I am wondering would happen if I keep using the existing logic of token renewal and process the saveOrderToExternalAPI parallelly as below.
List<Mono<ResponseEntity<Void>>> postedOrdersMono = listOfOrders.stream()
.map(orderHttpClient::saveOrderToExternalAPI)
.collect(Collectors.toList());
Mono.when(postedOrdersMono).block();
Now the calls will be done parallelly and the threads would try to update token cache at the same time (in case of 401 unauthorized) even though updating it once is enough. I am really new to reactive and would like to know if there's any callback or configuration that can fix this issue?

webclient retrieve() vs exchangeToMono() nothing working for this use case

API (Name it as api-1) has following property -
For 2XX it can return body
For 5XX it doesn’t return body
In another API (api-2), Our requirement is if api-1 status code is 2XX return “ABC” else return “XYZ” from webclient call, So we don’t wanted to consume response body in either cases.
Which one should work retrieve() or exchangeToMono() ?
When I use retrieve() I couldn’t return ABC or XYZ based on http response. If I use exchangeToMono() I was getting InvalidStateException with following message “The underlying HTTP client completed without emitting a response.”
Any help is appreciated.
Here is my approach.
String result = client
.get()
.uri("http://localhost:8080/endpoint")
.retrieve()
.toBodilessEntity()
.onErrorResume(WebClientResponseException.class, e -> Mono.just(ResponseEntity.status(e.getStatusCode()).build()))
.map(entity -> entity.getStatusCode().isError() ? "error" : "ok")
.block();
result is equal to ok when the request is completed successfully, and error otherwise.
You can change the condition in map if you want to return error only on 5xx exceptions.
EDIT: Or as Toerktumlare mentioned in his comment, you can use onStatus to omit an exception.
String result = client
.get()
.uri("http://localhost:8080/endpoint")
.retrieve()
.onStatus(HttpStatus::isError, e -> Mono.empty())
.toBodilessEntity()
.map(entity -> entity.getStatusCode().isError() ? "error" : "ok")
.block();

Rxjava approach the get last Observable from each thread

I'm thinking how to use RXJava for the scenario described bellow.
A List<Object>,each object will be sent to k8s and checked the status till the respone return true,so my polling active is that:
private Observable<Boolean> startPolling(String content) {
log.info("start polling "+ content);
return Observable.interval(2, TimeUnit.SECONDS)
.take(3)
.observeOn(Schedulers.newThread())
.flatMap(aLong -> Observable.just(new CheckSvcStatus().check(content)))
.takeUntil(checkResult -> checkResult)
.timeout(3000L, TimeUnit.MILLISECONDS, Observable.just(false))
;
}
Function of sent action:
Observable<Compo> sentYamlAndGet() {
log.info("sent yaml");
sentYaml()
return Observable.just(content);
}
I try to use the foreach to get each object status which like this:
public void rxInstall() throws JsonProcessingException {
List<Boolean>observables = Lists.newArrayList();
Observable.from(list)
.subscribeOn(Schedulers.newThread())
.concatMap(s -> sendYamlAndGet())
.timeout(3000l, TimeUnit.MILLISECONDS)
.subscribe()
;
Observable.from(list).forEach(s -> {
observables.add(Observable.just(s)
.flatMap(this::startPolling)
.toBlocking()
.last()
)
;
System.out.println(new ObjectMapper().writeValueAsString(observables));
}
Objects of outputs list is :{"o1","o2","o3","o4","o5"}
the last status of objest which I want is : [false,true,false,false,true].
All above style is not much 'ReactX',check object status action do not affect to each other.
How to throw foreach? I trid toIterable(),toList() but failed.
Observable.from(list)
.concatMap(s -> sentYamlAndGet())
.concatMap(this::startPolling)
....
;
Wanted to know if it's good practice to do that and what would be the best way to do that?
Thanks in advance.
pps: currentlly I'm using rxjava1 <version>1.2.0</version> but can change to 2(´▽`)ノ

Spring webflux non-blocking response

I have a request method that use 'username' and 'password' body parameters to find real user from database, validate it by using request password and if everything is fine - return generated token as String.
public Mono<ServerResponse> handleLogin(ServerRequest request) {
User body = request.bodyToMono(User.class).block();
return userRepository.findById(body.getUsername())
.filter(user -> passwordEncoder.matches(body.getPassword(), user.getPassword()))
.flatMap(user -> ServerResponse.ok().body(Mono.just(tokens.store(user)), String.class))
.switchIfEmpty(ServerResponse.badRequest().build());
}
This method works fine, but i'm trying to make it as non-blocking and i'm not sure how to achieve it. Any help appreciated.
Updated
For now i changed my method content to
return request.bodyToMono(User.class)
.flatMap(body -> userRepository.findById(body.getUsername()).flatMap(user -> Mono.just(new Object[]{body.getPassword(), user})))
.filter(obj -> {
User user = (User) obj[1];
return user.isActive() && passwordEncoder.matches((CharSequence) obj[0], user.getPassword());
})
.flatMap(obj -> {
User user = (User) obj[1];
return ServerResponse.ok().body(Mono.just(tokens.store(user)), String.class);
})
.switchIfEmpty(ServerResponse.badRequest().build());
This is non-blocking, but looks like not so elegant solution. Can it be improved/simplified somehow?
Remember
1) Everything is stream.
2) Keep your flow composed
3) No blocking operation at all.
Solution
So, keep your flow composed without blocking operation
public Mono<ServerResponse> handleLogin(ServerRequest request) {
return request.bodyToMono(User.class)
.flatMap(body -> userRepository.findById(body.getUsername())
.filter(user -> passwordEncoder.matches(body.getPassword(), user.getPassword()))
.flatMap(user -> ServerResponse.ok().body(Mono.just(tokens.store(user)), String.class))
.switchIfEmpty(ServerResponse.badRequest().build());
}
Regarding the last update
Regarding the last update, from my point of view, the code might be optimized in a next way:
return request.bodyToMono(User.class)
.flatMap(body -> userRepository.findById(body.getUsername())
.filter(user -> user.isActive())
.filter(user -> passwordEncoder.matches(body.getPassword(), user.getPassword()))
)
.flatMap(user -> ServerResponse.ok().syncBody(tokens.store(user)))
.switchIfEmpty(ServerResponse.badRequest().build());

onError not implemented error in RxJava2, even though its implemented

So I have a very basic workflow of RxJava observer stream where I request something from retrofit, with successful response I toast successful msg and with error I toast error msg.
The case I am referring below is the error case where I expect error msg from API, I convert it into user readable words and display as Toast, As shown below when I use doOnNextand doOnError method this way it crashes with error mentioned.
I have added throwExceptionIfFailure method as well which shows how I convert readable msg and the line where console points to the error.
registerNFCTag(body)
.map(result -> throwExceptionIfFailure(result))
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(result -> {
toggleLoaders(true);
appToast(getString(R.string.done_msg) + tagName);
})
.doOnError(throwable -> {
Toasty.error(this, throwable.getLocalizedMessage()).show();
toggleLoaders(true);
})
.subscribeOn(Schedulers.io())
.subscribe();
Error If this isnt enough I can post stacktrace as well.
java.lang.IllegalStateException: Exception thrown on Scheduler.Worker thread. Add `onError` handling.
ThrowExceptionIfFailure method.
public <T> T throwExceptionIfFailure(T res) {
Response result = (Response<?>) res;
if (!result.isSuccessful()) {
try {
String msg = result.errorBody().string();
Log.d(TAG, "throwExceptionIfFailure: "+msg);
if (result.code() == 401 || result.code() == 403) {
invalidateToken();
msg = context.getString(R.string.invalid_credential);
}
else if (result.code() == 502)
msg = context.getString(R.string.server_down);
else if (result.code() == 422)
msg = context.getString(R.string.invalid_domain);
else if (result.code() == 500)
msg = context.getString(R.string.internal_server_error_500_msg);
else if (result.code() == 451)
------><>>>>>> expected error msg works well with the case mentioned below with throwable in subscribe itself.
msg = context.getString(R.string.toast_tag_already_registered_error);
if (result.code() == 403)
throw new TokenException();
else
------>>>>>below line where console points error
throw Exceptions.propagate(new RuntimeException(msg));
} catch (Throwable e) {
throw Exceptions.propagate(e);
}
} else {
return res;
}
}
But same thing I subscribe this way and it works fine and I see the error msg toasted as expected.
registerNFCTag(body)
.map(result ->throwExceptionIfFailure(result))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(
result -> {
toggleLoaders(true);
appToast(getString(R.string.done_msg) + tagName);
}
, throwable -> {
Toasty.error(this, throwable.getLocalizedMessage()).show();
toggleLoaders(true);
});
Still novice in RxJava2 world so, Help me understand the difference. Thank you in advance.
As of RxJava 2.1.9 there are six overloads of subscribe() method. You have used an overload, that takes no consumers at all. Exception message tells you, that you should use a subscribe() overload, that takes error Consumer, e.g. subscribe(Consumer<? super T> onNext, Consumer<? super java.lang.Throwable> onError).
doOnError() is a "side-effect" operator, it has nothing to do with actual subscription.

Categories

Resources