okhttp源碼分析(二)-RetryAndFollowUpInterceptor過濾器

1.okhttp源碼分析(一)——基本流程(超詳細(xì))
2.okhttp源碼分析(二)——RetryAndFollowUpInterceptor過濾器
3.okhttp源碼分析(三)——CacheInterceptor過濾器
4.okhttp源碼分析(四)——ConnectInterceptor過濾器
5.okhttp源碼分析(五)——CallServerInterceptor過濾器

前言

緊接著上一篇基本流程分析完后,準(zhǔn)備的是將過濾器分析分析复局,過濾器可以說是OkHttp的點(diǎn)睛之筆。這一篇主要分析RetryAndFollowUpInterceptor這個(gè)過濾器,首先從名字我們就可以明白這個(gè)過濾器的職責(zé)是重試和重定向孕豹。

分析

1.宏觀流程

讀過上一篇博客的人應(yīng)該都清楚看一個(gè)過濾器的功能集嵌,重點(diǎn)都在他重寫Interceptor的intercept(Chain chain)方法妻味。
打開源碼突然發(fā)現(xiàn)這么長(zhǎng)。乍炉。。不要絕望滤馍,我按照我的理解將代碼處理一下岛琼,我們先從流程看起,至少?gòu)暮暧^上要對(duì)這個(gè)過濾器有個(gè)認(rèn)識(shí)巢株。

@Override public Response intercept(Chain chain) throws IOException {
    槐瑞。。阁苞。
    while (true) {
    困檩。。猬错。
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
      }
    窗看。。倦炒。
    if(滿足條件){
        return response;
    }
    显沈。。逢唤。
      //不滿足條件拉讯,一頓操作,賦值再來鳖藕!
      request = followUp;
      priorResponse = response;
    }
  }

其實(shí)就流程來上魔慷,我認(rèn)為宏觀上代碼縮減到這樣就夠了,甚至可以再刪點(diǎn)著恩,這里先從流程上理解.
其實(shí)代碼成這樣院尔,基本上大家都能理解了,一個(gè)while(true)表明這是個(gè)循環(huán)體喉誊,循環(huán)體主要做的事可以看到其實(shí)是遞歸的主要方法邀摆。

response = realChain.proceed(request, streamAllocation, null, null);

執(zhí)行了這個(gè)方法后,就會(huì)交給下一個(gè)過濾器繼續(xù)執(zhí)行伍茄,所以單從這里來看栋盹,我們可以簡(jiǎn)單的理解為這個(gè)過濾器其實(shí)沒做什么。
但是當(dāng)出現(xiàn)了一些問題敷矫,導(dǎo)致不滿足條件的時(shí)候例获,就需要進(jìn)行一系列的操作汉额,重新復(fù)制Request,重新請(qǐng)求榨汤,這也就是while的功能蠕搜,對(duì)應(yīng)的也就是這個(gè)過濾器的主要功能:重試和重定向。
這里我們宏觀上已經(jīng)對(duì)RetryAndFollowUpInterceptor有了一個(gè)基本的理解了件余。

2.過程細(xì)節(jié)

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    //streamAllocation的創(chuàng)建位置
    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
        call, eventListener, callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      //取消
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        //重試讥脐。。啼器。
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        //先判斷當(dāng)前請(qǐng)求是否已經(jīng)發(fā)送了
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        //同樣的重試判斷
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        //重試旬渠。。端壳。
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        //沒有捕獲到的異常告丢,最終要釋放
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      //這里基本上都沒有講,priorResponse是用來保存前一個(gè)Resposne的损谦,這里可以看到將前一個(gè)Response和當(dāng)前的Resposne
      //結(jié)合在一起了岖免,對(duì)應(yīng)的場(chǎng)景是,當(dāng)獲得Resposne后照捡,發(fā)現(xiàn)需要重定向颅湘,則將當(dāng)前Resposne設(shè)置給priorResponse,再執(zhí)行一遍流程栗精,
      //直到不需要重定向了闯参,則將priorResponse和Resposne結(jié)合起來。
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }
      //判斷是否需要重定向,如果需要重定向則返回一個(gè)重定向的Request悲立,沒有則為null
      Request followUp = followUpRequest(response);

      if (followUp == null) {
        //不需要重定向
        if (!forWebSocket) {
          //是WebSocket,釋放
          streamAllocation.release();
        }
        //返回response
        return response;
      }
      //需要重定向鹿寨,關(guān)閉響應(yīng)流
      closeQuietly(response.body());
      //重定向次數(shù)++,并且小于最大重定向次數(shù)MAX_FOLLOW_UPS(20)
      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
      //是UnrepeatableRequestBody, 剛才看過也就是是流類型薪夕,沒有被緩存脚草,不能重定向
      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }
      //判斷是否相同,不然重新創(chuàng)建一個(gè)streamConnection
      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }
      //賦值再來原献!
      request = followUp;
      priorResponse = response;
    }
  }

現(xiàn)在就要從源碼上具體學(xué)習(xí)理解這個(gè)過濾器了馏慨。這里我具體一點(diǎn)一點(diǎn)分析。

//streamAllocation的創(chuàng)建位置
    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
        call, eventListener, callStackTrace);

首先第一個(gè)點(diǎn)姑隅,這里可以看到這里對(duì)streamAllocation進(jìn)行了初始化操作熏纯,其實(shí)在過濾器的鏈?zhǔn)秸{(diào)用的過程中會(huì)陸陸續(xù)續(xù)創(chuàng)建一系列對(duì)應(yīng)的參數(shù),這一點(diǎn)從最初的創(chuàng)建Chain的時(shí)候就可以看出來粤策,可以看到一開始有很多參數(shù)是以null傳入的。

Response getResponseWithInterceptorChain() throws IOException {
    误窖。叮盘。秩贰。
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    。柔吼。毒费。
    return chain.proceed(originalRequest);
  }

這里先大概說一下StreamAllocation這個(gè)對(duì)象是干什么的。這個(gè)類大概可以理解為是處理Connections,Streams,Calls三者之間的關(guān)系愈魏,這一點(diǎn)其實(shí)從構(gòu)造函數(shù)的傳參也可以看出來觅玻。
接下來就要進(jìn)入循環(huán)體中看了,首先可以看到當(dāng)請(qǐng)求被取消的時(shí)候培漏,會(huì)跳出循環(huán)體(第一種跳出的情況)溪厘。

    boolean releaseConnection = true;
    try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        //重試。牌柄。畸悬。
        continue;
      }

接下來看try catch體中的內(nèi)容,try其實(shí)很簡(jiǎn)單珊佣,就是執(zhí)行后續(xù)過濾器鏈中的東西蹋宦,這里要稍微注意一下releaseConnection這個(gè)變量的,對(duì)后續(xù)的判斷理解是有影響的咒锻,可以看到初始化時(shí)將releaseConnection這個(gè)變量賦值為true冷冗。
下面是重點(diǎn)內(nèi)容了:
進(jìn)入catch體中,可以看到會(huì)捕獲很多okHttp自定義的Exception惑艇,從名字上可以有一個(gè)大體上的理解蒿辙,但是還是要從源碼上分析,這里先看第一個(gè)異常RouteException敦捧,先理解理解注釋:嘗試連接一個(gè)路由失敗须板,這個(gè)請(qǐng)求還沒有被發(fā)出,接下來執(zhí)行了一個(gè)方法recover(),這里注意一下false參數(shù)兢卵,現(xiàn)在進(jìn)入方法體中习瑰。

private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
    streamAllocation.streamFailed(e);

    // The application layer has forbidden retries.
    //如果OkHttpClient直接配置拒絕失敗重連,return false
    if (!client.retryOnConnectionFailure()) return false;

    // We can't send the request body again.
    //如果請(qǐng)求已經(jīng)發(fā)送秽荤,并且這個(gè)請(qǐng)求體是一個(gè)UnrepeatableRequestBody類型甜奄,則不能重試。
    //StreamedRequestBody實(shí)現(xiàn)了UnrepeatableRequestBody接口窃款,是個(gè)流類型课兄,不會(huì)被緩存,所以只能執(zhí)行一次晨继,具體可看烟阐。
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;

    // This exception is fatal.
    //一些嚴(yán)重的問題,就不要重試了
    if (!isRecoverable(e, requestSendStarted)) return false;

    // No more routes to attempt.
    //沒有更多的路由就不要重試了
    if (!streamAllocation.hasMoreRoutes()) return false;

    // For failure recovery, use the same route selector with a new connection.
    return true;
  }

可以看到這里面有很多的if判斷,這里先看第一個(gè)蜒茄。

if (!client.retryOnConnectionFailure()) return false;

//==========OkHttpClient.java===========
public boolean retryOnConnectionFailure() {
    return retryOnConnectionFailure;
  }

代碼一放上來其實(shí)就很好理解了唉擂,如果我們?cè)谂渲肙kHttpClient中配置retryOnConnectionFailure屬性為false,表明拒絕失敗重連檀葛,那么這里返回false(第一種拒絕重連的方式)玩祟。這里另外說明一下如果我們默認(rèn)方式創(chuàng)建OkHttpClient的話,retryOnConnectionFailure屬性是true屿聋。

if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;

下面一個(gè)判斷首先要明白參數(shù)的含義空扎,這里requestSendStarted這個(gè)參數(shù)就是剛才在recover方法中的第二個(gè)參數(shù),是為了表明請(qǐng)求是否已經(jīng)被發(fā)送润讥,這里這里為false转锈,但是這個(gè)判斷我們需要了解清楚。
單單從判斷條件我們可以理解為:如果請(qǐng)求已經(jīng)發(fā)送象对,并且這個(gè)請(qǐng)求體是一個(gè)UnrepeatableRequestBody類型黑忱,則不能重試(第二種拒絕重連的方式)。
這里就要說明一下UnrepeatableRequestBody這個(gè)類了勒魔。

public interface UnrepeatableRequestBody {
}

這就是UnrepeatableRequestBody的源碼甫煞,沒有看錯(cuò)...就是一個(gè)空的接口,作用就是標(biāo)記那些不能被重復(fù)請(qǐng)求的請(qǐng)求體冠绢,這時(shí)候可能就想要了解一下那些請(qǐng)求是不能被重復(fù)請(qǐng)求的哪抚吠?看一下那些Request實(shí)現(xiàn)了這個(gè)接口,結(jié)果會(huì)發(fā)現(xiàn)弟胀,到目前Okhttp源碼中楷力,只有一種請(qǐng)求實(shí)現(xiàn)了這個(gè)接口,那就是StreamedRequestBody孵户。

/**
 * This request body streams bytes from an application thread to an OkHttp dispatcher thread via a
 * pipe. Because the data is not buffered it can only be transmitted once.
 */
  final class StreamedRequestBody extends OutputStreamRequestBody implements UnrepeatableRequestBody {}

從這個(gè)類的注釋我們也可以理解萧朝,StreamedRequestBody實(shí)現(xiàn)了UnrepeatableRequestBody接口,是個(gè)流類型夏哭,不會(huì)被緩存检柬,所以只能執(zhí)行一次。

if (!isRecoverable(e, requestSendStarted)) return false;

//=====================isRecoverable()=====================
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
    // If there was a protocol problem, don't recover.
    //如果是協(xié)議問題竖配,不要在重試了
    if (e instanceof ProtocolException) {
      return false;
    }

    // If there was an interruption don't recover, but if there was a timeout connecting to a route
    // we should try the next route (if there is one).
    if (e instanceof InterruptedIOException) {
      //超時(shí)問題何址,并且請(qǐng)求還沒有被發(fā)送,可以重試
      //其他就不要重試了
      return e instanceof SocketTimeoutException && !requestSendStarted;
    }

    // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
    // again with a different route.
    if (e instanceof SSLHandshakeException) {
      // If the problem was a CertificateException from the X509TrustManager,
      // do not retry.
      //理解為如果是安全原因进胯,就不要重試了
      if (e.getCause() instanceof CertificateException) {
        return false;
      }
    }
    if (e instanceof SSLPeerUnverifiedException) {
      // e.g. a certificate pinning error.
      //安全原因
      return false;
    }

    // An example of one we might want to retry with a different route is a problem connecting to a
    // proxy and would manifest as a standard IOException. Unless it is one we know we should not
    // retry, we return true and try a new route.
    return true;
  }

下面一個(gè)判斷可以總體理解為:如果是一些嚴(yán)重的問題(協(xié)議用爪,安全...),拒絕重試(第三種拒絕重連的方式)
這里可以看到判斷被歸結(jié)到一個(gè)isRecoverable()的方法中胁镐,注釋頁(yè)寫的很清楚偎血,這里嚴(yán)重的情況主要由這幾種:

  • 1.協(xié)議問題诸衔,不能重試。
  • 2.如果是超時(shí)問題颇玷,并且請(qǐng)求沒有被發(fā)送署隘,可以重試,其他的就不要重試了亚隙。
  • 3.安全問題,不要重試违崇。
if (!streamAllocation.hasMoreRoutes()) return false;

//========================StreamAllocation.java=====================
public boolean hasMoreRoutes() {
    return route != null
        || (routeSelection != null && routeSelection.hasNext())
        || routeSelector.hasNext();
  }

下面這個(gè)判斷表明:沒有更多的可以使用的路由阿弃,則不要重試了(第四種拒絕重連的方式)
這里也列出了hasMoreRoutes()方法,可以看到羞延,這里面當(dāng)游標(biāo)在最末尾渣淳,也就是保存的路由的容器已經(jīng)遍歷完了,也就沒辦法繼續(xù)重試了伴箩。這里大概說明一下routeSelection是用List保存的入愧。

catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        //重試。嗤谚。棺蛛。
        continue;
      }

所以當(dāng)以上判斷結(jié)束后,如果需要重試巩步,則continue旁赊,重新執(zhí)行while循環(huán)體,也就是發(fā)揮了這個(gè)過濾器的作用椅野,重試

catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        //先判斷當(dāng)前請(qǐng)求是否已經(jīng)發(fā)送了
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        //同樣的重試判斷
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        //重試终畅。。竟闪。
        continue;
      }

這時(shí)候看一下下一個(gè)異常IOException离福,首先可以看到,需要先判斷請(qǐng)求是否已經(jīng)發(fā)送了炼蛤,緊接著繼續(xù)剛才分析的方法recover(),這時(shí)默認(rèn)傳的就不是false妖爷,而是判斷得到的requestSendStarted。最后同樣當(dāng)需要重試時(shí)鲸湃,continue赠涮。

finally {
        // We're throwing an unchecked exception. Release any resources.
        //沒有捕獲到的異常,最終要釋放
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

finally體中的內(nèi)容比較好理解暗挑,由于releaseConnection初始化為true笋除,而當(dāng)正常執(zhí)行realChain.proceed或在執(zhí)行過程中捕捉到異常時(shí)設(shè)置為false,所以當(dāng)執(zhí)行過程中捕捉到?jīng)]有檢測(cè)到的異常時(shí)炸裆,需要釋放一些內(nèi)容垃它。(此處感謝@messi_wpy的指正)

if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

接下來這段代碼一開始是我比較難以理解的,而且網(wǎng)上其他分析這個(gè)過濾器的都沒有分析這塊,最后自己分析国拇,理解為priorResponse是用來保存前一個(gè)Resposne的洛史,這里可以看到將前一個(gè)Response和當(dāng)前的Resposne結(jié)合在一起了。對(duì)應(yīng)的場(chǎng)景是:當(dāng)獲得Resposne后酱吝,發(fā)現(xiàn)需要重定向也殖,則將當(dāng)前Resposne設(shè)置給priorResponse,再執(zhí)行一遍流程务热,直到不需要重定向了忆嗜,則將priorResponse和Resposne結(jié)合起來。

   Request followUp = followUpRequest(response);

//=========================followUpRequest()==============================
private Request followUpRequest(Response userResponse) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    Connection connection = streamAllocation.connection();
    Route route = connection != null
        ? connection.route()
        : null;
    int responseCode = userResponse.code();

    final String method = userResponse.request().method();
    switch (responseCode) {
      case HTTP_PROXY_AUTH:
        Proxy selectedProxy = route != null
            ? route.proxy()
            : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        return client.proxyAuthenticator().authenticate(route, userResponse);

      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        if (!client.followRedirects()) return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        // Don't follow redirects to unsupported protocols.
        if (url == null) return null;

        // If configured, don't follow redirects between SSL and non-SSL.
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // Most redirects don't include a request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
          if (!maintainBody) {
            requestBuilder.removeHeader("Transfer-Encoding");
            requestBuilder.removeHeader("Content-Length");
            requestBuilder.removeHeader("Content-Type");
          }
        }

        // When redirecting across hosts, drop all authentication headers. This
        // is potentially annoying to the application layer since they have no
        // way to retain them.
        if (!sameConnection(userResponse, url)) {
          requestBuilder.removeHeader("Authorization");
        }
        //重新構(gòu)造了一個(gè)Request
        return requestBuilder.url(url).build();

      case HTTP_CLIENT_TIMEOUT:
        // 408's are rare in practice, but some servers like HAProxy use this response code. The
        // spec says that we may repeat the request without modifications. Modern browsers also
        // repeat the request (even non-idempotent ones.)
        if (!client.retryOnConnectionFailure()) {
          // The application layer has directed us not to retry the request.
          return null;
        }

        if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
          return null;
        }

        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
          // We attempted to retry and got another timeout. Give up.
          return null;
        }

        return userResponse.request();

      default:
        return null;
    }
  }

下面這行代碼主要是對(duì)followUpRequest()這個(gè)方法的理解崎岂,代碼我也粘出來了捆毫,這里其實(shí)沒必要在意每一行代碼,這樣反而影響我們閱讀冲甘,這里主要可以觀察發(fā)現(xiàn)绩卤,其實(shí)這個(gè)方法的主要操作就是,當(dāng)返回碼滿足某些條件時(shí)就重新構(gòu)造一個(gè)Request江醇,不滿足就返回null,所以接下來的代碼就很容易理解了濒憋。

if (followUp == null) {
        //不需要重定向
        if (!forWebSocket) {
          //是WebSocket,釋放
          streamAllocation.release();
        }
        //返回response
        return response;
      }

當(dāng)不需要重定向,也就是返回的為null,直接返回response嫁审。

      //需要重定向跋炕,關(guān)閉響應(yīng)流
      closeQuietly(response.body());
      //重定向次數(shù)++,并且小于最大重定向次數(shù)MAX_FOLLOW_UPS(20)
      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
      //是UnrepeatableRequestBody, 剛才看過也就是是流類型律适,沒有被緩存辐烂,不能重定向
      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }
      //判斷是否相同,不然重新創(chuàng)建一個(gè)streamConnection
      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }
      //賦值再來捂贿!
      request = followUp;
      priorResponse = response;

//==========================sameConnection()======================
private boolean sameConnection(Response response, HttpUrl followUp) {
    HttpUrl url = response.request().url();
    return url.host().equals(followUp.host())
        && url.port() == followUp.port()
        && url.scheme().equals(followUp.scheme());
  }

下面的代碼當(dāng)然就是當(dāng)返回的不為空纠修,也就是重新構(gòu)造了一個(gè)Request,需要重定向厂僧。

  • 1.首先關(guān)閉響應(yīng)流扣草。
  • 2.增加重定向的次數(shù),保證小于最大重定向次數(shù)MAX_FOLLOW_UPS(20)
  • 3.不能是UnrepeatableRequestBody類型颜屠,剛才也分析過辰妙,是一個(gè)空接口,用于標(biāo)記那些只能請(qǐng)求一次的請(qǐng)求甫窟。
  • 4.判斷是否相同密浑,如果不相同,則需要重新創(chuàng)建一個(gè)streamConnection粗井。
  • 5.重新賦值尔破,結(jié)束當(dāng)前循環(huán)街图,繼續(xù)while循環(huán),也就是執(zhí)行重定向請(qǐng)求懒构。

總結(jié)

到此餐济,RetryAndFollowUpInterceptor這個(gè)過濾器已經(jīng)大體分析完了,總體流程下來可以發(fā)現(xiàn)胆剧,這個(gè)過濾器的主要作用就是用于對(duì)請(qǐng)求的重試和重定向的絮姆。
其中拒絕重試的判斷條件有如下幾種:

  • 1.如果我們?cè)谂渲肙kHttpClient中配置retryOnConnectionFailure屬性為false,表明拒絕失敗重連秩霍,那么這里返回false
  • 2.如果請(qǐng)求已經(jīng)發(fā)送滚朵,并且這個(gè)請(qǐng)求體是一個(gè)UnrepeatableRequestBody類型,則不能重試
  • 3.如果是一些嚴(yán)重的問題(協(xié)議前域,安全...),拒絕重試
  • 4.沒有更多的可以使用的路由韵吨,則不要重試了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末匿垄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子归粉,更是在濱河造成了極大的恐慌椿疗,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糠悼,死亡現(xiàn)場(chǎng)離奇詭異届榄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)倔喂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門铝条,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人席噩,你說我怎么就攤上這事班缰。” “怎么了悼枢?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵埠忘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我馒索,道長(zhǎng)莹妒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任绰上,我火速辦了婚禮旨怠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渔期。我一直安慰自己运吓,他們只是感情好渴邦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拘哨,像睡著了一般谋梭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倦青,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天瓮床,我揣著相機(jī)與錄音,去河邊找鬼产镐。 笑死隘庄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的癣亚。 我是一名探鬼主播丑掺,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼述雾!你這毒婦竟也來了街州?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤玻孟,失蹤者是張志新(化名)和其女友劉穎唆缴,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黍翎,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡面徽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匣掸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趟紊。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖碰酝,靈堂內(nèi)的尸體忽然破棺而出织阳,到底是詐尸還是另有隱情,我是刑警寧澤砰粹,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布唧躲,位于F島的核電站,受9級(jí)特大地震影響碱璃,放射性物質(zhì)發(fā)生泄漏弄痹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一嵌器、第九天 我趴在偏房一處隱蔽的房頂上張望肛真。 院中可真熱鬧,春花似錦爽航、人聲如沸蚓让。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)历极。三九已至窄瘟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趟卸,已是汗流浹背蹄葱。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锄列,地道東北人图云。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像邻邮,于是被迫代替她去往敵國(guó)和親竣况。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容