I have a ViewPager and three webservice calls are made when ViewPager is loaded simultaneously.
When first one returns 401, Authenticator is called and I refresh the token inside Authenticator, but remaining 2 requests are already sent to the server with old refresh token and fails with 498 which is captured in Interceptor and app is logged out.
This is not the ideal behaviour I would expect. I would like to keep the 2nd and 3rd request in the queue and when the token is refreshed, retry the queued request.
Currently, I have a variable to indicate if token refresh is ongoing in Authenticator, in that case, I cancel all subsequent request in the Interceptor and user has to manually refresh the page or I can logout the user and force user to login.
What is a good solution or architecture for the above problem using okhttp 3.x for Android?
EDIT: The problem I want to solve is in general and I would not like to sequence my calls. i.e. wait for one call to finish and refresh the token and then only send rest of the request on the activity and fragment level.
Code was requested. This is a standard code for Authenticator:
public class CustomAuthenticator implements Authenticator {
#Inject AccountManager accountManager;
#Inject #AccountType String accountType;
#Inject #AuthTokenType String authTokenType;
#Inject
public ApiAuthenticator(#ForApplication Context context) {
}
#Override
public Request authenticate(Route route, Response response) throws IOException {
// Invaidate authToken
String accessToken = accountManager.peekAuthToken(account, authTokenType);
if (accessToken != null) {
accountManager.invalidateAuthToken(accountType, accessToken);
}
try {
// Get new refresh token. This invokes custom AccountAuthenticator which makes a call to get new refresh token.
accessToken = accountManager.blockingGetAuthToken(account, authTokenType, false);
if (accessToken != null) {
Request.Builder requestBuilder = response.request().newBuilder();
// Add headers with new refreshToken
return requestBuilder.build();
} catch (Throwable t) {
Timber.e(t, t.getLocalizedMessage());
}
}
return null;
}
}
Some questions similar to this:
OkHttp and Retrofit, refresh token with concurrent requests
You can do this:
Add those as data members:
// these two static variables serve for the pattern to refresh a token
private final static ConditionVariable LOCK = new ConditionVariable(true);
private static final AtomicBoolean mIsRefreshing = new AtomicBoolean(false);
and then on the intercept method:
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
// 1. sign this request
....
// 2. proceed with the request
Response response = chain.proceed(request);
// 3. check the response: have we got a 401?
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
if (!TextUtils.isEmpty(token)) {
/*
* Because we send out multiple HTTP requests in parallel, they might all list a 401 at the same time.
* Only one of them should refresh the token, because otherwise we'd refresh the same token multiple times
* and that is bad. Therefore we have these two static objects, a ConditionVariable and a boolean. The
* first thread that gets here closes the ConditionVariable and changes the boolean flag.
*/
if (mIsRefreshing.compareAndSet(false, true)) {
LOCK.close();
/* we're the first here. let's refresh this token.
* it looks like our token isn't valid anymore.
* REFRESH the actual token here
*/
LOCK.open();
mIsRefreshing.set(false);
} else {
// Another thread is refreshing the token for us, let's wait for it.
boolean conditionOpened = LOCK.block(REFRESH_WAIT_TIMEOUT);
// If the next check is false, it means that the timeout expired, that is - the refresh
// stuff has failed.
if (conditionOpened) {
// another thread has refreshed this for us! thanks!
// sign the request with the new token and proceed
// return the outcome of the newly signed request
response = chain.proceed(newRequest);
}
}
}
}
// check if still unauthorized (i.e. refresh failed)
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
... // clean your access token and prompt for request again.
}
// returning the response to the original request
return response;
}
In this way you will only send 1 request to refresh the token and then for every other you will have the refreshed token.
It is important to note, that accountManager.blockingGetAuthToken (or the non-blocking version) could still be called somewhere else, other than the interceptor. Hence the correct place to prevent this issue from happening would be within the authenticator.
We want to make sure that the first thread that needs an access token will retrieve it, and possible other threads should just register for a callback to be invoked when the first thread finished retrieving the token.
The good news is, that AbstractAccountAuthenticator already has a way of delivering asynchronous results, namely AccountAuthenticatorResponse, on which you can call onResult or onError.
The following sample consists of 3 blocks.
The first one is about making sure that only one thread fetches the access token while other threads just register their response for a callback.
The second part is just a dummy empty result bundle. Here, you would load your token, possibly refresh it, etc.
The third part is what you do once you have your result (or error). You have to make sure to call the response for every other thread that might have registered.
boolean fetchingToken;
List<AccountAuthenticatorResponse> queue = null;
#Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
synchronized (this) {
if (fetchingToken) {
// another thread is already working on it, register for callback
List<AccountAuthenticatorResponse> q = queue;
if (q == null) {
q = new ArrayList<>();
queue = q;
}
q.add(response);
// we return null, the result will be sent with the `response`
return null;
}
// we have to fetch the token, and return the result other threads
fetchingToken = true;
}
// load access token, refresh with refresh token, whatever
// ... todo ...
Bundle result = Bundle.EMPTY;
// loop to make sure we don't drop any responses
for ( ; ; ) {
List<AccountAuthenticatorResponse> q;
synchronized (this) {
// get list with responses waiting for result
q = queue;
if (q == null) {
fetchingToken = false;
// we're done, nobody is waiting for a response, return
return null;
}
queue = null;
}
// inform other threads about the result
for (AccountAuthenticatorResponse r : q) {
r.onResult(result); // return result
}
// repeat for the case another thread registered for callback
// while we were busy calling others
}
}
Just make sure to return null on all paths when using the response.
You could obviously use other means to synchronize those code blocks, like atomics as shown by #matrix in another response. I made use of synchronized, because I believe this to be the easiest to grasp implementation, since this is a great question and everyone should be doing this ;)
The above sample is an adapted version of an emitter loop described here, where it goes into great detail about concurrency. This blog is a great source if you're interested in how RxJava works under the hood.
You can try with this application level interceptor
private class HttpInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//Build new request
Request.Builder builder = request.newBuilder();
builder.header("Accept", "application/json"); //if necessary, say to consume JSON
String token = settings.getAccessToken(); //save token of this request for future
setAuthHeader(builder, token); //write current token to request
request = builder.build(); //overwrite old request
Response response = chain.proceed(request); //perform request, here original request will be executed
if (response.code() == 401) { //if unauthorized
synchronized (httpClient) { //perform all 401 in sync blocks, to avoid multiply token updates
String currentToken = settings.getAccessToken(); //get currently stored token
if(currentToken != null && currentToken.equals(token)) { //compare current token with token that was stored before, if it was not updated - do update
int code = refreshToken() / 100; //refresh token
if(code != 2) { //if refresh token failed for some reason
if(code == 4) //only if response is 400, 500 might mean that token was not updated
logout(); //go to login screen
return response; //if token refresh failed - show error to user
}
}
if(settings.getAccessToken() != null) { //retry requires new auth token,
setAuthHeader(builder, settings.getAccessToken()); //set auth token to updated
request = builder.build();
return chain.proceed(request); //repeat request with new token
}
}
}
return response;
}
private void setAuthHeader(Request.Builder builder, String token) {
if (token != null) //Add Auth token to each request if authorized
builder.header("Authorization", String.format("Bearer %s", token));
}
private int refreshToken() {
//Refresh token, synchronously, save it, and return result code
//you might use retrofit here
}
private int logout() {
//logout your user
}
}
You can set interceptor like this to okHttp instance
Gson gson = new GsonBuilder().create();
OkHttpClient httpClient = new OkHttpClient();
httpClient.interceptors().add(new HttpInterceptor());
final RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(BuildConfig.REST_SERVICE_URL)
.setClient(new OkClient(httpClient))
.setConverter(new GsonConverter(gson))
.setLogLevel(RestAdapter.LogLevel.BASIC)
.build();
remoteService = restAdapter.create(RemoteService.class);
Hope this helps!!!!
I found the solution with authenticator, the id is the number of the request, only for identification. Comments are in Spanish
private final static Lock locks = new ReentrantLock();
httpClient.authenticator(new Authenticator() {
#Override
public Request authenticate(#NonNull Route route,#NonNull Response response) throws IOException {
Log.e("Error" , "Se encontro un 401 no autorizado y soy el numero : " + id);
//Obteniendo token de DB
SharedPreferences prefs = mContext.getSharedPreferences(
BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String token_db = prefs.getString("refresh_token","");
//Comparando tokens
if(mToken.getRefreshToken().equals(token_db)){
locks.lock();
try{
//Obteniendo token de DB
prefs = mContext.getSharedPreferences(
BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String token_db2 = prefs.getString("refresh_token","");
//Comparando tokens
if(mToken.getRefreshToken().equals(token_db2)){
//Refresh token
APIClient tokenClient = createService(APIClient.class);
Call<AccessToken> call = tokenClient.getRefreshAccessToken(API_OAUTH_CLIENTID,API_OAUTH_CLIENTSECRET, "refresh_token", mToken.getRefreshToken());
retrofit2.Response<AccessToken> res = call.execute();
AccessToken newToken = res.body();
// do we have an access token to refresh?
if(newToken!=null && res.isSuccessful()){
String refreshToken = newToken.getRefreshToken();
Log.e("Entra", "Token actualizado y soy el numero : " + id + " : " + refreshToken);
prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
prefs.edit().putBoolean("log_in", true).apply();
prefs.edit().putString("access_token", newToken.getAccessToken()).apply();
prefs.edit().putString("refresh_token", refreshToken).apply();
prefs.edit().putString("token_type", newToken.getTokenType()).apply();
locks.unlock();
return response.request().newBuilder()
.header("Authorization", newToken.getTokenType() + " " + newToken.getAccessToken())
.build();
}else{
//Dirigir a login
Log.e("redirigir", "DIRIGIENDO LOGOUT");
locks.unlock();
return null;
}
}else{
//Ya se actualizo tokens
Log.e("Entra", "El token se actualizo anteriormente, y soy el no : " + id );
prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String type = prefs.getString("token_type","");
String access = prefs.getString("access_token","");
locks.unlock();
return response.request().newBuilder()
.header("Authorization", type + " " + access)
.build();
}
}catch (Exception e){
locks.unlock();
e.printStackTrace();
return null;
}
}
return null;
}
});
Related
I'm using nodeJS in the back-end environment and most of APIs are ready. I want to use Vaading in the front-end, because of the Java Spreadsheet component. I have looked at the Bakery example and designed a login page which sends the user credentials to my back-end API. This is the code for sending the POST request to the server.
public void submit(String username, String password) throws IOException {
JSONObject json = new JSONObject();
json.put("username", username);
json.put("password", password);
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
try {
HttpPost request = new HttpPost(URL);
HttpResponse response;
StringEntity params = new StringEntity(json.toString());
request.addHeader("content-type", "application/json");
request.setEntity(params);
response = httpClient.execute(request);
if(response.getStatusLine().getStatusCode() == 200) {
System.out.println("Okey!");
// The response contains token. How can I store this token?
// Also, How can I use the stored token for future Authentication:Bearer?
}
}
catch(Exception ex) {
System.out.println(ex);
}
finally {
httpClient.close();
}
}
I would like to store the response's token in somewhere(I can store it in the cookie object in React. However, I'm not familiar with this language) and fetch the token from this storage(cookie possibly, how to implement Cookie in Java web?) every time I make a request.
Which version of Vaadin do you use? If you are using Vaadin >= 10: How about using the session?
You could save the token in the VaadinSession and read it from there on every request.
I created a minimum example where (on button click) I check if a token is already saved in the session. If yes it is printed in a notification. If not it is saved into the session. The code looks like:
#Route("")
public class MainView extends VerticalLayout {
public MainView() {
Button button = new Button("Click me", event -> {
Object token = VaadinSession.getCurrent().getAttribute("token");
if (token == null) {
Notification.show("No token found in session. Now storing token = 123456");
VaadinSession.getCurrent().setAttribute("token", "123456");
} else {
Notification.show("Found token in session: " + token.toString());
}
});
add(button);
}
}
The result looks like:
(button was clicked three times)
Update: The same can be used in Vaadin 8 where the code of a minimum example then looks like:
#Theme("mytheme")
public class MyUI extends UI {
#Override
protected void init(VaadinRequest vaadinRequest) {
final VerticalLayout layout = new VerticalLayout();
Button button = new Button("Click me", event -> {
Object token = VaadinSession.getCurrent().getAttribute("token");
if (token == null) {
Notification.show("No token found in session. Now storing token = 123456");
VaadinSession.getCurrent().setAttribute("token", "123456");
} else {
Notification.show("Found token in session: " + token.toString());
}
});
layout.addComponents(button);
setContent(layout);
}
#WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
#VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
public static class MyUIServlet extends VaadinServlet {
}
}
Is there any way to execute multiple requests in sequence in Retrofit?
These requests uses same Java interface and differ only by parameters they take which are contained in ArrayList.
For requests A1, A2, A3, A4, A5...... An
Hit A1,
onResponse() of A1 is called
Hit A2,
onResponse() of A2 is called
Hit A3
.
.
.
.
.
.
onResponse() of An is called.
The problem can be easily solved with RxJava.
Assuming you have a retrofit Api class, that returns a Completable:
interface Api {
#GET(...)
fun getUser(id: String): Completable
}
Then you can perform this:
// Create a stream, that emits each item from the list, in this case "param1" continued with "param2" and then "param3"
Observable.fromIterable(listOf("param1", "param2", "param3"))
// we are converting `Observable` stream into `Completable`
// also we perform request here: first time parameter `it` is equal to "param1", so a request is being made with "param1"
// execution will halt here until responce is received. If response is successful, only then a call with second param ("param2") will be executed
// then the same again with "param3"
.flatMapCompletable { api.getUser(it) }
// we want request to happen on a background thread
.subscribeOn(Schedulers.io())
// we want to be notified about completition on UI thread
.observeOn(AndroidSchedulers.mainThread())
// here we'll get notified, that operation has either successfully performed OR failed for some reason (specified by `Throwable it`)
.subscribe({ println("completed") }, { println(it.message) })
If your retrofit API does not return a Completable, then change api.getUser(it) to api.getUser(it).toCompletable().
You can do this easily by using zip function in Rx (For ex: each request in retrofit 2 return Observable<Object>). It will run request sequentially. You can try my codes below:
public Observable buildCombineObserverable() {
List<Observable<Object>> observables = new ArrayList<>();
for (int i = 0; i < number_of_your_request; i++) {
observables.add(your_each_request_with_retrofit);
}
return Observable.zip(observables, new FuncN<Object>() {
#Override
public Object call(Object... args) {
return args;
}
});
}
You can subscribe this Observable and get data from all requests. Zip function will do request sequentially but zip data and return data as Object...
Ok, I don't know about Retrofit, I use loopj's library, but the concept is the same. they both have a method for success and a method for failure. so here is my general suggestion:
ArrayList<MyRequest> requests = new ArrayList<>();
int numberOfRequests = 10;
JSONObject params = null;
try{
params = new JSONObject("{\"key\":\"value\"}");
}catch(JSONException e){
e.printStackTrace();
}
MyRequest firstRequest = new MyRequest();
requests.add(firstRequest);
for(int i = 0; i < numberOfRequests; i++){
MyRequest myRequest = new MyRequest();
requests.get(requests.size() - 1).addNextRequest(myRequest);
myRequest.addPreviousRequest(requests.get(requests.size() - 1));
//don't invoke sendRequest before addNextRequest
requests.get(requests.size() - 1).sendRequest(params, "example.com", App.context);
requests.add(myRequest);
}
requests.get(requests.size() - 1).sendRequest(params, "example.com", App.context);
and the MyRequest class:
import android.content.Context;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import org.json.JSONObject;
import cz.msebera.android.httpclient.Header;
import cz.msebera.android.httpclient.entity.StringEntity;
public class MyRequest{
private Object result, nextRequestsResult;
private MyRequest nextRequest, previousRequest;
public void addNextRequest(MyRequest nextRequest){
this.nextRequest = nextRequest;
}
public void addPreviousRequest(MyRequest previousRequest){
this.previousRequest = previousRequest;
}
public void sendRequest(JSONObject parameters, String url, Context ctx){
AsyncHttpClient mClient = new AsyncHttpClient();
StringEntity entity = new StringEntity(parameters.toString(), "UTF-8");
String contentType = "application/json";
mClient.post(ctx, url, entity, contentType,
new AsyncHttpResponseHandler(){
private void sendResult(Object... results){
MyRequest.this.result = results;
if(previousRequest != null){
if(nextRequest != null){
if( nextRequestsResult != null){
previousRequest.onResult(results, nextRequestsResult);
}else{
//next request's result is not ready yet
//so we don't do anything here. When nextRequestsResult
//gets ready, it will invoke this request's onResult
}
}else {
//nextRequest == null means this the last request
previousRequest.onResult(results);
}
}else{
//previousRequest == null means this is the first request
if(nextRequest != null){
if(nextRequestsResult != null){
previousRequest.onResult(results, nextRequestsResult);
}else{
//next request's result is not ready yet
//so we don't do anything here. When nextRequestsResult
//gets ready, it will invoke this request's onResult
}
}else{
//next request and previous request are null so it means
//this is the only request, so this is the final destination
doFinalJobWithResults(results);
}
}
}
#Override
public void onSuccess(final int statusCode, final Header[] headers,
final byte[] responseBody){
sendResult(responseBody, true, null, false);//whatever
}
#Override
public void onFailure(final int statusCode, final Header[] headers,
final byte[] responseBody,
final Throwable error){
sendResult(responseBody, error);//or just sendResult();
}
});
}
/**
This method should be invoked only by next request
#param nextRequestsResult
results of the next request which this request is expecting.
*/
private void onResult(Object... nextRequestsResult){
this.nextRequestsResult = nextRequestsResult;
//do whatever you want with the result of next requests here
if(previousRequest != null){
if(result != null){
previousRequest.onResult(result, this.nextRequestsResult);
}
}else{
//if it doesn't have previous request then it means this is the first request
//so since this method gets invoked only by next request then it means
//all of the next requests have done their job and this is the final destination
if(nextRequestsResult != null){
if(this.result != null){
doFinalJobWithResults(nextRequestsResult, this.result);
}
}
}
}
private void doFinalJobWithResults(Object... results){
//whatever
}
}
It's a general purpose class, you can send hundreds of network requests simultaneously but their results will be processed in sequence.
This way for example 100 requests will be sent to the server but it takes the time of one request to get all responses of them and process.
I haven't tested this code at all, it may have some bugs and mistakes, I wrote it just for this question only to give an idea.
We're using stormpath with Java & also trying to combine form Login with REST API authentication on the same application.
I've setup stormpath servlet plugin as described here https://docs.stormpath.com/java/servlet-plugin/quickstart.html... This works very fine.
Now, on the same application, we have APIs where I've implemented oAuth authentication with stormpath see here http://docs.stormpath.com/guides/api-key-management/
The first request for an access-token works fine by sending Basic Base64(keyId:keySecret) in the request header and grant_type = client_credentials in the body. Access tokens are being returned nicely. However trying to authenticate subsequent requests with the header Bearer <the-obtained-access-token> does not even hit the application before
returning the following json error message...
{
"error": "invalid_client",
"error_description": "access_token is invalid."
}
This is confusing because I've set breakpoints all over the application and I'm pretty sure that the API request doesn't hit the anywhere within the application before stormpath kicks in and returns this error. And even if stormpath somehow intercepts the request before getting to the REST interface, this message doesn't make any sense to me because i'm certainly making the subsequent API calls with a valid access-token obtained from the first call to get access-token.
I have run out of ideas why this could be happening but i'm suspecting that it may have something to do with stormpath config especially with a combination
of form Login/Authentication for web views and oAuth Athentication for REST endpoints. With that said, here's what my stormpath.properties looks like. Hope this could help point at anything I may be doing wrong.
stormpath.application.href=https://api.stormpath.com/v1/applications/[app-id]
stormpath.web.filters.authr=com.app.security.AuthorizationFilter
stormpath.web.request.event.listener = com.app.security.AuthenticationListener
stormpath.web.uris./resources/**=anon
stormpath.web.uris./assets/**=anon
stormpath.web.uris./v1.0/**=anon
stormpath.web.uris./** = authc,authr
stormpath.web.uris./**/**=authc,authr
Help with this would be highly appreciated.
The problem might be related to an incorrect request.
Is it possible for you to try this code in your app?:
private boolean verify(String accessToken) throws OauthAuthenticationException {
HttpRequest request = createRequestForOauth2AuthenticatedOperation(accessToken);
AccessTokenResult result = Applications.oauthRequestAuthenticator(application)
.authenticate(request);
System.out.println(result.getAccount().getEmail() + " was successfully verified, you can allow your protect operation to continue");
return true;
}
private HttpRequest createRequestForOauth2AuthenticatedOperation(String token) {
try {
Map<String, String[]> headers = new LinkedHashMap<String, String[]>();
headers.put("Accept", new String[]{"application/json"});
headers.put("Authorization", new String[]{"Bearer " + token});
HttpRequest request = HttpRequests.method(HttpMethod.GET)
.headers(headers)
.build();
return request;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
I've prepared an example that demonstrates oauth token creation as well as authorized access to protected pages using access tokens.
It builds off of the servlet example in the Stormpath SDK. The repo can be found here: https://github.com/stormpath/stormpath-java-oauth-servlet-sample
It demonstrates running a servlet application and having an out-of-band program get and use oauth tokens to access protected resources.
The core of the oauth part is in TokenAuthTest.java:
public class TokenAuthTest {
public static void main(String[] args) throws Exception {
String command = System.getProperty("command");
if (command == null || !("getToken".equals(command) || "getPage".equals(command))) {
System.err.println("Must supply a command:");
System.err.println("\t-Dcommand=getToken OR");
System.err.println("\t-Dcommand=getPage OR");
System.exit(1);
}
if ("getToken".equals(command)) {
getToken();
} else {
getPage();
}
}
private static final String APP_URL = "http://localhost:8080";
private static final String OAUTH_URI = "/oauth/token";
private static final String PROTECTED_URI = "/dashboard";
private static void getToken() throws Exception {
String username = System.getProperty("username");
String password = System.getProperty("password");
if (username == null || password == null) {
System.err.println("Must supply -Dusername=<username> -Dpassword=<password> on the command line");
System.exit(1);
}
PostMethod method = new PostMethod(APP_URL + OAUTH_URI);
method.setRequestHeader("Origin", APP_URL);
method.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
method.addParameter("grant_type", "password");
method.addParameter("username", username);
method.addParameter("password", password);
HttpClient client = new HttpClient();
client.executeMethod(method);
BufferedReader br = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
String readLine;
while(((readLine = br.readLine()) != null)) {
System.out.println(readLine);
}
}
private static void getPage() throws Exception {
String token = System.getProperty("token");
if (token == null) {
System.err.println("Must supply -Dtoken=<access token> on the command line");
System.exit(1);
}
GetMethod method = new GetMethod(APP_URL + PROTECTED_URI);
HttpClient client = new HttpClient();
System.out.println("Attempting to retrieve " + PROTECTED_URI + " without token...");
int returnCode = client.executeMethod(method);
System.out.println("return code: " + returnCode);
System.out.println();
System.out.println("Attempting to retrieve " + PROTECTED_URI + " with token...");
method.addRequestHeader("Authorization", "Bearer " + token);
returnCode = client.executeMethod(method);
System.out.println("return code: " + returnCode);
}
}
I use this to config my retrofit:
RestAdapter restAdapter = new RestAdapter.Builder()
//add headers to requests
.setRequestInterceptor(getAuthenticatedRequestInterceptor())
.setEndpoint(BASE_URL)
.setConverter(new GsonConverter(getGson()))
.build();
and The getAuthenticatedRequestInterceptor() method adds headers to request:
public AccountRequestInterceptor getAuthenticatedRequestInterceptor() {
AccountRequestInterceptor interceptor = new AccountRequestInterceptor();
Map<String, String> headers = new HashMap<>();
String accessToken = null;
try {
accessToken = TokenProvider.getInstance(mContext).getToken();
} catch (InterruptedException e) {
}
headers.put(HeadersContract.HEADER_AUTHONRIZATION, O_AUTH_AUTHENTICATION + accessToken);
interceptor.setHeader(headers);
return interceptor;
}
getToken() method is:
private synchronized string getToken() throws InterruptedException {
if (!isRefreshing()) {
//This is very important to call notify() on the same object that we call wait();
final TokenProvider myInstance = this;
setRefreshing(true);
MyApplication.getRestClient().getAccountService().getRefreshedToken(mLoginData.getRefreshToken())
.subscribe(new Observer<LoginResponse>() {
#Override
public void onCompleted() {
synchronized (myInstance) {
setRefreshing(false);
myInstance.notifyAll();
}
}
#Override
public void onError(Throwable e) {
synchronized (myInstance) {
setRefreshing(false);
myInstance.notifyAll();
}
}
#Override
public void onNext(LoginResponse loginResponse) {
synchronized (myInstance) {
mLoginData = loginResponse;
mAccountProvider.saveLoginData(loginResponse);
myInstance.notifyAll();
}
}
});
}
this.wait();
return mLoginData.getToken();
}
The TokenProvider.getInstance(mContext).getToken() has a wait() on main thread to get the response from an async method and i know that is a bad thing to do but i need this here to wait for the response to take the token from it and then return the token.how can i do this in a separate thread to avoid waiting on the main thread?
Note:
1 - that this is called before any request with retrofit.
2 - I read this and i know i can refresh token after a fail request, but for business reasons i want to avoid having an invalid token.
3 - I call MyApplication.getRestClient().getAccountService().login(loginRequest,callback...) in my Activity and before adding token everything happened in background thread. so I want to use my token and do not block the main thread.
UPDATE: I added the following Interceptor to my new OkHttp:
public class RequestTokenInterceptor implements Interceptor {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Request newRequest;
try {
Log.d("addHeader", "Before");
String token = TokenProvider.getInstance(mContext).getToken();
if (token != null) {
newRequest = request.newBuilder()
.addHeader("Bearer", token)
.build();
} else {
// I want to cancel the request or raise an exception to catch it in onError method
// of retrofit callback.
}
} catch (InterruptedException e) {
Log.d("addHeader", "Error");
e.printStackTrace();
return chain.proceed(request);
}
Log.d("addHeader", "after");
return chain.proceed(newRequest);
}
}
Now how can i cancel the request or raise an exception to catch it in onError method of retrofit callback, if token is null?
It's a little bit strange issue but let me try to help you. :)
As you know you can refresh token after a failed request with retrofit using response interceptor.
Let's try to use interceptor before request.
public class RequestTokenInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Here where we'll try to refresh token.
// with an retrofit call
// After we succeed we'll proceed our request
Response response = chain.proceed(request);
return response;
}
}
And when you're creating your api create a new HttpClient:
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new RequestTokenInterceptor());
And add your http client to your adapter like below:
.setClient(new OkClient(client))
If this works, before every request you'll try to refresh token first and then will proceed your api request. So in ui there'll be no difference with your normal api calls.
Edit:
I'm editing my answer too. If you want to return an error in else case if token null, in else case you can create your custom response:
private Response(Builder builder) {
this.request = builder.request;
this.protocol = builder.protocol;
this.code = builder.code;
this.message = builder.message;
this.handshake = builder.handshake;
this.headers = builder.headers.build();
this.body = builder.body;
this.networkResponse = builder.networkResponse;
this.cacheResponse = builder.cacheResponse;
this.priorResponse = builder.priorResponse;
}
or simply you can return a null response. if you build your custom response and set your code not to 200 such as 401 or 400+ you'll receive that response in Retrofit's callbacks failure method. Than you can do what ever you want.
If you return null you'll get a RuntimeException i think and still you can catch response in your callback's failure method.
After you create your own response in else you can create your custom callback and catch your null response and transform your custom error how ever you want like below:
public abstract class DefaultRequestCallback<T> implements Callback<T> {
public abstract void failure(YourCustomException ex);
public abstract void success(T responseBean);
#Override
public void success(T baseResponseBean, Response response) {
if (response == null) {
// Here we catch null response and transform it to our custom Exception
failure(new YourCustomException());
}
} else {
success(baseResponseBean);
}
}
#Override
public void failure(RetrofitError error) {
// Here's your failure method.
// Also you can transform default retrofit errors to your customerrors
YourCustomException ex = new YourCustomException();
failure(ex);
}
}
This can help you i think.
Edit 2:
You can build a new Response like below. There's a builder pattern in Retrofit's Response class. You can check it from there.
Response response = new Response.Builder().setCode(401).setMessage("Error Message").build();
You could make all long actions in AsyncTask doInBackground method, while in onPre- and onPostExecute you could show/hide some progress bars when user is waiting
Ok, I think if you are calling your getAuthenticatedRequestInterceptor() on the main thread and which in turns call getInstance(),in which i feel you would be creating an object of Type TokenProvider hence when you create this object in the main thread your object.wait() runs on main thread hence to run this on a background thread probably modify your getAuthenticatedRequestInterceptor() method to execute the following lines in a new thread.
try {
accessToken = TokenProvider.getInstance(mContext).getToken();
} catch (InterruptedException e) {
}
headers.put(HeadersContract.HEADER_AUTHONRIZATION, O_AUTH_AUTHENTICATION + accessToken);
interceptor.setHeader(headers);
return interceptor;
but this will have problems for notifying your RestAdapter as the main thread will proceed executing, hence i would suggest
you call getAuthenticatedRequestInterceptor() method first in a new thread and then notify your main thread to build your RestAdapter.This will free your main thread but with the strategy you are employing you will have to wait until you receive the token to make any calls.
I'm building an Android application that will fetch data from a REST API.
To make the requests I'm using Retrofit together with Otto.
For all my requests I add a RequestInterceptor that will add a header (Authorization) to all my requests.
In the RequestInterceptor I'm calling a method to the my current access_token then I populate the header to the request.
RequestInterceptor requestInterceptor = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
Token token = TokenPreferences.getToken();
request.addHeader("Authorization", token.getTokenType() + " " + token.getAccessToken());
}
};
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://example.com")
.setRequestInterceptor(requestInterceptor)
.build();
...
This works fine until the access_token has expired, then the request will fail with HTTP status 401 Unauthorized.
When this happens, I want to make a new request to get a new access_token from my refresh_token I got and then do the first request again.
I'm not really sure how to make that work.
Try a com.squareup.okhttp.Authenticator. As far as I can tell, this is preferable to com.squareup.okhttp.Interceptor (which you'll find suggested elsewhere), because it will only kick in for unauthorized requests. Here's a basic example:
public class ApiAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
for (Challenge challenge : response.challenges()) {
if (challenge.getScheme().equals("Bearer")) {
String authToken = // Refresh the token here
if (authToken != null) {
return response.request().newBuilder()
.header("Authorization", "Bearer " + authToken)
.build();
}
}
}
return null;
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
return null;
}
}
You can attach it to your client like this:
okHttpClient.setAuthenticator(new ApiAuthenticator());
Be aware that if you're using Retrofit to refresh your token and the token is invalid, you might get unexpected, hard-to-debug behavior for 403 codes, but the solution is just to use a try/catch block.
try {
token = oauthService.refreshAccessToken(args);
} catch (RetrofitError error) {
// Do something that leads to login
}
Retry mechanizm is not implemented in Retrofit. It will be in v2. (Retrying requests manually)
You should implement retrying by recursive call from onFailure() callback as Deepack suggested.
I am facing the same issue and I currently don't know how to retry my request after it failed due to a Unauthorized error.
Although #Yuriy Ashaev mentioned that the retry mechanism should be part of the v2.0 Retrofit version, this should be dedicated only to 5xx errors (see Request Object draft description here) and attempting to retry a request that failed for another reason will raise an exception.
As of now, you can still add your own ErrorHandler to your RestAdapter and catch the Unauthorized error to fetch a refresh token. Here is a way to achieve this:
class RestErrorHandler implements retrofit.ErrorHandler {
#Override
public Throwable handleError(RetrofitError cause) {
Response r = cause.getResponse();
if (r != null && r.getStatus() == 401) {
Token token = mTokenProvider.fetchToken();
if (token != null) {
addHeader(HEADER_AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token());
}
// Todo: Relaunch previous request
return cause;
}
return cause;
}
}
However I don't see any way to retry the failed request from this point other than in all your failure() request callback which will be called when returning the RetrofitError from your ErrorHandler...
I would really appreciate if someone could point us how you could retry the failed request from the ErrorHandler as the response only point to f-the request url.