OkHttp 源碼解析(二)攔截器原理分析

1、前言

在上一篇 OkHttp 請求流程分析中,分別介紹了同步和異步請求的具體流程,也瞭解到具體的的網路請求時經過 OkHttp 中的 RealCall 的攔截器鏈實現的,還沒了解的朋友建議去看下上篇文章。java

OkHttp 的攔截器總共分爲如下幾種: 緩存

綠色的部分是咱們用戶能夠本身去定義添加的,不設置就沒有,藍色的部分是 OkHttp 內部實現的,不設置也有,而且不可更改。服務器

下面依次進行講解分析:cookie

2、用戶自定義攔截器之-應用攔截器

應用攔截器的使用:網絡

  1. 建立應用攔截器

  1. 設置攔截器app

這樣就添加了一個很普通的應用攔截器。less

官方對應用攔截器的描述異步

  1. Don’t need to worry about intermediate responses like redirects and retries.
  2. Are always invoked once, even if the HTTP response is served from the cache.
  3. Observe the application’s original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
  4. Permitted to short-circuit and not call Chain.proceed().
  5. Permitted to retry and make multiple calls to Chain.proceed().

中文:socket

  1. 不須要關心是否重定向或失敗重連
  2. 即便有緩存,也只會調用一次
  3. 只考慮應用的初始意圖,不去考慮Okhhtp注入的Header好比:if-None-Match,意思就是無論其餘外在因素只考慮最終的返回結果
  4. 容許切斷其餘攔截器,不調用 Chain.proceed()
  5. 能夠執行屢次調用其餘攔截器,經過Chain.proceed()

3、RetryAndFollowUpInterceptor 攔截器

RetryAndFollowUpInterceptor 攔截器是應用攔截器以後的第一個攔截器,主要是負責失敗重連和重定向的。ide

可是並非全部的網絡請求能夠進行重連的,在RetryAndFollowUpInterceptor 攔截器的內部會經過必定的邏輯進行判斷。具體來看下源碼是怎麼實現的:

根據上一篇文章中講述的,主要邏輯存在 intercept 方法中:

@Override public Response intercept(Chain chain) throws IOException {
  // 獲取原始請求
  Request request = chain.request();
	// 建立 StreamAllocation對象,這裏只是建立,具體處理交給後面的攔截器處理
  // 主要用戶獲取鏈接服務端的 Connect鏈接 和 用於服務端用於數據傳輸的輸入輸出流
  // 主要給 ConnectIntercept 使用的
  streamAllocation = new StreamAllocation(
      client.connectionPool(), createAddress(request.url()), callStackTrace);
  
  int followUpCount = 0;
  Response priorResponse = null;
  // 開啓死循環
  while (true) {
    // 若是取消,就釋放資源
    if (canceled) {
      streamAllocation.release();
      throw new IOException("Canceled");
    }

    Response response = null;
    boolean releaseConnection = true;
    try {
      // 傳入建立的StreamAllocation對象,交給下個攔截器處理,至於下個攔截器處理不處理,不用管
      // 在 catch 和 finally 中作了一些異常處理和資源釋放
      response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
      releaseConnection = false;
    } catch (RouteException e) {
       // 若是進入 RouteException 路由異常,則嘗試是否能夠從新進行請求,若能夠則從頭開始新的請求
      // 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) {
      // 如果進入 IOException IO異常,若能夠從新嘗試請求,則從頭開始新的請求
      // An attempt to communicate with a server failed. The request may have been sent.
      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();
      }
    }

    //若是以前發生太重定向,而且 priorResponse 不爲空,則建立新的 響應對象,並將其 body 置位空
    if (priorResponse != null) {
      response = response.newBuilder()
          .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
          .build();
    }

    // 等到一個接收 Response 的HTTP新請求。
    // 這將添加身份驗證、遵循重定向、處理客戶端請求超時。若是後續操做沒必要要或不適用,則返回null。
    Request followUp = followUpRequest(response);

    // 若 followUp 重試請求爲空,返回當前的響應
    if (followUp == null) {
      if (!forWebSocket) {
        streamAllocation.release();
      }
      return response;
    }
    //關閉響應結果
    closeQuietly(response.body());

    // 若是重試次數超過最大值MAX_FOLLOW_UPS,釋放資源,拋出異常
    if (++followUpCount > MAX_FOLLOW_UPS) {
      streamAllocation.release();
      throw new ProtocolException("Too many follow-up requests: " + followUpCount);
    }

    // 若是新請求的 body 屬於不可重複請求的 body,釋放資源
    if (followUp.body() instanceof UnrepeatableRequestBody) {
      streamAllocation.release();
      throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
    }
		// 判斷是不是相同的鏈接,若不相同,釋放 streamAllocation,建立新的 streamAllocation 對象
    if (!sameConnection(response, followUp.url())) {
      streamAllocation.release();
      streamAllocation = new StreamAllocation(
          client.connectionPool(), createAddress(followUp.url()), 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;
  }
}
複製代碼

總結主要做用下:

  1. 建立 StreamAllocation對象
  2. 調用 RealInterceptorChain.proceed() 方法進行下一階段的網絡請求
  3. 根據異常結果或者響應結果判斷是否要進行從新請求
  4. 調用下一個攔截器,返回 Response 給上一個攔截器

4、BridgeInterceptor攔截器

BridgeInterceptor 主要做用就是添加頭部、設置壓縮等。

仍是主要看下 Intercept 中的代碼:

@Override public Response intercept(Chain chain) throws IOException {
  Request userRequest = chain.request();
  Request.Builder requestBuilder = userRequest.newBuilder();
	// 拿到請求體
  RequestBody body = userRequest.body();
  if (body != null) {
    // 設置編碼方式
    MediaType contentType = body.contentType();
    if (contentType != null) {
      requestBuilder.header("Content-Type", contentType.toString());
    }
		// 設置傳輸長度
    long contentLength = body.contentLength();
    if (contentLength != -1) {
      requestBuilder.header("Content-Length", Long.toString(contentLength));
      requestBuilder.removeHeader("Transfer-Encoding");
    } else {
      requestBuilder.header("Transfer-Encoding", "chunked");
      requestBuilder.removeHeader("Content-Length");
    }
  }
	// 設置主機地址
  if (userRequest.header("Host") == null) {
    requestBuilder.header("Host", hostHeader(userRequest.url(), false));
  }
	// 設置鏈接方式,這裏看到默認是開啓持久鏈接的 使用 Keep-Alive
  if (userRequest.header("Connection") == null) {
    requestBuilder.header("Connection", "Keep-Alive");
  }

  // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
  // the transfer stream.
  // 默認不使用gzip 傳輸, 若是咱們添加 gzip 壓縮頭字段,會對傳輸的數據進行編碼解碼處理
  boolean transparentGzip = false;
  if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
    transparentGzip = true;
    requestBuilder.header("Accept-Encoding", "gzip");
  }
	// 設置 cookies
  List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
  if (!cookies.isEmpty()) {
    requestBuilder.header("Cookie", cookieHeader(cookies));
  }
	// 設置 ua
  if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", Version.userAgent());
  }
	// 調用下個攔截器,等待下個攔截器返回結果
  Response networkResponse = chain.proceed(requestBuilder.build());
	// 執行到這裏,下面都是對返回值進行處理
  // 將網絡請求返回的 Response 轉換成用戶可使用的 Response
  HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

  Response.Builder responseBuilder = networkResponse.newBuilder()
      .request(userRequest);
	// 若是請求的時候支持 gzip,而且服務器返回的 response 響應頭也支持 gzip,,而且響應體有值,就要負責解壓工做
  if (transparentGzip
      && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
      && HttpHeaders.hasBody(networkResponse)) {
    GzipSource responseBody = new GzipSource(networkResponse.body().source());
    Headers strippedHeaders = networkResponse.headers().newBuilder()
        .removeAll("Content-Encoding")
        .removeAll("Content-Length")
        .build();
    responseBuilder.headers(strippedHeaders);
    responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
  }

  return responseBuilder.build();
}
複製代碼

總結主要做用:

  1. 將用戶構建好的一個 Request 經過添加一些請求頭的操做,轉換成可以進行網絡訪問的請求
  2. 調用 RealInterceptorChain.proceed() 方法進行下一階段的網絡請求
  3. 將服務器返回的 Response 轉換成用戶可使用的 Response(添加響應頭、解壓 gzip 等)
  4. 調用下一個攔截器,返回 Response 給上一個攔截器

5、CacheInterceptor攔截器

緩存攔截器主要是對網絡請求的緩存進行處理。使用很簡單,主要經過 Cache 類實現的,在建立 OkHttpClient 的時候設置 cache 就行。

// 建立 Cache 類 "app/cache"是緩存的路徑 20*1024*1024 是緩存大小
Cache cache = new Cache(new File("app/cache"), 20 * 1024 * 1024);
OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(15, TimeUnit.SECONDS)
        .writeTimeout(15, TimeUnit.SECONDS)
        .addInterceptor(new ApplicationIntercept())
  			//設置緩存
        .cache(cache)
        .build();
複製代碼

這樣就可以藉助於 OkHttp 幫助咱們實現緩存。

具體看下 CacheInterceptor 的 Intercept 方法:

public final class CacheInterceptor implements Interceptor {
  // 實現緩存功能的 InternalCache 類,下面講解
  final InternalCache cache;

  public CacheInterceptor(InternalCache cache) {
    this.cache = cache;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    // 若是 cache 爲空,則用戶沒設置緩存,直接返回 null
    // 不爲空,設置了緩存,經過 cache 對象的 get 方法去獲取緩存,稱之爲候選緩存
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
		// 建立緩存策略,而且分別獲得 請求 networkRequest,和響應 cacheResponse
    // 裏面維護了一個 Request 和 Response ,關於這個策略,下面分析
    // public final Request networkRequest; 若是不須要網絡訪問,爲 null,不爲 null,須要網絡請求
    // public final Response cacheResponse; 不使用緩存,爲 null,不爲 null,表示可使用本地緩存
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }
		// 若是候選緩存不爲空,而且經過緩存策略拿到的值爲空,說明候選緩存不可用,關閉
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    // 若是網絡不可用,而且沒有緩存,經過構建者模式構建一個 504 的 Response。並返回
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    // 若是不須要網絡,而且有緩存,則返回存儲的緩存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    // 這裏是須要網絡從新請求的
    Response networkResponse = null;
    try {
      // 調用下一個攔截器去獲取 Response
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    // 此時經過網絡請求拿到了 Response ,而且本地有緩存的 response,
    if (cacheResponse != null) {
      // 若是響應碼爲 未修改,直接使用緩存的 Response做爲最終的返回 Response 
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    // 上面都沒返回,根據網絡請求響應生成最終的響應
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

		// 若是返回的 Response 有請求體
    if (HttpHeaders.hasBody(response)) {
      // 經過調用 maybeCache() 方法,進而調用 cache 的 put 寫入用戶緩存 
      CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
      // 寫入緩存
      response = cacheWritingResponse(cacheRequest, response);
    }

    return response;
  }
}
  
複製代碼

緩存策略CacheStrategy:

前面在 Intercept 方法中調用了

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;

// 看下靜態內部類的 Factory 構造方法,主要是讀取傳入的 緩存 Response 的 headers 信息

    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
				//拿到全部響應頭,開始遍歷
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          // 響應頭 key
          String fieldName = headers.name(i);
          // 響應頭 value
          String value = headers.value(i);
          // 有 Date 頭,服務器返回時間信息
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
           // 有 Expires 頭,獲取有效期信息 
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
           // 有 Last-Modified 頭,獲取最後修改時間,
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
           // 有 ETag 頭,獲取資源的版本
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
           // 有 Age 頭,獲取到如今的經歷了多少時間
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }

// 而後調用 get 方法

/** * Returns a strategy to satisfy {@code request} using the a cached response {@code response}. */
public CacheStrategy get() {
  // 返回一個假設可使用網絡請求的緩存策略,也就是 networkRequest 不爲 null
  CacheStrategy candidate = getCandidate();
  // 若是networkRequest 不爲 null,表示須要使用網絡,而且 Request 的 CacheControl 字段設置爲只使用緩存
  // 就須要設置 都爲 null,而後再 CacheInterceptor 中構建錯誤的返回。
  if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
    // We're forbidden from using the network and the cache is insufficient.
    // 返回不請求網絡,不使用緩存
    return new CacheStrategy(null, null);
  }
  return candidate;
}
複製代碼

前面講到的將響應 Response 添加到本地,主要是經過 CacheInterceptor 內部的 InternalCache 實現的,

public interface InternalCache {
  Response get(Request request) throws IOException;
  CacheRequest put(Response response) throws IOException;
  /** * Remove any cache entries for the supplied {@code request}. This is invoked when the client * invalidates the cache, such as when making POST requests. */
  void remove(Request request) throws IOException;
  /** * Handles a conditional request hit by updating the stored cache response with the headers from * {@code network}. The cached response body is not updated. If the stored response has changed * since {@code cached} was returned, this does nothing. */
  void update(Response cached, Response network);
  /** Track an conditional GET that was satisfied by this cache. */
  void trackConditionalCacheHit();
  /** Track an HTTP response being satisfied with {@code cacheStrategy}. */
  void trackResponse(CacheStrategy cacheStrategy);
}
複製代碼

而 InternalCache 是個接口,內部都是調用的 Cache 這個類中的方法,先看下 Cache 類:

public final class Cache implements Closeable, Flushable {
  private static final int VERSION = 201105;
  private static final int ENTRY_METADATA = 0;
  private static final int ENTRY_BODY = 1;
  private static final int ENTRY_COUNT = 2;

  // 這個 InternalCache 是個接口,裏面定義了經常使用的增刪改查等操做
  // 全部的 實現都是經過 外部的 Cache 類的對應方法實現的
  final InternalCache internalCache = new InternalCache() {
    @Override public Response get(Request request) throws IOException {
      return Cache.this.get(request);
    }

    @Override public CacheRequest put(Response response) throws IOException {
      return Cache.this.put(response);
    }

    @Override public void remove(Request request) throws IOException {
      Cache.this.remove(request);
    }

    @Override public void update(Response cached, Response network) {
      Cache.this.update(cached, network);
    }

    @Override public void trackConditionalCacheHit() {
      Cache.this.trackConditionalCacheHit();
    }

    @Override public void trackResponse(CacheStrategy cacheStrategy) {
      Cache.this.trackResponse(cacheStrategy);
    }
  };

  // 硬盤緩存實現
  final DiskLruCache cache;

  /* read and write statistics, all guarded by 'this' */
  int writeSuccessCount;
  int writeAbortCount;
  private int networkCount;
  // 命中次數
  private int hitCount;
  // 請求次數
  private int requestCount;

  public Cache(File directory, long maxSize) {
    this(directory, maxSize, FileSystem.SYSTEM);
  }

  Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
  }
複製代碼

前面在講 LruCache 的時候,既然是緩存就必然伴隨着 插入緩存、更新緩存、刪除緩存、查詢緩存。

先來看下 插入緩存的 put 方法:

CacheRequest put(Response response) {
  // 獲取請求方法
  String requestMethod = response.request().method();
  // 判斷若是是 POST、PATCH、PUT、DELETE、MOVE幾種請求方法,則不支持緩存,移除緩存,返回 null
  if (HttpMethod.invalidatesCache(response.request().method())) {
    try {
      remove(response.request());
    } catch (IOException ignored) {
      // The cache cannot be written.
    }
    return null;
  }
  // 若是不是 get 請求,返回 null。好比 post 不必緩存
  if (!requestMethod.equals("GET")) {
    // Don't cache non-GET responses. We're technically allowed to cache
    // HEAD requests and some POST requests, but the complexity of doing
    // so is high and the benefit is low.
    return null;
  }
  
  if (HttpHeaders.hasVaryAll(response)) {
    return null;
  }
  // 生成 Entry 實體,Entry 封裝了全部的返回值信息
  Entry entry = new Entry(response);
  // 緩存的實際操做,使用DiskLruCache實現硬盤緩存
  DiskLruCache.Editor editor = null;
  try {
    // 建立寫入的 editor。
    // key 值就是 key() 方法生成的,md5 加密,而後換算成 16 進制。
    // ByteString.encodeUtf8(url.toString()).md5().hex();
    editor = cache.edit(key(response.request().url()));
    if (editor == null) {
      return null;
    }
    // 將緩存寫入磁盤,在 writeTo 方法中,將頭部等信息進行緩存,若是是 https 請求,將會作特殊的處理
    entry.writeTo(editor);
    // 傳入 editor 建立一個 CacheRequestImpl
    return new CacheRequestImpl(editor);
  } catch (IOException e) {
    abortQuietly(editor);
    return null;
  }
}
複製代碼

刪除緩存的 remove 方法:

void remove(Request request) throws IOException {
  // 這裏調用 DiskLruCache 中的 remove 方法移除緩存
  cache.remove(key(request.url()));
}
複製代碼

更新緩存的 update 方法:

void update(Response cached, Response network) {
  Entry entry = new Entry(network);
  DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;
  DiskLruCache.Editor editor = null;
  try {
    editor = snapshot.edit(); // Returns null if snapshot is not current.
    if (editor != null) {
      entry.writeTo(editor);
      editor.commit();
    }
  } catch (IOException e) {
    abortQuietly(editor);
  }
}
複製代碼

這裏是先生成一個實體,而後找到保存的快照,而後用執行寫入

查找緩存的 get 方法:

Response get(Request request) {
  //先得到通過 md5 加密後的 url
  String key = key(request.url());
  // DiskLruCache 的快照
  DiskLruCache.Snapshot snapshot;
  // 保存緩存的實體
  Entry entry;
  try {
    // 從 DiskLruCache 中的 get 方法,查找到 快照。
    snapshot = cache.get(key);
    //爲空,則沒緩存
    if (snapshot == null) {
      return null;
    }
  } catch (IOException e) {
    // Give up because the cache cannot be read.
    return null;
  }
  try {
    // 建立Entry
    entry = new Entry(snapshot.getSource(ENTRY_METADATA));
  } catch (IOException e) {
    Util.closeQuietly(snapshot);
    return null;
  }
  // 從 Entry 照片中得到 Response
  Response response = entry.response(snapshot);
  if (!entry.matches(request, response)) {
    Util.closeQuietly(response.body());
    return null;
  }
  return response;
}
複製代碼

6、ConnectInterceptor攔截器

做用主要是打開與服務器之間的連接,正式開始網絡請求。

@Override public Response intercept(Chain chain) throws IOException {
    
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    // 拿到 RealInterceptorChain 中建立的 StreamAllocation 對象。
    StreamAllocation streamAllocation = realChain.streamAllocation();
    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    // 經過 newStream 建立一個 HttpCodec 對象,主要用來編碼 Request 和解碼 Response 的
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    // 得到 RealConnection 的對象 connection。 用來進行實際的 io 傳輸
    RealConnection connection = streamAllocation.connection();
    // 調用下個攔截器
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }


//來看下 newStream 方法

public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
  // 設置超時
  int connectTimeout = client.connectTimeoutMillis();
  int readTimeout = client.readTimeoutMillis();
  int writeTimeout = client.writeTimeoutMillis();
  // 是否重試
  boolean connectionRetryEnabled = client.retryOnConnectionFailure();
  try {
    // 構建 RealConnection 對象
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
    // 構建一個 HttpCodec 對象
    HttpCodec resultCodec = resultConnection.newCodec(client, this);
    synchronized (connectionPool) {
      codec = resultCodec;
      return resultCodec;
    }
  } catch (IOException e) {
    throw new RouteException(e);
  }
}

// 看下 findHealthyConnection

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) throws IOException {
  while (true) {
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        connectionRetryEnabled);
    // If this is a brand new connection, we can skip the extensive health checks.
    synchronized (connectionPool) {
      if (candidate.successCount == 0) {
        return candidate;
      }
    }
    // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
    // isn't, take it out of the pool and start again.
    if (!candidate.isHealthy(doExtensiveHealthChecks)) {
      noNewStreams();
      continue;
    }
    return candidate;
  }
  

複製代碼

在 findHealthyConnection 方法 內部調用了 findConnection 方法,在findConnection 方法內部主要是去找有沒有能夠服用的連接

  • 若是有就會複用鏈接,返回鏈接
  • 若是沒有就建立一個新的鏈接,而後放入到鏈接池中,返回鏈接

7、用戶自定義網絡攔截器攔截器

這個攔截器和應用攔截器都是用戶本身設置的攔截器。使用方法可應用攔截器差很少,這裏就不在講了,看下官方對其描述:

  • Able to operate on intermediate responses like redirects and retries.

  • Not invoked for cached responses that short-circuit the network.

  • Observe the data just as it will be transmitted over the network.

  • Access to the Connection that carries the request.

  • 可以對重定向和重試的時候進行操做(很好理解,重定向什麼的在第二層,應用攔截器不會調用,可是網絡攔截器能夠調用)。

  • 若是讀取緩存中的數據,就不會執行網絡攔截器

  • 能夠檢測到全部須要網絡傳輸的數據

  • 能夠訪問完整的Request 請求

因爲是在重定向攔截器以後的,因此在發生重定向的時候,網絡攔截器可能會執行屢次,能夠較爲完整的檢測網絡請求的情況。

而應用攔截器在請求重試攔截器以前,因此檢測不到重定向的請求,只能檢測到最初的請求和最終的返回。

8、CallServerInterceptor攔截器

CallServerInterceptor攔截器 是攔截器鏈中的最後一個攔截器,主要負責向服務器發送真正的網絡請求和接收服務器返回的響應。

@Override public Response intercept(Chain chain) throws IOException {
  // 獲取在 ConnectInterceptor 攔截器中建立的 httpCodec 對象
  HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
  // 獲取 streamAllocation
  StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
  Request request = chain.request();

  long sentRequestMillis = System.currentTimeMillis();
  // 向 Socket 中寫入請求的頭部信息
  httpCodec.writeRequestHeaders(request);

  Response.Builder responseBuilder = null;
  // 判斷該請求的請求方法是否容許被髮送請求體,請求體是否爲空
  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
    // Continue" response before transmitting the request body. If we don't get that, return what
    // we did get (such as a 4xx response) without ever transmitting the request body.
    if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
      httpCodec.flushRequest();
      responseBuilder = httpCodec.readResponseHeaders(true);
    }

    // Write the request body, unless an "Expect: 100-continue" expectation failed.
    if (responseBuilder == null) {
      Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
      BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
      // 向 Socket 中寫入請求的 body 信息
      request.body().writeTo(bufferedRequestBody);
      bufferedRequestBody.close();
    }
  }

  // 完成網絡請求的寫入工做
  httpCodec.finishRequest();

  // 下面是讀取操做
  if (responseBuilder == null) {
    // 讀取響應的頭部信息
    responseBuilder = httpCodec.readResponseHeaders(false);
  }

  // 構建返回的 Response
  Response response = responseBuilder
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  int code = response.code();
  // 判斷是否返回一個空的響應
  if (forWebSocket && code == 101) {
    // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
    response = response.newBuilder()
        .body(Util.EMPTY_RESPONSE)
        .build();
  } else {
    // 讀取響應中的響應體信息
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }
  // 判斷是否關閉長鏈接
  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }
  // 若是響應的狀態碼爲 204 和 205 而且響應體不爲空,則拋出異常
  if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
    throw new ProtocolException(
        "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
  }

  return response;
}
複製代碼

HttpCodec 是一個接口,主要用來編碼 Request 和解碼 Response 的,主要有兩個實現類:

/**A socket connection that can be used to send HTTP/1.1 messages. */
public final class Http1Codec implements HttpCodec {}
/** Encode requests and responses using HTTP/2 frames. */
public final class Http2Codec implements HttpCodec {}
複製代碼

都是如今主流的 Http 協議規範。

CallServerInterceptor攔截器主要就是完成最終的網絡請求工做,遵循 HTTP 協議規範,經過 HttpCodec 對象寫入請求頭、請求主體,而且讀取響應頭和響應主體,寫入 Response 中,依次返回給上級攔截器,最終傳遞到調用的地方,就完成了依次網絡請求。

9、最後

到這裏 OkHttp 源碼就分析完了,可能有些地方理解的還不是很透徹,繼續加油吧。

歡迎關注個人公衆號:

個人公衆號
相關文章
相關標籤/搜索