Подтвердить что ты не робот

Как повторить HTTP-запросы с помощью OkHttp/Retrofit?

Я использую Retrofit/OkHttp (1.6) в своем проекте Android.

Я не нашел механизм повторной попытки запроса, встроенный в любой из них. При поиске больше, я читаю OkHttp, похоже, молчат. Я не вижу, что это происходит на любом из моих соединений (HTTP или HTTPS). Как настроить повторы с помощью okclient?

В настоящее время я улавливаю исключения и повторяю, сохраняя переменную счетчика.

4b9b3361

Ответ 1

Для дооснащения 1.x;

Вы можете использовать перехватчики. Создать собственный перехватчик

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

И используйте его при создании RestAdapter.

new RestAdapter.Builder()
        .setEndpoint(API_URL)
        .setRequestInterceptor(requestInterceptor)
        .setClient(new OkClient(client))
        .build()
        .create(Adapter.class);

Для дооснащения 2.x;

Вы можете использовать метод Call.clone(), чтобы клонировать запрос и выполнить его.

Ответ 2

Я не знаю, является ли это для вас вариантом, но вы можете использовать RxJava вместе с Retrofit.

Модернизация может возвращать Observables при переадресации вызовов. В Oberservables вы можете просто вызвать retry(count) для повторной подписки на Observable, когда он испускает ошибку.

Вам нужно будет определить вызов в вашем интерфейсе следующим образом:

@GET("/data.json")
Observable<DataResponse> fetchSomeData();

Затем вы можете подписаться на этот Наблюдаемый следующим образом:

restApi.fetchSomeData()
.retry(5)  // Retry the call 5 times if it errors
.subscribeOn(Schedulers.io())  // execute the call asynchronously
.observeOn(AndroidSchedulers.mainThread())  // handle the results in the ui thread
.subscribe(onComplete, onError); 
// onComplete and onError are of type Action1<DataResponse>, Action1<Throwable>
// Here you can define what to do with the results

У меня была такая же проблема, как и вы, и на самом деле это было мое решение. RxJava - очень хорошая библиотека для использования в сочетании с Retrofit. Вы даже можете сделать много классных вещей в дополнение к повторной попытке (например, составление и цепочка вызовов).

Ответ 3

Проблема с response.isSuccessful() - это когда у вас есть исключение, такое как SocketTimeoutException.

Я изменил исходный код, чтобы исправить его.

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

Надеюсь, это поможет. С наилучшими пожеланиями.

Ответ 4

Я придерживаюсь мнения, что вы не должны смешивать обработку API (сделанную с помощью retrofit/okhttp) с повторными попытками. Механизмы повторных попыток являются более ортогональными и могут использоваться во многих других контекстах. Поэтому я использую Retrofit/OkHTTP для всех вызовов API и обработки запросов/ответов, и представляю еще один уровень выше, чтобы повторить вызов API.

До сих пор в своем ограниченном опыте работы с Java я обнаружил, что библиотека jhlaterman Failsafe (github: jhalterman/failsafe) является очень универсальной библиотекой для аккуратной обработки многих ситуаций "повторных попыток". В качестве примера, вот как я мог бы использовать его с модифицированным экземпляром mySimpleService для аутентификации -

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

Приведенный выше код перехватывает исключения сокетов, ошибки подключения, ошибки подтверждений и повторяет попытки максимум 3 раза с экспоненциальным откатом. Это также позволяет вам настраивать поведение при повторных попытках и также указывать запасной вариант. Он вполне настраивается и может адаптироваться к большинству ситуаций повторения.

Не стесняйтесь проверять документацию библиотеки, поскольку она предлагает много других полезностей, кроме повторных попыток.

Ответ 5

Любезность к верхнему ответу. Это то, что сработало для меня. Если возникают проблемы с подключением, лучше дождаться нескольких секунд, прежде чем повторить попытку.

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

}

Ответ 6

Я нашел способ (интерпретатор OKHttpClient), предоставленный Синаном Козаком, не работает, когда сбой HTTP-соединения не был, пока нет ответа HTTP-ответа.

Итак, я использую другой способ привязать объект Observable, вызывать .retryWhen на нем. Кроме того, я добавил ограничение retryCount.

import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.HttpException;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.jackson.JacksonConverterFactory;
import rx.Observable;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

Тогда

    RxJavaCallAdapterFactory originCallAdaptorFactory = RxJavaCallAdapterFactory.create();

    CallAdapter.Factory newCallAdaptorFactory = new CallAdapter.Factory() {
        @Override
        public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {

            CallAdapter<?> ca = originCallAdaptorFactory.get(returnType, annotations, retrofit);

            return new CallAdapter<Observable<?>>() {

                @Override
                public Type responseType() {
                    return ca.responseType();
                }

                int restRetryCount = 3;

                @Override
                public <R> Observable<?> adapt(Call<R> call) {
                    Observable<?> rx = (Observable<?>) ca.adapt(call);

                    return rx.retryWhen(errors -> errors.flatMap(error -> {
                        boolean needRetry = false;
                        if (restRetryCount >= 1) {
                            if (error instanceof IOException) {
                                needRetry = true;
                            } else if (error instanceof HttpException) {
                                if (((HttpException) error).code() != 200) {
                                    needRetry = true;
                                }
                            }
                        }

                        if (needRetry) {
                            restRetryCount--;
                            return Observable.just(null);
                        } else {
                            return Observable.error(error);
                        }
                    }));
                }
            };
        }
    };                

Тогда  добавить или заменить

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())

с

.addCallAdapterFactory(newCallAdaptorFactory)

Например:

return new Retrofit
        .Builder()
        .baseUrl(baseUrl)
        .client(okClient)
        .addCallAdapterFactory(newCallAdaptorFactory)
        .addConverterFactory(JacksonConverterFactory.create(objectMapper));

Примечание. Для простоты я просто обрабатываю код HTTP > 404 как повтор, пожалуйста, измените его для себя.

Кроме того, если HTTP-ответ равен 200, то выше rx.retryWhen не будет вызван, если вы настаиваете на проверке такого ответа, вы можете добавить rx.subscribeOn(...throw error... до .retryWhen.

Ответ 7

Для тех, кто предпочитает перехватчик для решения проблемы повторной попытки - Основываясь на ответе Синан, вот мой предлагаемый перехватчик, который включает в себя как количество повторов попыток, так и задержку отсрочки, и только повторяет попытки, когда сеть доступна, и когда запрос не был отменен. (работает только с IOExceptions (SocketTimeout, UnknownHost и т.д.))

    builder.addInterceptor(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            // try the request
            Response response = null;
            int tryCount = 1;
            while (tryCount <= MAX_TRY_COUNT) {
                try {
                    response = chain.proceed(request);
                    break;
                } catch (Exception e) {
                    if (!NetworkUtils.isNetworkAvailable()) {
                        // if no internet, dont bother retrying request
                        throw e;
                    }
                    if ("Canceled".equalsIgnoreCase(e.getMessage())) {
                        // Request canceled, do not retry
                        throw e;
                    }
                    if (tryCount >= MAX_TRY_COUNT) {
                        // max retry count reached, giving up
                        throw e;
                    }

                    try {
                        // sleep delay * try count (e.g. 1st retry after 3000ms, 2nd after 6000ms, etc.)
                        Thread.sleep(RETRY_BACKOFF_DELAY * tryCount);
                    } catch (InterruptedException e1) {
                        throw new RuntimeException(e1);
                    }
                    tryCount++;
                }
            }

            // otherwise just pass the original response on
            return response;
        }
    });

Ответ 8

Кажется, он будет присутствовать в модификации 2.0 из API Spec: https://github.com/square/retrofit/issues/297. В настоящее время лучшим способом является исключение catch и повторная попытка вручную.

Ответ 9

Как указано в docs, лучше всего использовать испеченные в аутентификаторах, например: закрытый конечный клиент OkHttpClient = новый 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());
  }

Ответ 10

Я очень много играю с этой проблемой, пытаясь найти, как наилучшим образом повторить запросы Retrofit. Я использую Retrofit 2, поэтому мое решение для Retrofit 2. Для Retrofit 1 вы должны использовать Interceptor, как принятый ответ здесь. Ответ @joluet верен, но он не упомянул, что метод повтора нужно вызвать до метода .subscribe(onComplete, onError). Это очень важно, иначе запрос не будет повторно повторен, как @pocmo, упомянутый в ответе @joluet. Вот мой пример:

final Observable<List<NewsDatum>> newsDetailsObservable = apiService.getCandidateNewsItem(newsId).map((newsDetailsParseObject) -> {
                    return newsDetailsParseObject;
                });

newsDetailsObservable.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .retry((integer, throwable) -> {
                //MAX_NUMBER_TRY is your maximum try number
                if(integer <= MAX_NUMBER_TRY){
                    return true;//this will retry the observable (request)
                }
                return false;//this will not retry and it will go inside onError method
            })
            .subscribe(new Subscriber<List<NewsDatum>>() {
                @Override
                public void onCompleted() {
                    // do nothing
                }

                @Override
                public void onError(Throwable e) {
                   //do something with the error
                }

                @Override
                public void onNext(List<NewsDatum> apiNewsDatum) {
                    //do something with the parsed data
                }
            });

apiService - мой объект RetrofitServiceProvider.

BTW: Я использую Java 8, поэтому в коде содержится много лямбда-выражений.

Ответ 11

Просто хочу поделиться своей версией. Он использует метод rxJava retryWhen. Моя версия повторяет соединение каждые N = 15 секунд и почти сразу отправляет попытку при восстановлении интернет-соединения.

public class RetryWithDelayOrInternet implements Function<Flowable<? extends Throwable>, Flowable<?>> {
public static boolean isInternetUp;
private int retryCount;

@Override
public Flowable<?> apply(final Flowable<? extends Throwable> attempts) {
    return Flowable.fromPublisher(s -> {
        while (true) {
            retryCount++;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                attempts.subscribe(s);
                break;
            }
            if (isInternetUp || retryCount == 15) {
                retryCount = 0;
                s.onNext(new Object());
            }
        }
    })
            .subscribeOn(Schedulers.single());
}}

И вы должны использовать его перед подпиской, как это:

.retryWhen(new RetryWithDelayOrInternet())

Вы должны вручную изменить поле isInternetUp

public class InternetConnectionReceiver extends BroadcastReceiver {


@Override
public void onReceive(Context context, Intent intent) {
    boolean networkAvailable = isNetworkAvailable(context);
    RetryWithDelayOrInternet.isInternetUp = networkAvailable;
}
public static boolean isNetworkAvailable(Context context) {
    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
    return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}}

Ответ 12

Решение, которое работало для меня на OkHttp 3.9.1 (учитывая другие ответы на этот вопрос):

@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
    Request  request      = chain.request();
    int      retriesCount = 0;
    Response response     = null;

    do {
        try {
            response = chain.proceed(request);

        // Retry if no internet connection.
        } catch (ConnectException e) {
            Log.e(TAG, "intercept: ", e);
            retriesCount++;

            try {
                Thread.sleep(RETRY_TIME);

            } catch (InterruptedException e1) {
                Log.e(TAG, "intercept: ", e1);
            }
        }

    } while (response == null && retriesCount < MAX_RETRIES);

    // If there was no internet connection, then response will be null.
    // Need to initialize response anyway to avoid NullPointerException.
    if (response == null) {
        response = chain.proceed(newRequest);
    }

    return response;
}

Ответ 13

Рабочее решение.

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