program story

OkHttp / Retrofit으로 HTTP 요청을 재 시도하는 방법은 무엇입니까?

inputbox 2020. 12. 11. 08:07
반응형

OkHttp / Retrofit으로 HTTP 요청을 재 시도하는 방법은 무엇입니까?


Android 프로젝트에서 Retrofit / OkHttp (1.6)를 사용하고 있습니다.

어느 쪽에도 내장 된 요청 재시도 메커니즘을 찾지 못했습니다. 더 많이 검색하면 OkHttp가 자동 재 시도하는 것 같습니다. 내 연결 (HTTP 또는 HTTPS)에서 그런 일이 발생하지 않습니다. okclient로 재 시도를 구성하는 방법은 무엇입니까?

지금은 예외를 포착하고 카운터 변수를 유지 관리하고 있습니다.


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

Retrofit 2.x의 경우;

Call.clone () 메서드를 사용 하여 요청을 복제하고 실행할 수 있습니다.


이것이 옵션인지는 모르겠지만 RxJava 를 Retrofit과 함께 사용할 수 있습니다 .

Retrofit은 휴식 호출시 Observable을 반환 할 수 있습니다. Oberservables retry(count)에서는 Observable이 오류를 내보냈을 때 다시 구독하도록 호출 할 수 있습니다 .

다음과 같이 인터페이스에서 호출을 정의해야합니다.

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

그런 다음 다음과 같이이 Observable을 구독 할 수 있습니다.

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과 함께 사용하기에 정말 좋은 라이브러리입니다. 재시도 외에도 많은 멋진 작업을 수행 할 수 있습니다 (예 : 호출 작성 및 연결 ).


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

도움이 되었기를 바랍니다. 문안 인사.


재시 도와 API 처리 (retrofit / okhttp에 의해 수행됨)를 혼합해서는 안된다고 생각합니다. 재시도 메커니즘은 더 직각적이며 다른 많은 컨텍스트에서도 사용할 수 있습니다. 따라서 모든 API 호출 및 요청 / 응답 처리에 Retrofit / OkHTTP를 사용하고 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 회 재 시도합니다. 또한 재시도 동작을 사용자 정의 할 수 있으며 폴 백도 지정할 수 있습니다. 구성이 가능하며 대부분의 재시도 상황에 적응할 수 있습니다.

재시도 외에도 많은 다른 장점을 제공하므로 라이브러리 문서를 자유롭게 확인하십시오.


최고 답변에 대한 예의, 이것은 나를 위해 일한 것입니다. 연결 문제가있는 경우 다시 시도하기 전에 몇 초 동안 기다리는 것이 좋습니다.

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

}


http 연결이 실패하면 Sinan Kozak에서 제공하는 방법 (OKHttpClient 인터셉터)이 작동하지 않는다는 것을 알았습니다. 아직 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 전에 추가 할 수 있습니다 .


재시도 문제를 처리하기 위해 인터셉터를 선호하는 사람들을 위해-Sinan의 답변을 바탕으로 여기에 제안 된 인터셉터가 있습니다. 여기에는 재시도 횟수와 백 오프 지연이 모두 포함되며 네트워크를 사용할 수 있고 요청이 없을 때 재 시도 만 시도합니다. 취소 된. (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;
        }
    });

API Spec : https://github.com/square/retrofit/issues/297 에서 retrofit 2.0에있을 것 같습니다 . 현재 가장 좋은 방법은 예외를 포착하고 수동으로 재 시도하는 것 같습니다.


문서에 명시된 바와 같이 인증 자에 구운 것을 사용하는 것이 더 좋을 수 있습니다. 예 : 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());
  }

나는 Retrofit 요청을 재 시도하는 가장 좋은 방법을 찾으려고 노력 하면서이 문제를 많이 가지고 있습니다. Retrofit 2를 사용하고 있으므로 내 솔루션은 Retrofit 2입니다. Retrofit 1의 경우 여기에서 허용되는 대답과 같은 인터셉터를 사용해야합니다. @joluet의 대답은 정확하지만 .subscribe (onComplete, onError) 메서드보다 먼저 retry 메서드를 호출해야한다고 언급하지 않았습니다. 이것은 매우 중요합니다. 그렇지 않으면 @joluet 답변에 언급 된 @pocmo처럼 요청이 다시 시도되지 않습니다. 내 예는 다음과 같습니다.

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을 사용하고 있으므로 많은 람다식이 코드 내에 있습니다.


내 버전을 공유하고 싶습니다. 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());
}}

다음과 같이 .subscribe 전에 사용해야합니다.

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

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

작동하는 제품 솔루션.

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

참고 URL : https://stackoverflow.com/questions/24562716/how-to-retry-http-requests-with-okhttp-retrofit

반응형