I make a request (any, authorization, registration, etc.) and only then I find out that I need to update the ACCESS-TOKEN, that is, I get the error 401.
Here is the authorization request:
BaseApplication.getApiClient()
.signIn(accessToken, body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<UserProfile>() {
#Override
public void onSubscribe(Disposable d) {
Log.d("-- SignInOnSubscribe", "Subscribed!");
}
#Override
public void onSuccess(UserProfile userProfile) {
if (userProfile.getErrorDetails() != null) {
onSignInFinishedCallback.onLoginFailure(userProfile.getErrorDetails());
Log.d("-- SignInOnError", userProfile.getErrorDetails());
} else {
onSignInFinishedCallback.onLoginSuccess(userProfile);
profileRepository.updateUserProfile(userProfile);
Log.d("-- SignInOnSuccess", userProfile.getName());
}
}
#Override
public void onError(Throwable e) {
Log.d("-- SignInOnError", e.getMessage());
if (e.getMessage().equals(Constants.CODE_UNAUTHORIZED)){
// Action on error 401
}
onSignInFinishedCallback.onLoginFailure(e.getMessage());
}
});
The API requests:
#POST("/api/login")
Single<UserProfile> getAccessToken(#Body Map<String, String> requestBody);
#POST("/api/abonent/login")
Single<UserProfile> signIn(#Header("X-ACCESS-TOKEN") String accessToken,
#Body Map<String, String> requestBody);
For example, the request for authorization is request 1, the request to receive TOKEN is query 2.
Question: How can I update TOKEN if I get an error in query 1 and after query 2 succeeds, back to do query 1?
I'm not sure how you receive the new token, since the return type of getAccessToken() is Single<UserProfile>. I suppose it should be Single<String> instead. Maybe this is not the case and you receive the token in a header or as a field of UserProfile. In either case, you can get an idea from the below solution and adjust it to your case.
The approach is that we create a new observable from your original one that uses a token store, which holds the most up-to-date token. We handle the 401 error using compose and onErrorResumeNext so that a token refresh request is made, the new token is saved to the token store, and the original request is retried with the new token this time.
For a more detailed explanation, see the comments in the code below:
public void signIn(final Map<String, String> body) {
Single
// Wrap the original request with a "defer" so that the access token is
// evaluated each time it is called. This is important because the refreshed
// access token should be used the second time around.
.defer(new Callable<SingleSource<UserProfile>>() {
#Override
public SingleSource<UserProfile> call() throws Exception {
return BaseApplication.getApiClient()
.signIn(accessTokenStore.getAccessToken(), body);
}
})
// Compose it with a transformer that refreshes the token in the token store and
// retries the original request, this time with the refreshed token.
.compose(retryOnNotAuthorized(body))
// The code remains the same from here.
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<UserProfile>() {
#Override
public void onSubscribe(Disposable d) {
Log.d("-- SignInOnSubscribe", "Subscribed!");
}
#Override
public void onSuccess(UserProfile userProfile) {
if (userProfile.getErrorDetails() != null) {
onSignInFinishedCallback.onLoginFailure(userProfile.getErrorDetails());
Log.d("-- SignInOnError", userProfile.getErrorDetails());
} else {
onSignInFinishedCallback.onLoginSuccess(userProfile);
profileRepository.updateUserProfile(userProfile);
Log.d("-- SignInOnSuccess", userProfile.getName());
}
}
#Override
public void onError(Throwable e) {
Log.d("-- SignInOnError", e.getMessage());
if (e.getMessage().equals(Constants.CODE_UNAUTHORIZED)) {
// Action on error 401
}
onSignInFinishedCallback.onLoginFailure(e.getMessage());
}
});
}
#NonNull
private SingleTransformer<UserProfile, UserProfile> retryOnNotAuthorized(final Map<String, String> body) {
return new SingleTransformer<UserProfile, UserProfile>() {
#Override
public SingleSource<UserProfile> apply(final Single<UserProfile> upstream) {
// We use onErrorResumeNext to continue our Single stream with the token refresh
// and the retrial of the request.
return upstream.onErrorResumeNext(new Function<Throwable, SingleSource<? extends UserProfile>>() {
#Override
public SingleSource<UserProfile> apply(Throwable throwable) throws Exception {
if (throwable instanceof HttpException
&& ((HttpException) throwable).code() == 401) {
return BaseApplication.getApiClient().getAccessToken(body)
// I always use doOnSuccess() for non-Rx side effects, such as caching the token.
// I think it's clearer than doing the caching in a map() or flatMap().
.doOnSuccess(new Consumer<String>() {
#Override
public void accept(String accessToken) throws Exception {
// Save the access token to the store for later use.
accessTokenStore.storeAccessToken(accessToken);
}
})
// We don't need the result of getAccessToken() any more, so I
// think it's cleaner to convert the stream to a Completable.
.toCompletable()
// After the token is refreshed and stored, the original request
// should be repeated.
.andThen(upstream);
}
// If the error was not 401, pass through the original error
return Single.error(throwable);
}
});
}
};
}
Update: The token store is just a regular interface with a get and a store method. You should implement it either as a POJO (storing the token in a field) or you could store the token in a shared preference so that the token survives app restarts.
Related
I calling to the api with the basic retrofit Call object:
public interface dataApi {
#GET("animal/cats")
Call<AllAnimals> getAllData(
#Query("api_key") String apiKey
);
}
And I can get the response inside my view model like this:
call.enqueue(new Callback<AllAnimals>() {
#Override
public void onResponse(Call<AllAnimals> call, Response<AllAnimals> response) {
animals.setValue(response.body());
}
#Override
public void onFailure(Call<AllAnimals> call, Throwable t) {
Log.i(TAG, "onFailure: " + t);
}
});
Nothing speical here.
I've several problem with this approach
FIRST - if I give the wrong api key for example, the response should give me a response with the code of the problem, instead I just get null body.
SECOND I am planning to have more api calls, and it's a huge code duplication to handle errors every call I wrote.
How can I implement custom error handling for this situation, that will be apply to other calls too?
I think you can use okhttp interceptor and define yourself ResponseBody converter to fix your problem.
First,intercept you interested request and response;
Second,check the response,if response is failed then modify the response body to empty。
define a simple interceptor
Interceptor interceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String url = request.url().toString();
System.out.println(request.url());
okhttp3.Response response = chain.proceed(request);
if (!response.isSuccessful() && url.contains("animal/cats")) {
// request failed begin to modify response body
response = response.newBuilder()
.body(ResponseBody.create(MediaType.parse("application/json"), new byte[] {}))
.build();
}
return response;
}
};
define self ResponseBody converter
most code from com.squareup.retrofit2:converter-jackson we just add two lines:
final class JacksonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final ObjectReader adapter;
JacksonResponseBodyConverter(ObjectReader adapter) {
this.adapter = adapter;
}
#Override public T convert(ResponseBody value) throws IOException {
try {
if (value.contentLength() == 0) {
return null;
}
return adapter.readValue(value.charStream());
} finally {
value.close();
}
}
}
the below code is added:
if (value.contentLength() == 0) {
return null;
}
I have the following endpoints in REST API:
public interface AutomoticzAPI {
#POST("/api/beacon_auth/login")
Single<LoginResponse> login(#Body LoginRequest request);
#GET("/api/system/ws_devices")
Single<WSDevicesResponse> wsDeviceList(#Header("Authorization") String tokenHeader);
}
When I call login endpoint, in response I recieve access token that I save into ClientSession holder object. Later I can retrieve token from ClientSession use to call server's protected resources:
api.login(ClientSession.getInstance().getLoginRequest(login, password))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(loginResponse -> {
String accessToken = loginResponse.getAccessToken();
ClientSession.getInstance().setAccessToken(accessToken);
view.onLoginSuccess();
}, throwable -> {
RetrofitException exception = (RetrofitException) throwable;
if (exception.getKind().equals(RetrofitException.Kind.HTTP)){
view.onLoginFailed(exception.getMessage());
} else if(exception.getKind().equals(RetrofitException.Kind.NETWORK))
{
view.onLoginFailed("Network error...");
} else {
view.onLoginFailed("Unknown error occurred...");
}
});
When I'm calling wsDeviceList endpoint, server could return 401 HTTP response code and json body with error code and message:
{
"code": "EXPIRED-TOKEN",
"message": "Token expired"
}
If that happens I want to call login endpoint once again to get new access token. Here is my code so far:
ClientSession clientSession = ClientSession.getInstance();
String token = "Bearer "+clientSession.getAccessToken();
String url = ClientSession.getInstance().getUrl();
AutomoticzAPI api = NetworkManager.getApiClient(url);
api.wsDeviceList(token)
.retryWhen(throwableFlowable -> throwableFlowable.flatMap(
new Function<Throwable, Publisher<?>>() {
#Override
public Publisher<?> apply(Throwable throwable) throws Exception {
RetrofitException exception = (RetrofitException) throwable;
if (exception.isUnauthorizedError()){
return relogin(api, clientSession.getLoginRequest());
}
return (Publisher<?>) throwable;
}
}
))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(wsDevicesResponse -> {
view.onDeviceListLoaded(wsDevicesResponse.getWsdevices());
}, throwable -> {
RetrofitException exception = (RetrofitException) throwable;
view.onError(exception);
});
}
public Publisher<?> relogin(AutomoticzAPI api, LoginRequest loginRequest){
return (Publisher<?>) api.login(loginRequest)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(loginResponse -> {
String accessToken = loginResponse.getAccessToken();
ClientSession.getInstance().setAccessToken(accessToken);
}, throwable -> {
RetrofitException exception = (RetrofitException) throwable;
view.onError(exception);
});
}
But when relogin method gets executed my program crashes.
I'm not proficient in RxJava and probably doing this wrong. How I can make recall login to refresh access token and then call wsDeviceList once again?
Use Authenticator API of retrofit and inside this call access token api to get access token and re try the fail API call using this access token.
My Goal is to receive some token from downstream server response headers by using ServerHttpResponseDecorator without this I am not able to get response headers in GlobalFilter. based on token I am planning to alter downstream response by raising a custom exception and handled in ErrorWebExceptionHandler.
The problem is once I have read the response headers from downstream service even exception also not able to stop the flow I am getting an original response whatever is coming from downstream service but if I raised an exception before headers reading It is working as expected.
GlobalFilter Sample code
#Component
public class CustomFilter implements GlobalFilter, Ordered {
#Override
public int getOrder() {
return -2;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
#Override
public HttpHeaders getHeaders() {
String tokenFromHeader = super.getHeaders().getFirst("TOKEN");
String regIdFromHeader = super.getHeaders().getFirst("regId");
if (false) { // if (true) { It is hadled by exception handler as expected
// I have some Buginese logic here
throw new RuntimeException();
}
if (tokenFromHeader != null && regIdFromHeader != null) {
if (true) {
//I have some Buginese logic here
// No use I am getting original response from down streams
throw new RuntimeException();
}
}
return getDelegate().getHeaders();
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
}
Exception Handler
public class MyWebExceptionHandler implements ErrorWebExceptionHandler {
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
byte[] bytes = ( "Some custom text").getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
exchange.getResponse().getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return exchange.getResponse().writeWith(Flux.just(buffer));
}
}
Expected out put is
Some custom text
But I am getting an original response
Working on a backend with Spring (Java) and Firebase. We are using the Firebase tokens (appended as an authentication header) to identify the user, using the built in UID.
Unfortunately, extracting this UID from the token must be done asynchronously, so I can only get the token from the onSuccess callback.
To serve a response, I must return an object from the below deleteUser method, however I cannot know what the response will be until I get a success/failure callback!
I can imagine a way to do this by waiting on a flag which is set my the callback, or with some messy timing, but I'm wondering if there is a clean way of handling this without introducing race conditions or lots of extra code. Can anyone help?
Request Mapping (handles request, serves response)
#RequestMapping(value = "/users", method = RequestMethod.DELETE)
public #ResponseBody String deleteUser(#RequestHeader("Authentication") String token) {
FirebaseUtil.getUid(token, new OnSuccessListener<FirebaseToken>() {
#Override
public void onSuccess(FirebaseToken decodedToken) {
String uid = decodedToken.getUid();
//RETURN SUCCESSFUL HERE
}
}, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
//RETURN FAILURE HERE
}
});
//MUST RETURN SOMETHING HERE?
User userToDelete = userDao.get(uid); //DONT HAVE THE uid HERE
userDao.delete(uid);
clearUserAccounts(userToDelete);
return uid + " was deleted";
}
FirebaseUtil.getUid()
public static void getUid(String token, OnSuccessListener<FirebaseToken> successListener, OnFailureListener failureListener) {
FirebaseAuth.getInstance()
.verifyIdToken(token)
.addOnSuccessListener(successListener)
.addOnFailureListener(failureListener);
}
While there are ways to block the thread until the asynchronous request finishes, there is a simple and more resource-effective solution since Spring 3.2.
You can use DeferredResult<T> as your return type to enable asynchronous processing. This allows the servlet container to reuse the HTTP worker thread right away, while sparing you the headache of forcefully serializing a chain of asynchronous requests.
By filling out the comments, your code would look like this:
#RequestMapping(value = "/users", method = RequestMethod.DELETE)
public DeferredResult<String> deleteUser(#RequestHeader("Authentication") String token) {
final DeferredResult<String> result = new DeferredResult<>();
FirebaseUtil.getUid(token, new OnSuccessListener<FirebaseToken>() {
#Override
public void onSuccess(FirebaseToken decodedToken) {
String uid = decodedToken.getUid();
User userToDelete = userDao.get(uid);
userDao.delete(uid);
clearUserAccounts(userToDelete);
result.setResult(uid + " was deleted");
}
}, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
result.setErrorResult(e);
}
});
return result;
}
Hi folks I'm creating an android application's login/register part using the Android Volley Library. My application was working well, but the UI and logic were at the same class. So, I have separated them into two classes. My app makes requests to my NodeJS server using POST methods and gets JSON response. So I have tried to keep the POST request function in another class.
After separating the classes, I have a problem while waiting for response. Here is the function;
public String doWebRequestLogin(Context context, boolean checkLoginForm, final Map<String,String> json){
result[0] = "FREE";
this.context = context;
if(checkLoginForm){
StringRequest post = new StringRequest(Request.Method.POST, loginUrl, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
try {
Log.d("Login Response: ",response);
data = response;
res = new JSONObject(data);
if (res.getString(KEY_SUCCESS) != null) {
int success = Integer.parseInt(res.getString(KEY_SUCCESS));
if (success == 1) {
result[0] = "LOGGED";
} else if (success == 0) {
result[0] = "LOGIN ERROR";
} else {
result[0] = "INVALID POST";
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.d("Response Error", error.toString());
result[0] = "INVALID POST";
}
}){
#Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> map = json;
return map;
}
};
VolleyController.getInstance(this.context).getRequestQueue().add(post);
}
return result[0];
}
This function returns result[0] as "FREE" at every time due to response time. How could it wait for the response and set result[0] according to the response? I need to know what happened while making requests.
I'm calling doWebRequestLogin() on the UI within an onclick function
Then you do NOT want to "wait for the response". That will freeze your UI for however long the network I/O takes, and your users will... be unimpressed.
Instead, update your UI in the onResponse() and onErrorResponse() methods.
This sort of asynchronous call, handling the results via callbacks, is core to the event-driven programming model at the heart of Android.
The request is asynchronous and you must not block the main thread waiting for a response. Make the method void and use a callback to handle the response once it's received.
public void doWebRequestLogin(SomeCallback callback, Context context, boolean checkLoginForm, final Map<String,String> json){
[...]
if (res.getString(KEY_SUCCESS) != null) {
int success = Integer.parseInt(res.getString(KEY_SUCCESS));
callback.someMethod(success);
}
}
For the callback:
public interface SomeCallback{
void someMethod(int result); // response received, handle it
}
Callback may also have a return type or be generic, this depends solely on your needs...