1. 程式人生 > >OKhttp原始碼解析---攔截器之RetryAndFollowUpInterceptor

OKhttp原始碼解析---攔截器之RetryAndFollowUpInterceptor

我們看下它的intercept

public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()));

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

      Response response = null;
      boolean releaseConnection = true;
      try {
        response = ((RealInterceptorChain) chain).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(), true, request)) throw e.getLastConnectException();
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        if (!recover(e, false, 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.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                .body(null)
                .build())
            .build();
      }

      Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      closeQuietly(response.body());

      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(
            client.connectionPool(), createAddress(followUp.url()));
      } else if (streamAllocation.stream() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }
  }

這裡首先建立了一個StreamAllocation,StreamAllocation是用來做連線分配的,傳遞的引數有兩個,一個是前面建立的連線池,另外一個是呼叫createAddress建立的Address
private Address createAddress(HttpUrl url) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (url.isHttps()) {
      sslSocketFactory = client.sslSocketFactory();
      hostnameVerifier = client.hostnameVerifier();
      certificatePinner = client.certificatePinner();
    }

    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
        client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
  }

 public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory,
      SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier,
      CertificatePinner certificatePinner, Authenticator proxyAuthenticator, Proxy proxy,
      List<Protocol> protocols, List<ConnectionSpec> connectionSpecs, ProxySelector proxySelector) {
    this.url = new HttpUrl.Builder()
        .scheme(sslSocketFactory != null ? "https" : "http")
        .host(uriHost)
        .port(uriPort)
        .build();

    if (dns == null) throw new NullPointerException("dns == null");
    this.dns = dns;

    if (socketFactory == null) throw new NullPointerException("socketFactory == null");
    this.socketFactory = socketFactory;

    if (proxyAuthenticator == null) {
      throw new NullPointerException("proxyAuthenticator == null");
    }
    this.proxyAuthenticator = proxyAuthenticator;

    if (protocols == null) throw new NullPointerException("protocols == null");
    this.protocols = Util.immutableList(protocols);

    if (connectionSpecs == null) throw new NullPointerException("connectionSpecs == null");
    this.connectionSpecs = Util.immutableList(connectionSpecs);

    if (proxySelector == null) throw new NullPointerException("proxySelector == null");
    this.proxySelector = proxySelector;

    this.proxy = proxy;
    this.sslSocketFactory = sslSocketFactory;
    this.hostnameVerifier = hostnameVerifier;
    this.certificatePinner = certificatePinner;
  }
更加client和請求的相關資訊初始化了 Address

再看下 StreamAllocation的建立

  public StreamAllocation(ConnectionPool connectionPool, Address address) {
    this.connectionPool = connectionPool;
    this.address = address;
    this.routeSelector = new RouteSelector(address, routeDatabase());
  }
這裡儲存了前面傳過來的連線池和地址,並建立了一個RouteSelector,並進行了路由的一個選擇

回到intercept,進入while迴圈

1、首先檢視請求是否已經取消

2、呼叫RealInterceptorChain的proceed處理這個請求並把剛建立的StreamAllocation傳遞進去

3、如果前面第二步沒有出現異常,則說明請求完成,設定releaseConnection為false,出現異常則將releaseConnection置為true,並釋放前面建立的StreamAllocation

4、priorResponse不為空,則說明前面已經獲取到了響應,這裡會結合當前獲取的Response和先前的Response

5、呼叫followUpRequest檢視響應是否需要重定向,如果不需要重定向則返回當前請求

6、重定向次數+1,並且判斷StreamAllocation是否需要重新建立

7、重新設定request,並把當前的Response儲存到priorResponse,繼續while迴圈

我們看下對是否需要重定向的判斷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;

        // Redirects don't include a request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            requestBuilder.method(method, null);
          }
          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");
        }

        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 (userResponse.request().body() instanceof UnrepeatableRequestBody) {
          return null;
        }

        return userResponse.request();

      default:
        return null;
    }
  }
這裡主要是根據響應碼,檢視是否需要重定向,並重新設定請求

這樣RetryAndFollowUpInterceptor攔截器就分析完了,下一個攔截器的啟動是通過呼叫RealInterceptorChain的proceed

public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpStream != null && !sameConnection(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpStream != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpStream, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpStream != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }
這裡index為1,建立的RealInterceptorChain的index為2,獲取到的攔截器是BridgeInterceptor,下一篇我們分析它的intercept方法

相關推薦

OKhttp原始碼解析---攔截RetryAndFollowUpInterceptor

我們看下它的intercept public Response intercept(Chain chain) throws IOException { Request request = chain.request(); streamAllocation

OKhttp原始碼解析---攔截CallServerInterceptor

CallServerInterceptor負責傳送請求和獲取響應,我們看下它的intercept @Override public Response intercept(Chain chain) throws IOException { HttpStream ht

OkHttp中Interceptor攔截公共引數請求封裝

前言 之前在面試的時候遇到這樣的一個問題,那就是如果app中所有的請求都要加入一些引數(例如 版本號、手機號、登入使用者名稱、token等。。。)那麼怎麼做才能實現,而不至於在每次請求的時候都去進行新增這些請求頭。其實這個問題,剛開始我是拒絕的(之前沒有遇到過

Okhttp原始碼解析Interceptor(攔截)

上一篇部落格,我們一步步瞭解了Okhttp網路請求的基本流程以及原始碼解析,最後我們發現核心的問題都放在了一系列的攔截器上面,本章我就會對各個攔截器一一一結合原始碼去理解okhttp的網路請求是如何獲取到網路資料的。上篇部落格的地址:(http://blog.c

OKHttp原始碼解析(4)----攔截CacheInterceptor

簡介 Serves requests from the cache and writes responses to the cache. 快取攔截器,負責讀取快取直接返回、更新快取。當網路請求有符合要求的Cache時,直接返回Cache。如果當前Cache失效,則刪除。CacheStrategy:快取策略

OKHttp原始碼解析(6)----攔截CallServerInterceptor

系列文章 OKHttp原始碼解析(1)----整體流程 OKHttp原始碼解析(2)----攔截器RetryAndFollowUpInterceptor OKHttp原始碼解析(3)----攔截器BridgeInterceptor OKHttp原始碼解析(4)----攔截器CacheIntercept

(4.2.36.4)HTTPOkHttp(四): OkHttp原始碼解析

原始碼開始之前我先貼一段OkHttp請求網路的例項 OkHttpClient mOkHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .url("

Struts2 原始碼分析-----攔截原始碼解析 --- ParametersInterceptor

ParametersInterceptor攔截器其主要功能是把ActionContext中的請求引數設定到ValueStack中,如果棧頂是當前Action則把請求引數設定到了Action中,如果棧頂是一個model(Action實現了ModelDriven介面)則把引數設定到了model中。 下面是該攔截

ABP中的攔截AuditingInterceptor

esc oot on() users resource self ini intercept 日誌文件   在上面兩篇介紹了ABP中的ValidationInterceptor之後,我們今天來看看ABP中定義的另外一種Interceptor即為AuditingInterce

Okhttp的系統攔截

目錄 1.系統攔截器作用及執行順序 2.原始碼驗證執行順序 3.原始碼驗證各個攔截器的作用 1)RetryAndFollowUpInterceptor 2)BridgeInterceptor 3)CacheInterceptor 4)ConnectInterceptor

Mybatis攔截資料許可權過濾與分頁整合

解決方案之改SQL 原sql SELECT a.id AS "id", a.NAME AS "name", a.sex_cd AS "sexCd", a.org_id AS "orgId", a.STATUS AS "status", a.create_org_id AS "createOrgId"

Andriod 網路框架 OkHttp 原始碼解析

1、OkHttp 的基本使用 OkHttp 是 Square 的一款應用於 Android 和 Java 的 Http 和 Http/2 客戶端。使用的時候只需要在 Gradle 裡面加入下面一行依賴即可引入: implementation 'com.squareup.okhttp3:okhttp:3.1

okHttp的日誌攔截

日誌攔截器的分類: a.網路攔截器 b.應用攔截器 建立一個類 /** * 日誌攔截器類,請求來了,先在這裡進行處理,可以得到發請求到得到請求消耗多久的時間 * 作用:可以排查網路請求速度慢的根本原因 * 1.有可能是我們在請求網路時,客戶端寫了一堆業務邏輯 * 2.有可能

rxJava和rxAndroid原始碼解析系列四subscribeOn和observeOn的理解(學習終結篇)

本篇文章主要解決subscribeOn和observeOn這兩個方法為什麼subscribeOn只有一次有效果,observeOn切換多次回撥的都有效果。 不知道朋友有沒有看過rxandroid的原始碼,如果看過的話,就會迎刃而解,沒什麼疑慮啦。沒看過原始碼的朋友,可以看看我這個系列的前幾篇文章

rxJava和rxAndroid原始碼解析系列二observer訂閱

建立完Observable物件後,以後一步ObservableObserveOn.subscribe(new Observer<String>() {.....})這一步又發生了什麼呢? 接著跟蹤原始碼。 @SchedulerSupport(Schedu

OkHttp原始碼解析(一)

簡單使用 OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); Request request = new Request.Builder() .url("www.bai

OkHttp原始碼解析(二)

上一篇講到OkHttp的整體流程,但是裡面有一個很重要的方法getResponseWithInterceptorChain還沒有講到,這個方法裡面包括了整個okHttp最重要的攔截器鏈,所以我們今天來講解一下。 Response getResponseWithI

okhttp原始碼解析

前言 http請求的功能應該是很簡單,只是為了魯棒性和效能需要寫很多的程式碼,發現okhttp還是挺複雜的,但是我們這裡還是要好好的搞定他。 正文 我們從最簡單的使用開始 OkHttpClient client = new OkHttpClient(); Req

徹底理解OkHttp - OkHttp 原始碼解析OkHttp的設計思想

OkHttp 現在統治了Android的網路請求領域,最常用的框架是:Retrofit+okhttp。OkHttp的實現原理和設計思想是必須要了解的,讀懂和理解流行的框架也是程式設計師進階的必經之路,程式碼和語言只是工具,重要的是思想。 在OKhttp 原始碼解析之前,我們必須先要了解http的相

OkHttp 原始碼解析(1)

專案用到OkHttp,準備研究研究(OkHttp現在很火啊,Retrofit使用OkHttp,Volley支援替換底層http棧為OkHttp,甚至Google的最新原始碼裡,都用起了OkHttp,替換了原來用的HttpClient)。 OkHttp在網路