Token 更新方案

127 阅读1分钟
public class TokenInterceptor extends BaseInterceptor {

    static final String KEY_CODE = "code";
    static final String KEY_TOKEN = "token";
    
    final static ConditionVariable LOCK = new ConditionVariable(true);
    static final AtomicBoolean mIsRefreshing = new AtomicBoolean(false);
    static final Long REFRESH_WAIT_TIME = 25 * 1000L;


    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Map<String, Object> requestParams = //...;
  
        Response response = chain.proceed(request);
        if (response.isSuccessful()) {
            String code = getJsonResponseValue(parseResponseStr(response), KEY_CODE);
            if (TOKEN_EXPIRE_CODE.equals(code)) {
                /*
                 *  Because we send out multiple HTTP requests in parallel, they might all list expire or invalid 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)) {
                    try {
                        LOCK.close();
                        Logger.d("API", "lock the request" + request.url().toString());
                        //let’s check the token, refresh or login out
                        String currentToken = (String) requestParams.get(KEY_TOKEN);
                        String localToken = //...;
                        if (currentToken != null && Objects.equals(currentToken, localToken)) {
                            //sync execute to update the token
                            boolean updateTokenStatus = updateToken(chain, currentToken);
                           
                            if (updateTokenStatus) {
                                response = useNewTokenRequestAgain(chain, requestParams);
                            } else {
                               //login out
                            }
                        } else if (currentToken != null) {
                            response = useNewTokenRequestAgain(chain, requestParams);
                        }
                    } finally {
                        LOCK.open();
                        mIsRefreshing.set(false);
                    }
                } else {
                    //Another thread is refreshing the token, so wait for it
                    boolean conditionOpen = LOCK.block(REFRESH_WAIT_TIME);
                    if (conditionOpen) {
                        Logger.d("API", "other request again" + request.url().toString());
                        //if execute here,let's say we have get new token
                        //so we should build a new request to execute again
                        response = useNewTokenRequestAgain(chain, requestParams);
                    } else {
                        //time out... wait for lock
                        //login out
                    }
                }
            } 
        }
        // returning the response to the original request
        return response;
    }

 
  
    private boolean updateToken(Chain chain, String currentToken) throws IOException {
        JsonRequest request = //...;
        request.addParam(KEY_TOKEN, currentToken);
        if (Constants.DEBUG) {
            Logger.d("API", "Updating token start, old token is:" + currentToken);
        }
        Response response = chain.proceed(request.getRequest());
        if (response.isSuccessful()) {
            String jsonResp = parseResponseStr(response);
            if (Constants.DEBUG) {
                Logger.d("API", "update token" + jsonResp);
            }
            TokenResponse tokenResponse = //...
             if (tokenResponse.isSuccessful()) { //Success
                //update local token
                return true
            }
        } else {
            response.close();
        }
        return false;
    }


    /**
     * with the {@link okhttp3.Interceptor.Chain} and new token that we updated,
     * we can resend the request again to obtain what we wanted
     */
    private Response useNewTokenRequestAgain(Chain chain, Map<String, Object> params) throws IOException {
        String newToken = //...;
        Request.Builder againBuilder = chain.request().newBuilder();
        Request request = JsonRequest.buildRequest(againBuilder, params, toke);
        return chain.proceed(request);
    }
}


/**
 * use buffer to parse response string,so we can read the buffer more than once
 */
protected String parseResponseStr(Response response) throws IOException {
    ResponseBody responseBody = response.body();
    if (responseBody != null) {
        BufferedSource source = responseBody.source();
        source.request(Long.MAX_VALUE);
        Buffer buffer = source.getBuffer();
        return buffer.clone().readString(StandardCharsets.UTF_8);
    }
    return null;
}


/**
 * parse the json,and get the value of key
 */
protected String getJsonResponseValue(String jsonString, String key) {
    if (TextUtils.isEmpty(jsonString)) return null;
    try {
        return new JsonParser().parse(jsonString).getAsJsonObject().get(key).getAsString();
    } catch (Exception exception) {
        return null;
    }
}