How to retry HTTP requests with OkHttp/Retrofit?

后端 未结 13 719
一向
一向 2020-12-07 09:04

I am using Retrofit/OkHttp (1.6) in my Android project.

I don\'t find any request retry mechanism built-in to either of them. On searching more, I read OkHttp seems

相关标签:
13条回答
  • 2020-12-07 09:44

    I am of the opinion that you shouldn't mix API handling (done by retrofit/okhttp) with retries. Retrying mechanisms are more orthogonal, and can be used in many other contexts as well. So I use Retrofit/OkHTTP for all the API calls and request/response handling, and introduce another layer above, for retrying the API call.

    In my limited Java experience so far, I have found jhlaterman's Failsafe library (github: jhalterman/failsafe) to be a very versatile library for handling many 'retry' situations cleanly. As an example, here's how I would use it with a retrofit instantiated mySimpleService, for authentication -

    AuthenticationResponse authResp = Failsafe.with(
    new RetryPolicy().retryOn(Arrays.asList(IOException.class, AssertionError.class))
            .withBackoff(30, 500, TimeUnit.MILLISECONDS)
            .withMaxRetries(3))
    .onRetry((error) -> logger.warn("Retrying after error: " + error.getMessage()))
    .get(() -> {
        AuthenticationResponse r = mySimpleAPIService.authenticate(
                new AuthenticationRequest(username,password))
                .execute()
                .body();
    
        assert r != null;
    
        return r;
    });
    

    The code above catches socket exceptions, connection errors, assertion failures, and retries on them maximum of 3 times, with exponential backoff. It also allows you to customise on-retry behaviour, and allows you to specify a fallback as well. It's quite configurable, and can adapt to most of the retry situations.

    Feel free to check the documentation of the library as it offers many other goodies apart from just retries.

    0 讨论(0)
  • 2020-12-07 09:44

    Working prod solution.

    public int callAPI() {
        return 1; //some method to be retried
    }
    
    public int retrylogic()  throws InterruptedException, IOException{
        int retry = 0;
        int status = -1;
        boolean delay = false;
        do {
            if (delay) {
                Thread.sleep(2000);
            }
    
            try {
                status = callAPI();
            }
            catch (Exception e) {
                System.out.println("Error occured");
                status = -1;
            }
            finally {
                switch (status) {
                case 200:
                    System.out.println(" **OK**");
                    return status; 
                default:
                    System.out.println(" **unknown response code**.");
                    break;
                }
                retry++;
                System.out.println("Failed retry " + retry + "/" + 3);
                delay = true;
    
            } 
        }while (retry < 3);
    
        System.out.println("Aborting download of dataset.");
        return status;
    }
    
    0 讨论(0)
  • 2020-12-07 09:45

    For Retrofit 2.x;

    You can use Call.clone() method to clone request and execute it.

    For Retrofit 1.x;

    You can use Interceptors. Create a custom interceptor

        OkHttpClient client = new OkHttpClient();
        client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
        client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
        client.interceptors().add(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
    
                // try the request
                Response response = chain.proceed(request);
    
                int tryCount = 0;
                while (!response.isSuccessful() && tryCount < 3) {
    
                    Log.d("intercept", "Request is not successful - " + tryCount);
    
                    tryCount++;
    
                    // retry the request
                    response = chain.proceed(request);
                }
    
                // otherwise just pass the original response on
                return response;
            }
        });
    

    And use it while creating RestAdapter.

    new RestAdapter.Builder()
            .setEndpoint(API_URL)
            .setRequestInterceptor(requestInterceptor)
            .setClient(new OkClient(client))
            .build()
            .create(Adapter.class);
    
    0 讨论(0)
  • 2020-12-07 09:46

    Courtesy to the top answer,This is what worked for me. If there is a connectivity issues, its better to wait for a few seconds before retry.

    public class ErrorInterceptor implements Interceptor {
    ICacheManager cacheManager;
    Response response = null;
    int tryCount = 0;
    int maxLimit = 3;
    int waitThreshold = 5000;
    @Inject
    public ErrorInterceptor() {
    
    }
    
    @Override
    public Response intercept(Chain chain){
    
       // String language =  cacheManager.readPreference(PreferenceKeys.LANGUAGE_CODE);
      Request request = chain.request();
      response =  sendReqeust(chain,request);
        while (response ==null && tryCount < maxLimit) {
            Log.d("intercept", "Request failed - " + tryCount);
            tryCount++;
            try {
                Thread.sleep(waitThreshold); // force wait the network thread for 5 seconds
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           response = sendReqeust(chain,request);
        }
        return response;
    }
    
    private Response sendReqeust(Chain chain, Request request){
        try {
            response = chain.proceed(request);
            if(!response.isSuccessful())
                return null;
            else
            return response;
        } catch (IOException e) {
          return null;
        }
    }
    

    }

    0 讨论(0)
  • 2020-12-07 09:47

    As stated in the docs, a better might be to use the baked in authenticators, eg: private final OkHttpClient client = new OkHttpClient();

      public void run() throws Exception {
        client.setAuthenticator(new Authenticator() {
          @Override public Request authenticate(Proxy proxy, Response response) {
            System.out.println("Authenticating for response: " + response);
            System.out.println("Challenges: " + response.challenges());
            String credential = Credentials.basic("jesse", "password1");
            return response.request().newBuilder()
                .header("Authorization", credential)
                .build();
          }
    
          @Override public Request authenticateProxy(Proxy proxy, Response response) {
            return null; // Null indicates no attempt to authenticate.
          }
        });
    
        Request request = new Request.Builder()
            .url("http://publicobject.com/secrets/hellosecret.txt")
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }
    
    0 讨论(0)
  • 2020-12-07 09:50

    The problem with response.isSuccessful() is when you have an exception like SocketTimeoutException.

    I modified the original code to fix it.

    OkHttpClient client = new OkHttpClient();
    client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    client.interceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = null;
            boolean responseOK = false;
            int tryCount = 0;
    
            while (!responseOK && tryCount < 3) {
                try {
                     response = chain.proceed(request);
                     responseOK = response.isSuccessful();                  
                }catch (Exception e){
                     Log.d("intercept", "Request is not successful - " + tryCount);                     
                }finally{
                     tryCount++;      
                }
            }
    
            // otherwise just pass the original response on
            return response;
        }
    });
    

    Hope it helps. Regards.

    0 讨论(0)
提交回复
热议问题