当多个请求发送到服务器时,Okhttp刷新过期令牌 [英] Okhttp refresh expired token when multiple requests are sent to the server

查看:110
本文介绍了当多个请求发送到服务器时,Okhttp刷新过期令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个ViewPager,并且同时加载ViewPager时进行了三个Web服务调用.

I have a ViewPager and three webservice calls are made when ViewPager is loaded simultaneously.

当第一个返回401时,调用Authenticator并刷新Authenticator内的令牌,但其余2个请求已使用旧的刷新令牌发送到服务器,并失败,并在Interceptor中捕获了498个,应用登出.

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.

当前,我有一个变量来指示Authenticator中是否正在进行令牌刷新,在这种情况下,我取消了Interceptor中的所有后续请求,并且用户必须手动刷新页面,或者我可以注销用户并强制用户登录.

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.

使用okhttp 3.x(适用于Android)解决上述问题的最佳解决方案或体系结构是什么?

What is a good solution or architecture for the above problem using okhttp 3.x for Android?

我想解决的问题是一般性的,我不想对我的电话进行排序.即等待一个呼叫完成并刷新令牌,然后仅在活动和片段级别发送其余请求.

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.

请求输入密码.这是Authenticator的标准代码:

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;
    }
}

一些与此类似的问题: OkHttp和翻新,使用并发请求刷新令牌

Some questions similar to this: OkHttp and Retrofit, refresh token with concurrent requests

推荐答案

需要特别注意的是,除了拦截器以外,仍可以在其他位置调用accountManager.blockingGetAuthToken(或非阻塞版本).因此,防止此问题发生的正确位置应该是在验证器的内部.

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.

我们要确保需要访问令牌的第一个线程将检索它,并且可能的其他线程应该只注册一个回调,以便在第一个线程完成检索令牌后调用该函数.
好消息是,AbstractAccountAuthenticator已经具有一种传递异步结果的方式,即AccountAuthenticatorResponse,可以在其上调用onResultonError.

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.

以下示例由3个块组成.

The following sample consists of 3 blocks.

第一个是关于确保只有一个线程获取访问令牌,而其他线程只需注册其response进行回调.

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
  }
}

使用response时,请务必在所有路径上返回null.

Just make sure to return null on all paths when using the response.

您显然可以使用其他方式来同步这些代码块,例如@matrix在另一个响应中显示的原子.我使用synchronized是因为我相信这是最容易掌握的实现,因为这是一个很大的问题,每个人都应该这样做;)

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 ;)

以上示例是发射器循环的改编版本,其中详细介绍了并发.如果您对RxJava的幕后工作方式感兴趣,那么此博客是一个很好的来源.

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.

这篇关于当多个请求发送到服务器时,Okhttp刷新过期令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆