改造:如何从 okhttp 拦截器再次重试请求?

Posted

技术标签:

【中文标题】改造:如何从 okhttp 拦截器再次重试请求?【英文标题】:Retrofit: How do I retry the request again from okhttp interceptor? 【发布时间】:2021-11-02 21:07:08 【问题描述】:

我创建了一个拦截器。在某些情况下,我想重试请求“n”次,我该怎么做?

class NetworkErrorHandler constructor():  Interceptor 

    //Central error handling block for errors which has impact all over the app
    override fun intercept(chain: Interceptor.Chain): Response 
        val request =  chain.request()
        var response = chain.proceed(request) 

        return  
            when (response.code) 
                401 ->  
                    response
                
                200 ->  
                    response
                
                else ->  
                        var tryCount = 0
                        while (tryCount < 3) 
                            try  
                                response = chain.proceed(request)
                                tryCount++
                            catch (e: java.lang.Exception)
                                tryCount++
                            
                          
                    response
                
             
    

它给了我这个错误:

Suppressed: java.lang.IllegalStateException: network interceptor must call proceed() exactly once

如果是,我必须在这里做吗,然后怎么做?

【问题讨论】:

我想我们不能:square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/… 【参考方案1】:

我认为您从另一个问题 How to retry HTTP requests with OkHttp/Retrofit? 引导自己(如果没有,我建议您这样做)。您的代码的问题是您的 while 仅受 trycount 限制,而不受响应结果的限制。尝试在那里添加关于响应状态的验证,这样它就不会并行执行 .proceed 方法两次。

// try the request
Response response = chain.proceed(request);

int tryCount = 0;
while (!response.isSuccessful() && tryCount < 3) 

【讨论】:

我能够在这一行的帮助下再次发出请求:response = chain.call().clone().execute() 受tryCount以外的response.code的状态码限制。 第一条评论的解决方案是可能的,但你最终会调用 3 次服务,如果这对你来说没问题,那就完美了。 对于限制,我的意思是在 while 本身,而不是在第一次调用和 while 内的调用之间,我不确定,但我认为编译器会看到有没有限制再次执行while,而如果您放置响应,编译器知道它必须等待调用完成才能继续下一次迭代 我明白你的意思,删除 while 并使用 if 不是更好吗?请在下面查看我的答案。【参考方案2】:

所以我可以通过使用这条线从拦截器进行另一个调用

response.close()      
chain.call().clone().execute()
                

根据问题的完整代码:

//Central error handling block for errors which has impact all over the app
class NetworkErrorHandler constructor(): Interceptor 

        var tryCount = 0
        override fun intercept(chain: Interceptor.Chain): Response 
            val request = chain.request()
            var response = chain.proceed(request)

            return
            when(response.code) 
                401 - > 
                    response
                
                200 - > 
                    response
                
                else - > 

                    if (tryCount < 3) 
                        Thread.sleep(2000)
                        tryCount++
                        response.close()
                        chain.call().clone().execute()

                    
                    response.newBuilder()
                    .code(401) // Whatever code
                    .body("".toResponseBody(null)) // Whatever body
                    .protocol(Protocol.HTTP_2)
                    .message("Network Error")
                    .request(chain.request())
                    .build()
                
            
         

【讨论】:

【参考方案3】:

这就是我的处理方式:

@Slf4j
public class HttpRetryInterceptor implements Interceptor 
    @Override
    public @NotNull Response intercept(Chain chain) throws IOException 
        log.error("Making request for the first time.");
        var request = chain.request();
        Response response = null;
        boolean responseOK = false;
        byte tryCount = 0;
        while (!responseOK && tryCount < 3) 
            try 
                Thread.sleep(5000 * tryCount);
                response = chain.proceed(request);
                log.info("Response is: ",response);
                log.info("Response message: ",response.message());
                responseOK = response.isSuccessful();

            catch (Exception e)
                e.printStackTrace();
                log.error("Request was not successful:  . Retrying." , tryCount);
            finally
                assert response != null;
                response.close();
                tryCount++;
            
        

        return response != null ? response : chain.proceed(request);
    

然后我将拦截器添加到我的客户端,例如:new OkHttpClient.Builder().addInterceptor(new HttpRetryInterceptor()).build()

【讨论】:

【参考方案4】:

在Failsafe's OkHttp 支持和RetryPolicy 的支持下,这非常容易:

RetryPolicy<Response> retryPolicy = RetryPolicy.<Response>builder()
  .handleResultIf(r -> r.code() != 401 && r.code() != 200)
  .withMaxAttempts(3)
  .build();
Call call = client.newCall(request);
FailsafeCall failsafeCall = FailsafeCall.with(retryPolicy).compose(call);

// Execute with retries
Response response = failsafeCall.execute();

【讨论】:

以上是关于改造:如何从 okhttp 拦截器再次重试请求?的主要内容,如果未能解决你的问题,请参考以下文章

42. OkHttp总结

如何使用 OkHttp/Retrofit 重试 HTTP 请求?

OkHttp的学习

改造 - 是不是可以避免从应用程序拦截器调用实际 api 并返回硬编码响应?

OkHttp源码分析:五大拦截器详解

Android开发老生新谈:从OkHttp原理看网络请求