Как повторить HTTP-запросы с помощью OkHttp/Retrofit?
Я использую Retrofit/OkHttp (1.6) в своем проекте Android.
Я не нашел механизм повторной попытки запроса, встроенный в любой из них. При поиске больше, я читаю OkHttp, похоже, молчат. Я не вижу, что это происходит на любом из моих соединений (HTTP или HTTPS). Как настроить повторы с помощью okclient?
В настоящее время я улавливаю исключения и повторяю, сохраняя переменную счетчика.
Ответы
Ответ 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;
}