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.
Related
Getting produced no reply for request Message from element "int:delayer" implemented inside chain ("int:chain").
Inside delayer expression and default-delay evaluated successfully after that getting below error due to which retry is not working in case of any failure.
from source: ''int:delayer' with id='delayRetry''' produced no reply for request Message
Tried setting value for requires-reply='false' for the calling parent component. but still getting same issue
Code Snippet:
<int:header-enricher>
<int:header name="delay" expression="headers.containsKey('SomeVariable')?0:(headers['adviceDelay']?:1000)*1.2"
type="java.lang.Integer" overwrite="true" />
</int:header-enricher>
<int:delayer id=delayRetry " expression="headers['delay']" default-delay="100" ignore-expression-failures="false"
message-store="adviceRetryMessageStore" scheduler="delayerTaskScheduler">
<int:transactionaltransaction-manager = jdbcTransactionManager " />
</int:delayer>
<int:routerexpression = "headers.someChannel" />
Here we are setting delay value based on condition defined in expression headers.containsKey('SomeVariable')
If this condition satisfies then, adding 0 delay in retry. In this case retry is working fine and flow routed back to headers.someChannel for retry.
But in another scenario when header not contains SomeVariable, so setting delay of 1200. In this case code enters in error flow and retry is not working.
Hm. That is correct. The delayer logic is like this:
protected Object handleRequestMessage(Message<?> requestMessage) {
boolean delayed = requestMessage.getPayload() instanceof DelayedMessageWrapper;
if (!delayed) {
long delay = determineDelayForMessage(requestMessage);
if (delay > 0) {
releaseMessageAfterDelay(requestMessage, delay);
return null;
}
}
// no delay
return delayed
? ((DelayedMessageWrapper) requestMessage.getPayload()).getOriginal()
: requestMessage;
}
So, if we delay message, we return null. The super class AbstractReplyProducingMessageHandler has this logic:
protected final void handleMessageInternal(Message<?> message) {
Object result;
if (this.advisedRequestHandler == null) {
result = handleRequestMessage(message);
}
else {
result = doInvokeAdvisedRequestHandler(message);
}
if (result != null) {
sendOutputs(result, message);
}
else if (this.requiresReply && !isAsync()) {
throw new ReplyRequiredException(message, "No reply produced by handler '" +
getComponentName() + "', and its 'requiresReply' property is set to true.");
}
else if (!isAsync()) {
logger.debug(LogMessage.format("handler '%s' produced no reply for request Message: %s", this, message));
}
}
so, it does emit that debug message when we no reply to produce. Well, it is there somewhere after delay.
I think it will be safe to mark a DelayHandler as an async component to avoid that debug message. Please, raise a GH issue and we will see what we can do to fix such a confusion. Meanwhile you can just don't use a DEBUG login level for framework categories.
I have a problem with getting a response body with a reactive spring. The service that I'm integrating with can have a different JSON body in response, so I cannot have a DTO that I can map the response to, so I was trying with String.
To check the solution for now I have mocked a simple server with SoapUi to return code 400 and JSON :
{
"errorCode" : "code",
"errorMessage" : "message"
}
and I would like to be able to log in in console in case of having an error, so here is part of my code:
.bodyValue(eventDataDto)
.exchange()
.doOnSuccess((response) -> {
if (response.rawStatusCode() != HttpStatus.OK.value()) {
throw new RuntimeException("Sending request failed with status: " + response.rawStatusCode() +
" with error message: " + response.bodyToMono(String.class);
}
})
.doOnError((throwable) -> {
throw new RuntimeException(throwable);
})
.block();
but the response that I'm getting is:
Sending request failed with status: 400 with error message: checkpoint("Body from POST http://localhost:8095/test/ [DefaultClientResponse]")
I was trying to use subscribe, map, and flatMap on that mono, but the only result was that I was getting a different class type on that error message, but nothing close to what I'm looking for.
I would appreciate any help with getting the response body in any form, that I could use its content.
EDIT WITH ANSWER:
A minute after posting it I have found a solution:
So I have tried with subscribe on mono:
.bodyValue(eventDataDto)
.exchange()
.doOnSuccess((response) -> {
if (response.rawStatusCode() != HttpStatus.OK.value()) {
Mono<String> bodyMono = response.bodyToMono(String.class);
bodyMono.subscribe(body -> {
throw new RuntimeException(
"Sending request failed with status: " + response.rawStatusCode() +
" with error message: " + body);
});
}
})
.doOnError((throwable) -> {
throw new RuntimeException(throwable);
})
.block();
I completly forgot that Mono will not give a result until subscribed.
You are receiving the proper result as your HTTP call succeeds (go-through) but returns a 400 ~ Bad Request response code which is a full concise HTTP response (which is the correct response you have configured as per your description in the OP).
In order to have the response body retrieved, either being the proper response body on a successful (200) or unsuccessful (400) response, you should use:
Either the toEntity operator
Or the bodyToMono one
Here down how your call chain would look like mapping the error response content using bodyToMono:
webClient.get()
.uri("some-uri")
.bodyValue(eventDataDto)
.exchange()
.flatMap((response) -> {
if (response.rawStatusCode() != HttpStatus.OK.value()) {
return response.bodyToMono(String.class)
.map(errorContent -> "Sending request failed with status: " + response.rawStatusCode() + " with error message: " + errorContent);
} else {
return response.bodyToMono(String.class);
})
.block();
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.
I have code:
SqlTemplate
.forQuery(client, "SELECT * FROM user WHERE id=#{id}")
.execute(parameters)
.onSuccess(users -> {
users.forEach(row -> {
// exception here
System.out.println(row.get(UUID.class, "id1") + " " + row.getString("title"));
});
})
What is the best way to handle exceptions in consumers?
For now if exception raises it will be swallowed...
Assuming you want to fail the flow when an exception is thrown, it is better to use compose to iterate through the users and handle .onSuccess() and .onFailure() separately.
You can use CompositeFuture to achieve this. You can have a Future list and add succeededFuture / failedFuture (in case of exception) to the list in the forEach loop.
SqlTemplate
.forQuery(client, "SELECT * FROM user WHERE id=#{id}")
.execute(parameters)
.compose(users -> {
List<Future> usersFuture = new ArrayList<>();
users.forEach(row -> {
try {
// exception here
System.out.println(row.get(UUID.class, "id1") + " " + row.getString("title"));
usersFuture.add(Future.succeededFuture());
} catch (Exception e) {
usersFuture.add(Future.failedFuture(e));
}
});
return CompositeFuture.all(usersFuture).mapEmpty();
})
.onSuccess(res -> {
// end the flow with success
})
.onFailure(e -> {
// Add error message and fail the flow
});
Also in general I'm curious about the exceptions that can be thrown here. As it is the data that you have written to your db after validations, you should be aware of the possible error scenarios and handle them accordingly without failing the flow.
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(´▽`)ノ