2017-07-11 20 views
14

मेरे पास ViewPager है और ViewPager एक साथ लोड होने पर तीन webservice कॉल किए जाते हैं।ओकेhttp रीफ्रेश समाप्त हो गया टोकन ताज़ा करें जब सर्वर पर एकाधिक अनुरोध भेजे जाते हैं

पहले एक रिटर्न जब 401, Authenticator कहा जाता है और मैं Authenticator अंदर टोकन ताज़ा है, लेकिन शेष 2 अनुरोध पहले से ही पुराने ताज़ा टोकन के माध्यम से सर्वर के लिए भेजा जाता है और 498 जो इंटरसेप्टर में कब्जा कर लिया है और एप्लिकेशन को बाहर लॉग होता है के साथ विफल रहता है।

यह आदर्श व्यवहार नहीं है जिसे मैं उम्मीद करूंगा। मैं कतार में दूसरा और तीसरा अनुरोध रखना चाहता हूं और जब टोकन रीफ्रेश किया जाता है, तो कतारबद्ध अनुरोध को पुनः प्रयास करें।

वर्तमान में, मैं अगर टोकन ताज़ा Authenticator में चल रही है, उस मामले में, मैं Interceptor में बाद के सभी अनुरोध को रद्द और उपयोगकर्ता को अपने पृष्ठ को ताज़ा करने है या मैं करने के लिए उपयोगकर्ता और बल उपयोगकर्ता लॉगआउट कर सकते हैं इंगित करने के लिए एक चर है लॉग इन करें।

Android के लिए okhttp 3.x का उपयोग कर उपरोक्त समस्या के लिए एक अच्छा समाधान या आर्किटेक्चर क्या है?

संपादित करें: जिस समस्या को मैं हल करना चाहता हूं वह सामान्य है और मैं अपनी कॉल अनुक्रमित नहीं करना चाहता हूं। यानी एक कॉल को समाप्त करने और टोकन को रीफ्रेश करने का इंतजार करें और फिर केवल गतिविधि और खंड स्तर पर शेष अनुरोध भेजें।

कोड का अनुरोध किया गया था।

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 and Retrofit, refresh token with concurrent requests

+0

कृपया कुछ कोड पोस्ट करें –

+0

क्या आप अपना प्रामाणिक कोड पोस्ट कर सकते हैं? धन्यवाद। आप अपने एपीआई से 4 9 8 को अपने समाप्त टोकन के साथ क्यों प्राप्त करते हैं? – savepopulation

+0

@ सेवपोप्यूलेशन 498 का ​​अर्थ अमान्य टोकन है। पहले अनुरोध के साथ भेजे गए 2 अनुरोध पुराने टोकन हैं और अनुरोध 498 त्रुटि कोड के साथ विफल रहता है। – sat

उत्तर

6

आप ऐसा कर सकते हैं:

डेटा सदस्यों के रूप में उन जोड़ें: यह Authenticator के लिए एक मानक कोड है

// 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. 
        * 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; 
    } 

इस तरह आप केवल टोकन को रीफ्रेश करने के लिए 1 अनुरोध भेजेंगे और फिर एक दूसरे के लिए आपको ताज़ा टोकन होगा।

2

आप इस आवेदन के स्तर इंटरसेप्टर

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

साथ कोशिश कर सकते हैं आप उदाहरण okHttp को

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

आशा इस मदद करता है इस तरह इंटरसेप्टर सेट कर सकते हैं !!!!

8

यह ध्यान रखना महत्वपूर्ण है कि accountManager.blockingGetAuthToken (या गैर-अवरुद्ध संस्करण) को अभी भी इंटरसेप्टर के अलावा कहीं और कहा जा सकता है। इसलिए इस मुद्दे को होने से रोकने के लिए सही स्थान प्रमाणीकरणकर्ता के भीतर होगा।

हम यह सुनिश्चित करना चाहते हैं कि एक्सेस थोकन की आवश्यकता वाले पहले थ्रेड को इसे पुनर्प्राप्त कर दिया जाए, और संभवतः अन्य धागे को कॉलबैक के लिए पंजीकरण करना चाहिए जब पहले थ्रेड टोकन को पुनर्प्राप्त करना समाप्त कर दिया जाए।
अच्छी खबर यह है कि AbstractAccountAuthenticator में पहले से ही एसिंक्रोनस परिणाम देने का एक तरीका है, अर्थात् AccountAuthenticatorResponse, जिस पर आप onResult या onError पर कॉल कर सकते हैं।


निम्नलिखित नमूने में 3 ब्लॉक होते हैं।

पहले कोई यह सुनिश्चित करने के बारे में है कि केवल एक थ्रेड पहुंच टोकन प्राप्त करता है जबकि अन्य धागे कॉलबैक के लिए केवल response पंजीकृत करते हैं।

दूसरा भाग सिर्फ एक डमी खाली परिणाम बंडल है। यहां, आप अपना टोकन लोड करेंगे, संभवतः इसे रीफ्रेश करेंगे, आदि

तीसरा हिस्सा आपके परिणाम (या त्रुटि) के बाद आप क्या करते हैं। आपको पंजीकृत होने वाले हर दूसरे धागे के लिए प्रतिक्रिया को कॉल करना सुनिश्चित करना होगा।

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 वापस जाने के लिए सुनिश्चित करें।

आप स्पष्ट रूप से उन कोड ब्लॉक को सिंक्रनाइज़ करने के लिए अन्य साधनों का उपयोग कर सकते हैं, जैसे परमाणुओं को @matrix द्वारा अन्य प्रतिक्रिया में दिखाया गया है। मैं synchronized का इस्तेमाल किया है, क्योंकि मेरा मानना ​​है कि यह सबसे आसान कार्यान्वयन समझ होने के लिए के बाद से इस एक बड़ा सवाल है और हर कोई यह कर किया जाना चाहिए;)


ऊपर नमूना एक emitter loop described here का एक रूपांतरित संस्करण है, जहां यह समवर्तीता के बारे में बहुत विस्तार से जाता है। यदि आप रुचि रखते हैं कि RxJava कैसे हुड के नीचे काम करता है तो यह ब्लॉग एक महान स्रोत है।

+0

इसके साथ खेल रहा है और देख रहा हूं जो fetching टोकन कभी सच नहीं है। 'GetAuthToken' के लिए प्रत्येक कॉल केवल पूरी विधि के माध्यम से चलती है। क्या मैं कुछ भूल रहा हूँ? – Jack

+0

@ जैक जब तक आप इसे लागू करने में कोई त्रुटि नहीं करते हैं, इसका मतलब यह है कि आपको किसी रेसिंग की स्थिति नहीं मिली है।यह कोड सुनिश्चित करता है कि IFF एकाधिक थ्रेड को एक नई पहुंच की आवश्यकता है टोकन केवल एक अनुरोध करता है और परिणामों को दूसरों को पास करता है। मैं बैकग्राउंड थ्रेड पर एक बार में 10+ एपीआई कॉल फायर करने से पहले एक्सेस टोकन को अमान्य करके इसका परीक्षण करने में सक्षम था। फिर एक धागा लुकअप करेगा, जबकि अन्य परिणाम के लिए इंतजार करेंगे। –

+0

मेरे पास वापस आने के लिए धन्यवाद। मैंने यही माना। मैं 'getAuthToken() 'को एक okhttp' प्रमाणीकरणकर्ता' के भीतर से कॉल कर रहा हूं। मैं आरएक्सजेवा का उपयोग कर रहा हूं और 'io' थ्रेड पर सभी मूल अनुरोधों को फेंक रहा हूं जो कि' getAuthToken 'को कॉल करने के लिए बस समाप्त होता है, जिस तरह से मैं विधि में सिंक्रनाइज़ेशन की अपेक्षा करता हूं। मैं सिंक्रनाइज़ेशन के लिए बिल्कुल नया हूं इसलिए मैं अभी भी इसके माध्यम से काम कर रहा हूं। यदि सिंक्रनाइज़ विधि को उसी धागे से बुलाया जा रहा है तो क्या यह अभी भी इंतजार करेगा? – Jack

संबंधित मुद्दे