Scenario: I am using OkHttp / Retrofit to access a web service: multiple HTTP requests are sent out at the same time. At some point the auth token expires, and
Thanks for your answers - they led me to the solution. I ended up using a ConditionVariable
lock and an AtomicBoolean. Here's how you can achieve this: read through the comments.
/**
* This class has two tasks:
* 1) sign requests with the auth token, when available
* 2) try to refresh a new token
*/
public class SignedRequestInterceptor implements Interceptor {
// 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);
...
@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.
mAccountManager.invalidateAuthToken(AuthConsts.ACCOUNT_TYPE, token);
// do we have an access token to refresh?
String refreshToken = mAccountManager.getUserData(account, HorshaAuthenticator.KEY_REFRESH_TOKEN);
if (!TextUtils.isEmpty(refreshToken)) {
.... // refresh token
}
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. The thread in charge of refreshing the token has taken care of
// redirecting the user to the login activity.
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 user for login again.
}
// returning the response to the original request
return response;
}
}