從OkHttp的源碼來看HTTP的實現過程

1.OkHttp的歷史:

  • 最初是square以爲android給的那一套方案不是很好用,因而他給作了一下包裝,包裝之後就好用了
    • 慢慢地,他們把httpclient給剔除了,
    • 再後來,他被Google給收購了,
    • 如今咱們用的比較新的android系統(4.4的時候),內部的HttpURLConnection的實現用的實際上是okhttp的代碼。
  • okhttp其實就是原生的從頭至尾實現了http的一個工具,
  • 同時讓你對http的使用方便一點,你想要cash、cookie都比較方便。
  • 他並不僅是一個方便工具,首先他是一個http。
  • 可是tcp這些鏈接的過程他也全都作了
  • 他徹底不依賴Google的那一套東西了。最初依賴,後來不依賴,最後Google把他給收了。

2.是什麼?

  • 看官網介紹square.github.io/okhttp/
    • 他是一個http和http2的client。
    • 在介紹retrofit的時候還提到了他是類型安全的。(retrofit的設計原理(找一找第一次讀源碼的感受))由於他自己就作了一些額外的工做,他接收到的那些數據他會去作處理,會去作轉型,你收到的是一個body,可是你能夠轉成一個User,這個就跟類型安全有關,你怎麼轉我不給你報錯。
    • 而okhttp只是去實現http的,並且他主要作的是下層支持,上層只是比較舒服而已,但他不是一個上層庫。

3.怎麼用?

  • 看官網。
  • 我先建立一個client,而後newcall、execute。
  • 而後咱們android不能用execute,由於這個execute跟retrofit同樣,execute是同步的,是不轉線程的,可是咱們得把他放在後臺線程。我要換另一個方法是enqueue
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

複製代碼
  • 本身寫一個demo,用法特別簡單。
OkHttpClient client = new OkHttpClient();
client.newCall(new Request.Builder().url("http://api.github.com").build())
        .enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                
            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {

            }
        });

複製代碼
  • 第一步把他初始下來,
  • 而後設定我要訪問哪一個url,
  • 這樣的話默認就是一個get請求,你的get不用寫了,默認是get,就好像你在瀏覽器裏面寫同樣。
new Request.Builder().url("http://api.github.com").build()
複製代碼
  • 括號裏面是建立一個request,你每次request都須要從新建立一次,而後他的生命週期,怎麼去調數據,怎麼去進行訪問,返回這結果,這都是okhttp來幫你管理,可是你每次要作的時候,你須要建立一個新的請求,這個請求叫作request。
  • 而後,enqueue就是異步地去作請求。

4.源碼解讀

  • 主要講結構:
    • okhttp是怎樣實現了http,是怎樣實現了tcp,是怎麼實現了https

newcall入手

  • newcall入手,newcall是什麼?
    • 是他建立一個call,client經過這個方法建立一個call,而後用這個call去進行真正的網絡交互了。
    • newcall傳了一個參數,這個參數是request,而這個request是你本身拼出來的。
    • 這個方法就是,傳進來一個request,而後我用這個request建立一個call,建立一個待用的網絡請求。
  • 點進去
@Override public Call newCall(Request request) {
  return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製代碼
  • 他會調用另外一個方法,RealCall.newRealCall(),他會返回一個realcall,
  • 我要的是call,返回的是realcall,很明顯realcall是call的實現。
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  // Safely publish the Call instance to the EventListener.
  RealCall call = new RealCall(client, originalRequest, forWebSocket);
  call.eventListener = client.eventListenerFactory().create(call);
  return call;
}
複製代碼
  • 第一行是建立這個對象, 利用你傳過來的參數,建立一個call
    • client是大總管,總配置的,沒有什麼好解析的。等會細講。
    • originalRequest就是你傳過來的request,他爲何叫originalRequest?
      • 這個跟待會要講到的責任鏈有關了,他是最初的request。在鏈的運轉中會慢慢變得不同。
    • forWebSocket關係就比較遠了,
      • 他是指你這是否是一個WebSocket的call。
      • WebSocket是http的一種擴展。他能在你的http的請求和響應之間作一點手腳,你依然是一個http,可是我作一點手腳,我讓大家的交互,
        • 他是什麼狀態呢?咱們以前說http是一種怎樣的模式,是一種cs模式。是一個客戶端服務器的模式,客戶端發一個請求,服務器接收之後再把這個請求的響應返回來。你再發一個我再返回來。都是那種你來我往,一來一往這樣的交互。服務器是不能主動發請求的。我向客戶端請求數據,或者我直接向客戶端推送,這個作不到。
        • 而WebSocket就是對http作了這樣的擴展。他並無擴展標準,只是在實現上他改了。那麼就可讓服務器去給客戶端作推送了。
        • 可是咱們Android用的都是傳統的http。不多有用到WebSocket這種方式的。WebSocket誰用呢?那些作交易平臺的,不是鹹魚那種交易平臺,而是股票,證券什麼的這種交易平臺,還有虛擬貨幣這種東西,他們須要常常地,頻繁的刷新數據。可是每次都是去輪詢的話,很是的費流量,很是費電,那麼他們會用WebSocket,用這種方式來實現推送。
        • 因此通常都用不到。
  • 第二行,會建立一個eventListener。
    • 你的okhttp他的http過程當中會有一些關鍵的時間點,好比tcp鏈接創建了,全部鏈接都是指的tcp鏈接,或者是ssl鏈接沒有所謂的http鏈接。
      • 什麼叫鏈接?就是,我是一個機器,你是一個機器,咱們兩個交互,我要可以記住你是誰。那麼你給我發消息我直接就知道了, 你不用再說你是誰,這是鏈接。
      • http原本就是無鏈接的,全部鏈接都是指的tcp鏈接,或者是ssl鏈接(安全鏈接,就是https所使用的鏈接)。
    • 鏈接過程會有不少不少狀態,什麼鏈接創建呀、開始請求呀、還有返回響應這些東西,他們都是一些時間點,你能夠記錄在eventListener裏面,用他來作響應作標記。

再看enqueue

  • 點進來
public interface Call extends Cloneable {
...
void enqueue(Callback responseCallback);
...
}
複製代碼
  • 他是一個接口,這個call跟retrofit的call不是一個call,可是他們都是一個接口,那麼我怎麼看他的實現呢?剛剛看到了RealCall是call的實現,我就直接跳到RealCall裏面
@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製代碼
  • 內容很少,往listener裏面加了個事件
eventListener.callStart(this);
複製代碼
  • 另外有這麼一行,他又把東西轉交給別人了。
  • 轉交給一個叫dispatcher的東西,讓他去enqueue了,
  • 而後他也生成一個新的對象AsyncCall,一個異步的call
client.dispatcher().enqueue(new AsyncCall(responseCallback));
複製代碼
  • 那咱們分別看一下dispatcher()和AsyncCall是什麼
public Dispatcher dispatcher() {
  return dispatcher;
}
複製代碼
  • dispatcher返回的是一個Dispatcher對象,那麼我看一下Dispatcher
public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;
  ...
  }
複製代碼
  • Dispatcher實際上是一個管理線程的東西,
    • 你的每個新的請求,新的request和他的response這個過程,你的請求和返回的過程,他是須要一個單獨的線程,這樣你不用的請求之間不會互相被擋着。
    • 怎麼作的呢?靠的是線程控制。那線程控制用的是誰呢?用的就是這個Dispatcher。他的內部實現用的是execute。
  • 暫時只要知道,他是用來管理線程的,有了他,多個線程就能夠被簡單地控制,我要多就能夠多,我要少就能夠少,能夠分配。
private int maxRequests = 64;
 private int maxRequestsPerHost = 5;
複製代碼
  • Dispatcher有兩個默認的東西java

    • maxRequests:
      • 當個人總鏈接達到64的時候,就不去作新的請求了,我等一等,
    • maxRequestsPerHost:
      • 當我對某一個主機的請求達到5個,這個時候我對這個主機不作新的請求了。好比如今我只請求了兩個主機,a主機有兩個請求,b主機有五個請求,這個時候用戶又要往b主機發一個請求,我把她先給放着。
      • 就是防止對某一個網站,對某一個服務器施加過大的壓力。
  • enqueue的過程,是壓到隊列的過程,他並非讓你每個請求按順序執行,而是讓他們分部,按隊列執行,只是到必定程度的時候他們會等一等。他們的程度就是在這設置的。android

  • 這兩個值均可以設置,若是你想讓請求一個一個按順序執行,就能夠把maxRequests設爲1。git

  • 這個時候知道什麼是Dispatcher了,那看一下他的enqueue,剛纔已經說過enqueue是幹什麼了,就是讓他們並列執行,只是到頂峯的時候,再往隊列裏面存一存。程序員

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

複製代碼
  • if分兩步:
    • 若是個人數量沒超限,那麼就直接執行了,
    • 若是我超限了,就放到ready隊列裏面。一個待命的隊列,隨時準備發出請求。
  • 另外看一個東西,他的參數是AsyncCall,這個AsyncCall他作了什麼?
    • 他應該有一個run方法,可是沒有找到,線程控制都是runnable裏面的run方法被執行
final class AsyncCall extends NamedRunnable {...}
複製代碼
  • 這裏面沒有,就去他的父類看。
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}
複製代碼
  • 父類裏面有一個run方法,不過我發現這個run方法仍是執行execute(),注意這個execute()又是另一個類了,跟前面的dispatcher不是一個。是誰?就是RealCall裏的AsyncCall實現的execute()。
@Override protected void execute() {
  boolean signalledCallback = false;
  try {
    Response response = getResponseWithInterceptorChain();
    if (retryAndFollowUpInterceptor.isCanceled()) {
      signalledCallback = true;
      responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
    } else {
      signalledCallback = true;
      responseCallback.onResponse(RealCall.this, response);
    }
  } catch (IOException e) {
    if (signalledCallback) {
      // Do not signal the callback twice!
      Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
    } else {
      eventListener.callFailed(RealCall.this, e);
      responseCallback.onFailure(RealCall.this, e);
    }
  } finally {
    client.dispatcher().finished(this);
  }
}

複製代碼
  • 說到這有好幾層了,其實就是你在外部調用enqueue的時候,最終會來到RealCall裏的AsyncCall實現的execute()方法。
client.newCall(new Request.Builder().url("http://api.github.com").build())
        .enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {

            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {

            }
        });

複製代碼
  • AsyncCall的execute()方法作了什麼,重點全在這個方法裏面:
Response response = getResponseWithInterceptorChain();
複製代碼
  • 獲取響應,經過攔截器的鏈,尚未執行,怎麼就有響應了?都在這裏面,可是往裏面講就很深了,待會說。github

  • 如今總結一下,若是你是用enqueue這種方法,web

    • 那麼他會進入你的realcall,
    • 調用你的dispatcher這個線程管理工具的enqueue,
    • 從而觸發了你的AsyncCall的run方法,再觸發他的execute方法,
    • 最終到了我稍後要講的getResponseWithInterceptorChain()。
  • 那麼這個到這就圓滿了,怎麼圓滿了?你先是把你的請求放到後臺,而後去作實際的網絡請求,而後把網絡請求結果返回回來。這是一個完整的過程。面試

  • 除了enqueue還有一個execute,咱們用得不多,可是不是徹底不用,由於有的時候咱們已經在後臺了。好比,如今個人網絡請求失敗了,網絡請求因爲某種緣由失敗了,好比個人權限不足。我須要如今獲取個人token,去在線獲取個人token,獲取token以後,繼續進行請求,那麼我如今是在後臺的,我就不須要enqueue,我使用execute。算法

  • 那麼我簡單看一下execute作了什麼,就是直接進行網絡請求的狀況。設計模式

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  try {
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    eventListener.callFailed(this, e);
    throw e;
  } finally {
    client.dispatcher().finished(this);
  }
}
複製代碼
  • 直接調用getResponseWithInterceptorChain()。

大體結構出來了

  • 這個就是他的大結構,api

    • 建立一個realcall,
    • enqueue或者execute去進行網絡請求,
    • 而後返回響應。
  • 如今說兩個東西:

    • okhttp在實用的角度,無論在開發的實用,仍是讓咱們去理解okhttp和http他們的緊密關係,以及讓你加深對http的理解,無論是從哪一個角度,須要先看一下okhttpclient這個方法,他裏面的多種配置項,都有什麼?他們有什麼做用?這是一個。
OkHttpClient client = new OkHttpClient();
複製代碼
  • 另外就是去解讀getResponseWithInterceptorChain()這個方法。這個方法是okhttp技術上的核心。若是你想理解他的原理,你想更好地使用他,或者是你想在面試的時候更加帥,這個東西是你須要理解的。

OkHttpClient有些什麼配置

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {...}
複製代碼
  • 說一下他的配置,這些項,有幾個我也不是很清楚,可是我所用過的,以及我沒用過可是我認爲會比較有用的,我都進行了瞭解。
public static final class Builder {
  Dispatcher dispatcher;
  @Nullable Proxy proxy;
  List<Protocol> protocols;
  List<ConnectionSpec> connectionSpecs;
  final List<Interceptor> interceptors = new ArrayList<>();
  final List<Interceptor> networkInterceptors = new ArrayList<>();
  EventListener.Factory eventListenerFactory;
  ProxySelector proxySelector;
  CookieJar cookieJar;
  @Nullable Cache cache;
  @Nullable InternalCache internalCache;
  SocketFactory socketFactory;
  @Nullable SSLSocketFactory sslSocketFactory;
  @Nullable CertificateChainCleaner certificateChainCleaner;
  HostnameVerifier hostnameVerifier;
  CertificatePinner certificatePinner;
  Authenticator proxyAuthenticator;
  Authenticator authenticator;
  ConnectionPool connectionPool;
  Dns dns;
  boolean followSslRedirects;
  boolean followRedirects;
  boolean retryOnConnectionFailure;
  int connectTimeout;
  int readTimeout;
  int writeTimeout;
  int pingInterval;
  ...
  }

複製代碼

1.Dispatcher 線程調度

  • 首先,這個Dispatcher,上面有涉及到了。
  • 他是去控制線程,調度線程,而後使用不一樣的線程進行網絡請求的,
  • 另外他還會有一個性能的平衡,在達到必定數量以後我不作了,我歇一歇,等一等。

2.Proxy 代理

  • 第二個是Proxy,Proxy是你本身能夠配置的一個代理。
  • 對他的理解,其實主要是對概念要有理解。什麼是Proxy?
    • 好比如今我想要訪問一個國外的網站,這個國外的網站因爲一些緣由,從我家的網絡連不過去,而後我須要從另一個地方連過去。國外有兩個服務器,一個是從我家不知道什麼緣由連不到,可是我知道這個服務器並無壞掉,我但願連過去,我從個人主機去連到另一臺主機,這臺主機多是國外的,也多是國內的,無所謂,總之,這臺中介機器,他能夠連到目標機器,那麼我去找個人中介機器,他就是個人代理,我把我要作的事情,我想要訪問誰,我寫得清清楚楚,去交給代理服務器,而後代理服務器把我請求作了,這個就是一個合理的,合法的,合要求,而且我本身清清楚楚他是什麼做用的中間人。

3.Protocol 協議版本

  • Protocol是什麼?點進去就知道了。他是你列給你客戶端okhttpclient,你所支持的協議的版本。這樣你的okhttp他的工做過程當中他就知道哪些是他的選項,他本身會去作調配。
  • SPDY說一下,SPDY跟http2很像,他是http2的一個前身,http2借鑑了SPDY不少東西。spdy初是Google在用的,Google本身用,後來慢慢你們都吸取一下,改進一下,成爲標準。這個標準叫作http2。因此spdy如今其實已經被廢棄了。
public enum Protocol {
  HTTP_1_0("http/1.0"),
  HTTP_1_1("http/1.1"),
  SPDY_3("spdy/3.1"),
  HTTP_2("h2"),
  ...
  }

複製代碼
  • Protocol就是這樣一個東西,因此她應該是一個列表,是多個選項。你的客戶端能夠現場去選擇,就像咱們的瀏覽器,咱們的瀏覽器會同時支持http1.0、1.一、1.二、2.0。可能舊一點的瀏覽器就沒法支持2.0。

4.ConnectionSpec 鏈接標準

  • 看名字,鏈接規格?點進去。
public final class ConnectionSpec {

  // This is nearly equal to the cipher suites supported in Chrome 51, current as of 2016-05-25.
  // All of these suites are available on Android 7.0; earlier releases support a subset of these
  // suites. https://github.com/square/okhttp/issues/1972
  private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
      CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
      CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
      CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
      CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
      CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
      CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,

      // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
      // continue to include them until better suites are commonly available. For example, none
      // of the better cipher suites listed above shipped with Android 4.4 or Java 7.
      CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
      CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
      CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
      CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
      CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
      CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
      CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
  };

  /** A modern TLS connection with extensions like SNI and ALPN available. */
  public static final ConnectionSpec MODERN_TLS = new Builder(true)
      .cipherSuites(APPROVED_CIPHER_SUITES)
      .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
      .supportsTlsExtensions(true)
      .build();

  /** A backwards-compatible fallback connection for interop with obsolete servers. */
  public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS)
      .tlsVersions(TlsVersion.TLS_1_0)
      .supportsTlsExtensions(true)
      .build();

  /** Unencrypted, unauthenticated connections for {@code http:} URLs. */
  public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
  ...
  }
複製代碼
  • 這些就是配置的,你是要使用http仍是https?若是你要是使用https的話,你的版本ssl3.0,仍是tls1.0、1.一、1.2,他也是一個列表。若是你使用的是https的話,你的可用tls也是一個列表。
CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
複製代碼
  • 這個在以前講https的時候有講過(Https究竟是什麼?(Https加密過程全解析)),你在客戶端向服務器去通知你想創建一個安全鏈接的時候,你會發什麼?你會發一個CIPHER_SUITES(密碼組件),CIPHER_SUITES裏面有什麼呢?
    • RSA:非對稱加密算法
    • AES_256:對稱加密算法
    • SHA384:哈希算法
  • 除了CIPHER_SUITES還會發一個tls版本。1.3,1.2,1.1,1.0
public static final ConnectionSpec MODERN_TLS = new Builder(true)
      .cipherSuites(APPROVED_CIPHER_SUITES)
      .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
      .supportsTlsExtensions(true)
      .build();

複製代碼
  • 這些是一個統一的支持,由於他是一個可接受方案,你可以支持什麼,你可以接受什麼?至於對方怎麼給你,對方可以接受什麼,大家最終商討一個方案,他們是固定的。好比最終大家決定要麼用1.1,要麼用1.2,確定不能是既用1.1,又用1.2,可是商討過程,發的確定是一個列表,讓他去選。

  • COMPATIBLE_TLS是給比較舊的服務器用的,通常也不用。

public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS)
    .tlsVersions(TlsVersion.TLS_1_0)
    .supportsTlsExtensions(true)
    .build();
複製代碼
  • CLEARTEXT是什麼意思,須要單獨解釋一下。
public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
複製代碼
  • CLEARTEXT是明文的意思。所謂明文,對於ConnectionSpec,對於這個鏈接標準,徹底等於http。
  • 由於http就是你的數據是透明的,就是敞開的,不加密的。反過來講,什麼狀況下就是明文呢?不加密嘛。

5.interceptors、networkInterceptors

  • 這兩個Interceptor暫時先不說。仍是稍後講到的方法,getResponseWithInterceptorChain()。
  • interceptors和networkInterceptors他們是能夠在這個chain裏面插入的兩個Interceptor列表。他們分別作什麼?有什麼區別?等會再講。

6.eventListenerFactory

  • 這個更不用說了,eventListener是用來作記錄的,factory是用來建立listener的東西。

7.proxySelector

  • 不知道是什麼,沒有用過。

8.cookieJar

  • cookieJar這個東西有的時候真的感受到咱們中國學程序是挺吃虧的。
    • cookieJar這個詞原本根本不用解釋,可是中國人就得解釋一下。
    • 首先cookie是瀏覽器跟服務器之間一個完整的機制,服務器要存東西,他讓服務器來存,是用cookie。
    • 可是實際上cookie他的本意是什麼?小餅乾,曲奇餅乾。
    • 什麼是jar呢?咱們Java程序員看到這,就以爲是java的jar包。java的打包不就是jar嗎?後來android的打包不是aar嗎?jar不就是java的包嗎?咱們會這樣感受,大家cookieJar咱們就理解不了了。但實際上jar是什麼呢?jar是罐子,並且外國的小朋友,有一個本身的餅乾罐,他的媽媽給他用烤箱給他作了不少餅乾以後,吃不完,裝哪?裝在他的cookieJar裏面。
  • 這實際上是一個很是形象的東西,他實際上是一個咱們cookie的存儲器,咱們cookie存到本地,仍是存到內存裏面?這個okhttp是沒有默認實現的。他無論,這根本就跟他的性格有關。okhttp他們是以爲,咱們作客戶端的沒有必要去是實現cookie,那麼大家要實現的話,大家實現吧,我不實現,咱們作android的咱們不用cookie。他們有這樣的性格,可是他會給你實現出來這樣的接口,你能夠本身去發展。大家最終就是這麼一個半生半熟的狀態。能夠用,可是你想用好的話,本身去指定他應該怎麼存,怎麼取。

9.Cache

  • 這個比較好理解,就是咱們http的cash,不過下面的InternalCache,我沒有看懂他是幹嗎的。

10.SocketFactory

  • 咱們作android的人,可能對Socket瞭解不是不少,有些人可能用到,Socket是什麼呢?socket就是咱們tcp的端口,就是一個(ip-端口)對。

    • 有個東西提一下,默認端口是80
    • hencoder.com:80/
    • 另外你也能夠寫成8080、8088,這個你能夠隨便寫,只要不超過他的上限就能夠了。這個端口是tcp端口。
    • 你在解析完以後,可能會給你解析成某個ip
    • https://222.222.222.222:80/
    • 222.222.222.222:80,這一部分他們兩個其實不是一級的
      • 222.222.222.222是ip地址
      • 80的端口
  • SocketFactory就是用來建立這個端口的。什麼叫建立端口呢?

    • 其實就是和對方服務器鏈接,獲取到鏈接端口,獲取到能夠往服務器寫東西,能夠從服務器讀東西的那個所謂的端口,就是這個socket。
  • 同理SSLSocketFactory他是ssl鏈接的factory,爲何要分開呢?

    • 由於socket是tcp的東西,而ssl socket實際上是ssl的東西,都不是一層了。因此他們要分開作。
    • 他們原理都不同。包括創建鏈接的方式也不不同。
    • tcp怎麼創建?三次握手,ssl怎麼創建?
    • 一大堆,什麼密鑰交換呀... 他們過程徹底不同。
  • http是沒有端口的,沒有所謂的http端口。由於他沒有面向鏈接,他不面向鏈接。有時候咱們說的比較順嘴,說什麼會http端口,其實他說的是tcp端口,http是沒有端口的。

11.CertificateChainCleaner

  • 他是咱們從服務器拿下來的那些證書,有時候會包含不少不少內容,會包含好幾個證書。證書嵌證書,一個證書鏈。
  • 有時候發下來會有一些無關的證書。那麼CertificateChainCleaner會作什麼呢?
    • 他會把你的這些東西都整理了,整理完以後就是一個鏈,或者一個序列。
    • 這些序列第一個就是對方網站的證書,而後一個一個往下,最後一個是你信任的本地根證書。
    • 這樣是方便驗證的,可是跟咱們沒有什麼關係,已經很下層了。我只是讓你知道他是什麼。我爲何要讓你知道他是什麼東西,爲何?回顧http。只講原理記不住。你配合代碼會發現,okhttp不就是這麼作的嗎?

12.HostnameVerifier 主機名驗證器

  • 他是給https用的。怎麼用的?你要去驗證對方的host是否是你要訪問的host。點進去看一下你會更清楚。
public interface HostnameVerifier {
    /** * Verify that the host name is an acceptable match with * the server's authentication scheme. * * @param hostname the host name * @param session SSLSession used on the connection to host * @return true if the host name is acceptable */
    public boolean verify(String hostname, SSLSession session);
}
複製代碼
  • HostnameVerifier有一個verify方法。option+command+鼠標左鍵,點進去看接口實現
@Override
public boolean verify(String host, SSLSession session) {
  try {
    Certificate[] certificates = session.getPeerCertificates();
    return verify(host, (X509Certificate) certificates[0]);
  } catch (SSLException e) {
    return false;
  }
}

複製代碼
  • 對第一個證書作驗證。爲何對第一個證書作驗證?由於這是對方網站的證書
Certificate[] certificates = session.getPeerCertificates();
    return verify(host, (X509Certificate) certificates[0]);

複製代碼
  • 這個時候要分是IpAddress仍是Hostname
public boolean verify(String host, X509Certificate certificate) {
  return verifyAsIpAddress(host)
      ? verifyIpAddress(host, certificate)
      : verifyHostname(host, certificate);
}
複製代碼
  • 再點進去,最終通過各類各樣的處理和排錯,會比較對方的服務器名跟咱們訪問的是否是同樣。
  • 若是同樣,經過,這確定不是第三方中間人攻擊。
return hostname.equals(pattern);

複製代碼

13.CertificatePinner

  • 這個多是對咱們比較有用的一個點,他是用來作自簽名的。有人可能會在公司用子簽名,我說一種使用自簽名比較簡單的方法。 直接使用CertificatePinner就能夠實現。
  • 首先Certificate是什麼?是證書。Pinner圖釘。CertificatePinner就是一個證書固定器。
    • 有時候你的在線證書有時候會在本地驗證不經過,也許他是自簽名的,也許是你的本地的證書機構沒有更新。總之在你本地驗證不經過。可是你很清楚我是這個網站的開發者,或者說我是這個網站的程序員,咱們公司的工程師可以很清楚地把這個證書的公鑰告訴我,並且他告訴我,這個絕對就是咱們的證書公鑰。我把這個證書的信息記錄在本地,我去下載證書,下完之後我去對比,兩個證書只要同樣不就完了,跟證書機構什麼的不要緊了。我只須要一個,什麼密碼學什麼的都跟我無關,我只須要比對他們是一個。那麼我怎麼比對呢?
    • 我把從遠端下載下來的證書鏈,他的每個證書的公鑰記錄下來,記在個人本地。而後我在實時請求的時候去比對。在咱們鏈接創建的過程當中,這個過程尚未到http,只是tls他的鏈接創建過程當中,當我獲取到這個證書的時候,我去比對一下他的公鑰信息和我本地所存的公鑰信息是否是同樣,若是一致就能夠了。
  • 這個東西怎麼用?示例裏面的代碼直接貼過來。改一下
String hostname = "publicobject.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
//這裏能夠加多個,越多越容易
        .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
        .build();
OkHttpClient client =new OkHttpClient.Builder()
        .certificatePinner(certificatePinner)
        .build();
Request request = new Request.Builder()
        .url("https://" + hostname)
        .build();
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {

    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {

    }
});
複製代碼

14.Authenticator:proxyAuthenticator、authenticator

  • 他們都是用來寫登陸受權的Authorization的header的。加這個作什麼呢?
    • 當你的權限不足的時候,他會給你報錯,會給你返回一個401...這個錯okhttp會自動給你攔住,若是你使用authenticator()配了Authenticator,那麼你稍後再遇到這種錯的時候他就會自動彈過來,自動調用authenticator的回調方法,這個時候,你再去請求獲取你的token,或者添加你的password和username組成的basic Authorization,把他們添加進去就能夠了。
  • 怎麼用?
OkHttpClient client =new OkHttpClient.Builder()
        .certificatePinner(certificatePinner)
        .authenticator(new Authenticator() {
            @javax.annotation.Nullable
            @Override
            public Request authenticate(Route route, Response response) throws IOException {
                return response.request().newBuilder().addHeader("Authorization","Basic 用戶名密碼的base64").build();
            }
        })
        .build();
複製代碼

15.ConnectionPool 鏈接池

  • 鏈接池、線程池,都是一個帶緩存的集合。好比鏈接池我設置他的最大值是20,初始值是5,那麼剛上來我就有五個能夠隨時拿着就用的線程,或者隨時拿着就用的鏈接,我有這樣的鏈接池的好處就是,我能夠隨時有空了,我不須要現場建立就能夠用。一樣的,他有上限就不會讓個人資源耗用過多。

16.Dns

  • dns是什麼?簡單看一下他的實現
Dns SYSTEM = new Dns() {
  @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
    if (hostname == null) throw new UnknownHostException("hostname == null");
    try {
      return Arrays.asList(InetAddress.getAllByName(hostname));
    } catch (NullPointerException e) {
      UnknownHostException unknownHostException =
          new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
      unknownHostException.initCause(e);
      throw unknownHostException;
    }
  }
};

複製代碼
  • 關鍵是這一行
Arrays.asList(InetAddress.getAllByName(hostname));
複製代碼
  • 他是用系統的dns方法去獲取dns,根據你的域名去拿到一個ip列表。是個列表。一個域名可能對應多個ip。這就是dns,你直接用就能夠了。有時候你須要本身配是何時?是某個ip你不想解析,你想本身給他指,這種狀況不多。

17.(boolean)followSslRedirects、followRedirects

  • boolean followRedirects,就是我遇到重定向須要跳轉的時候, 我是否是要跳轉,由於再跳轉就是兩次請求了。那麼okhttp給我兩個選擇你是否是要跳轉,默認就是跳轉。
  • followSslRedirects:這個可不是https遇到的時候要不要跳,而是當你訪問的是http,但要你跳的是https,或者反過來,這樣互跳的時候,是否是跳轉。

爲何要單獨設置這個呢?由於他們是有一些安全上的風險的。

18.(boolean)retryOnConnectionFailure

  • 名字上來看是,當你的鏈接創建失敗的時候你是否要重試?
  • 不過實際上不止是鏈接創建失敗,就是你的請求失敗了你要不要從新請求一下。
  • 不過請求返回404,500這種不算。這種屬於別的例子。

19.(int)connectTimeout、readTimeout、writeTimeout

  • connectTimeout:tcp鏈接時間超時報錯。
  • readTimeout:下載響應的時候等待時間
  • writeTimeout:寫入一個請求的時候他的時間

20.(int)pingInterval

  • pingInterval是針對WebSocket的,
  • WebSocket是用http的方式來作一個能夠推送的,能夠雙向交互的一個通道
    • 這個東西就須要長鏈接了,他須要去發送小的心跳的消息,去作確認,讓那個鏈接可以持久創建,
    • pingInterval就是ping的間隔,多長時間ping一次。爲何叫ping呢?
      • 他們作鏈接有兩個東西,一個叫ping,一個叫pong。乒乓乒乓,實質上就是這麼發的。我發一個ping過來,你接收之後發一個pong過來,讓我知道你那邊沒斷開。

核心:getResponseWithInterceptorChain()裏的責任鏈設計模式

  • 剛纔咱們看到無論是同步請求仍是異步請求都有這個, 點進去具體看一下是怎麼實現的。
Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));

  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

  return chain.proceed(originalRequest);
}
複製代碼
  • 這裏面是什麼內容,回顧一下,就是把你準備好的請求去作一個網絡請求,而後獲得響應。這個東西作的工做挺多的,他連tcp鏈接都是本身創建。那麼整個過程是怎麼回事?
  • 你看這麼幾行,行數很少,可是每一行都有不一樣的工做,因此東西仍是很豐滿的。這幾行代碼就是okhttp在技術上的核心。大致說一下。

主幹:分三步

  • 首先前面這幾行都是去建立這個list,豐富這個list。
List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));
複製代碼
  • 接下來是另一行代碼,他建立了一個chain,這個chain叫RealInterceptorChain。
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

複製代碼
  • 而後在chain建立完之後調用了他的proceed方法。
return chain.proceed(originalRequest);
複製代碼
  • 接下來講一下這三步是什麼,具體幹什麼了。

責任鏈

  • 這是我一個Interceptor,取名叫I,接着建立6個,這是個人一個鏈。那個list是什麼不用說,list是用來建立鏈裏面每個節點的。最終list準備好了以後就會把他建立成一個chain。接着會調用chain的proceed。

image.png

  • 個人整個過程這個chain他是幹嗎用的?他是用來作網絡請求的。
  • 實際網絡請求他是怎麼幹的?是有方向的。我沿着這個方向一路走過去,就像一條生產線同樣。發過去,再發回來。

image.png

  • 爲何要一個鏈呢?舉個例子說一下這個鏈是什麼做用。
    • 漢堡店有人訂餐,他給我打電話了,行,你地址是哪跟我說,我是那個作漢堡的師傅,也是店老闆,我把漢堡作好了,我把漢堡給店裏負責分發漢堡的人,他等着,過一會咱們店的送餐員來了,他把這個漢堡給了送餐員,送餐員騎車送到訂餐人的家裏面,而後那人開門,把漢堡取走,他錢給了送餐員,而後送餐員扣掉一塊錢送餐費之後把剩下的錢交給了負責分發漢堡的人,而後她把神仙的錢交給我了,我是店老闆。
    • 這就是一個鏈,我就是鏈的起始端。我負責作漢堡,交出漢堡以及最後把錢拿到手。店裏的店員負責交出去,交給送餐員,以及把送餐員拿回的扣過的錢交給我。送餐員負責把漢堡送給對方,以及收到錢,雁過拔毛,留下一塊錢,而後把剩下的錢再給店裏的店員。而終點是訂餐的那我的,他作了兩件事,一個是接收漢堡,一個是把漢堡錢給送餐員。這是個鏈。
  • 結構比較複雜的時候,你把他拆成鏈,每個節點作不一樣的事情。每個節點就是攔截器。
    • 爲何叫chain,爲何叫Interceptor.Chain?就是這麼一個做用,並非說他要擋着你的路,而是他須要作一些額外的事情。停一下,讓我加工加工。在作以前加工一下,作以後也加工一下,這就是攔截器的他的模型。
  • 具體說一下,咱們網絡攔截器Interceptor.Chain具體作的是什麼。

proceed()

  • chain.proceed()他是什麼呢?
  • 我從這個點,到這,而後回來。

image.png

  • 這個什麼意思呢?
    • 就是從出發到這,工做從第一個Interceptor,交給第二個Interceptor,而後第一個Interceptor等待,等工做回來,作返回的加工。
    • chain.proceed()就是你的鏈往前的意思,前進一步。對於我來講就是把漢堡交給下一我的,你作你的工做吧。對於每個節點來講,就是作事、等待、作事。
  • 接下來說一下每一個節點。
  • 這兩個先不說,由於這兩個東西是本身配的,原本是沒有的,先把有的東西講了。
interceptors.addAll(client.interceptors());
interceptors.addAll(client.networkInterceptors());
複製代碼

Interceptor攔截器

1.RetryAndFollowUpInterceptor 與 proceed的調用

- 第一個,retry就是重試,FollowUp就是跟進,去跟進重定向的連接。
複製代碼
interceptors.add(retryAndFollowUpInterceptor);
複製代碼
  • 跳過去看一下。他的關鍵在於intercept方法。
public final class RetryAndFollowUpInterceptor implements Interceptor {
...
@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 streamAllocation = new StreamAllocation(client.connectionPool(),
      createAddress(request.url()), call, eventListener, callStackTrace);
  this.streamAllocation = streamAllocation;

  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(), streamAllocation, 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.
      boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
      if (!recover(e, streamAllocation, 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.
    if (priorResponse != null) {
      response = response.newBuilder()
          .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
          .build();
    }

    Request followUp = followUpRequest(response, streamAllocation.route());

    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) {
      streamAllocation.release();
      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()), call, eventListener, callStackTrace);
      this.streamAllocation = streamAllocation;
    } 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;
  }
}
...
複製代碼
  • intercept方法他會作什麼呢?
    • 他作三件事情,就是剛纔說的事前準備、交給下一個並等待回來、回來以後的後續處理。
    • 第一件是最初的準備StreamAllocation,StreamAllocation注意聽一下,他是關於OKhttp的一個很關鍵的概念。
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
      createAddress(request.url()), call, eventListener, callStackTrace);
複製代碼
  • 點進去看StreamAllocation的註釋
Connections:</strong> physical socket connections to remote servers. These are
potentially slow to establish so it is necessary to be able to cancel a connection
currently being connected.
Streams:</strong> logical HTTP request/response pairs that are layered on
connections. Each connection has its own allocation limit, which defines how many
concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream
at a time, HTTP/2 typically carry multiple.
Calls:</strong> a logical sequence of streams, typically an initial request and
its follow up requests. We prefer to keep all streams of a single call on the same
connection for better behavior and locality.
複製代碼
  • okhttp有三個關鍵概念:
    • Connection:指的是我和服務器之間的一個鏈接。
    • Stream:我作一次請求,可能要和服務器發消息,接收消息,這麼一個對,這一個對叫stream。
    • Call:我如今想要訪問一個https的url,我進行請求最終獲得一個響應這就是一個call。
  • 咱們的call可能會作跳轉,可能會作重定向。因此call可能會有多個stream。
  • 接下來RetryAndFollowUpInterceptor的前置工做,基本上沒有什麼前置工做,由於做爲重試和重定向的東西,要什麼前置工做?主要是後置工做。
this.streamAllocation = streamAllocation;

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

    Response response;
    boolean releaseConnection = true;
複製代碼
  • 什麼是後置呢?你得先把這個事幹了。也就是你在這也調用一次proceed
response = realChain.proceed(request, streamAllocation, null, null);
複製代碼
  • proceed流程是這樣,前面的節點預處理完,調用proceed,可是proceed沒有結束,他會等待,等到後面的節點依次預處理,而且依次proceed完之後,最前面的proceed纔算結束,而後走後置的代碼,有點像一個遞歸。

image.png

  • 因此RetryAndFollowUpInterceptor裏面全部proceed這一行以前的是前置工做,proceed以後的是後置工做,而proceed這一行的做用是把工做交給下一個節點,而且等待。
  • 把這個理解明白之後,okhttp的鏈的工做結構就明白了。
  • 看一下他的後置工做,他的後置工做是什麼呢?想想就能想出來,重試嘛,好比下面這個,RouteException,路由異常,也就是說找這個ip找失敗了,怎麼辦呢?若是有多個ip換一個ip嘛。可是 他也是有前前提的,點進去看一下這個recover。
catch (RouteException e) {
  // The attempt to connect via a route failed. The request will not have been sent.
  if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
    throw e.getLastConnectException();
  }
複製代碼
  • 他會看一下,先看你是否是有這個配置retryOnConnectionFailure(),鏈接失敗是否重試,而後hasMoreRoutes(),是否還有別的路由方式。
  • 總之這個就是去判斷你是否是能夠對這個情形進行嘗試性恢復。
private boolean recover(IOException e, StreamAllocation streamAllocation, boolean requestSendStarted, Request userRequest) {
  streamAllocation.streamFailed(e);

  // The application layer has forbidden retries.
  if (!client.retryOnConnectionFailure()) return false;

  // We can't send the request body again.
  if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;

  // This exception is fatal.
  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;
}
複製代碼
  • 其餘後置工做也和這個大同小異,就是對異常的各類判斷。
  • 最終你會走到這,
Request followUp = followUpRequest(response, streamAllocation.route());
複製代碼
  • 你去獲取一個你的followUpRequest,若是你請求成功的話這兒會返回空的,
if (followUp == null) {
  if (!forWebSocket) {
    streamAllocation.release();
  }
  return response;
}
複製代碼
  • 若是你失敗了,並且符合重試條件,或者跳轉條件,你就會得到一個新的followUpRequest,一個新的請求賦值進去,而後循環。
request = followUp;
複製代碼
  • 往上看,這是一個死循環。
  • 要麼你拿到結果,return,請求成功了;
  • 要麼你獲取到30一、302,你給他一個新的請求,再循環一次,
while (true) {...}
複製代碼
  • Interceptor他主要是後置邏輯。

2.BridgeInterceptor

  • 重點也是intercept方法
public final class RetryAndFollowUpInterceptor implements Interceptor {
...
@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));
  }

  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.
  boolean transparentGzip = false;
  if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
    transparentGzip = true;
    requestBuilder.header("Accept-Encoding", "gzip");
  }

  List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
  if (!cookies.isEmpty()) {
    requestBuilder.header("Cookie", cookieHeader(cookies));
  }

  if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", Version.userAgent());
  }

  Response networkResponse = chain.proceed(requestBuilder.build());

  HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

  Response.Builder responseBuilder = networkResponse.newBuilder()
      .request(userRequest);

  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);
    String contentType = networkResponse.header("Content-Type");
    responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
  }

  return responseBuilder.build();
}
...
}
複製代碼
  • 先找到proceed方法,而後用這個來判斷前置後置。
  • 看一下前置作了什麼工做。
if (contentType != null) {
  requestBuilder.header("Content-Type", contentType.toString());
}
複製代碼
  • 若是你有body,往裏面添加Content-Type。
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");
}
複製代碼
  • 而後Content-Length,你以前不知道你的內容有多長,在建立過程當中你是不知道body有多長的,如今你知道了,你能夠寫他的長度。另外你能夠用Content-Length寫,也能夠用Transfer-Encoding寫,兩種均可以,他會自動判斷應該寫哪一種。Content-Length是固定長,Transfer-Encoding是不定長。
if (userRequest.header("Host") == null) {
  requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
複製代碼
  • 添加host,主機名稱。
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
  transparentGzip = true;
  requestBuilder.header("Accept-Encoding", "gzip");
}
複製代碼
  • Accept-Encoding是什麼?你接受的編碼格式。
  • 並且會加一個gzip,若是你沒有加的話,表示,我做爲客戶端,能夠接受你給我發一個通過gzip壓縮的編碼格式的數據。這個頗有意思,我做爲開發者尚未說我接不接受這種編碼格式,你就幫我接受了,萬一我處理不了怎麼辦呢?爲何這樣作呢?由於這個東西okhttp他給我作了。okhttp他自己有了對這種數據格式的支持。所以他會主動加上這個encoding。你做爲軟件開發者根本不用管。服務器發了gzip數據,我負責解壓縮數據,這樣就省了帶寬。全部服務器,只要他支持,那麼你用http的狀況下,他必定幫你壓縮數據的。解壓縮的方式就在下面的後置工做裏面。
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);
  String contentType = networkResponse.header("Content-Type");
  responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
複製代碼
  • 另外說一點,cookieJar是須要本身寫的。
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
  requestBuilder.header("Cookie", cookieHeader(cookies));
}
複製代碼
  • 對於cookiejar簡單說一點點
public interface CookieJar {
  /** A cookie jar that never accepts any cookies. */
  CookieJar NO_COOKIES = new CookieJar() {
    @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    }

    @Override public List<Cookie> loadForRequest(HttpUrl url) {
      return Collections.emptyList();
    }
  };
  ...
}
複製代碼
  • 就兩個方法:
    • saveFromResponse:拿到的響應把他存起來。
    • loadForRequest:當我須要作請求的時候,我從個人cookiejar裏面取出來。我對應的這個域名他有沒有存過。
  • 怎麼寫?我只須要去指定這個東西怎麼寫就能夠了。
    • 你看默認事件是這個是吧,我不這麼寫。
    • 我有一個Map,存怎麼存,cookies.put
CookieJar NO_COOKIES = new CookieJar() {
    Map map = new HashMap();
  @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
      map.put(url,cookies)
  }

  @Override public List<Cookie> loadForRequest(HttpUrl url) {
    return cookies.get(url);
  }
};
複製代碼
  • 這個是cookiejar,須要本身實現的。默認是沒實現的。默認存和寫都是空代碼。
if (userRequest.header("User-Agent") == null) {
  requestBuilder.header("User-Agent", Version.userAgent());
}
複製代碼
  • 接下來就是用戶代理,若是你沒有設置用戶代理,他會加一個字符串。
public static String userAgent() {
  return "okhttp/3.10.0";
}
複製代碼
  • 接下來他就會去作proceed,
Response networkResponse = chain.proceed(requestBuilder.build());
複製代碼
  • proceed以後就會去作解碼,去作數據的解析。
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

Response.Builder responseBuilder = networkResponse.newBuilder()
    .request(userRequest);

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);
  String contentType = networkResponse.header("Content-Type");
  responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
複製代碼
  • 那麼什麼是BridgeInterceptor?
    • 就是在要發射以前,我把發射所須要的數據準備好,還有壓縮我去提供一下支持。後續事件就是把這些東西都解一解,而後壓縮的解壓縮一下。

3.CacheInterceptor

  • CacheInterceptor很是簡單。
  • 他的intercept是對cash的處理,把cash存下來。存cash關鍵是這一行。
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
複製代碼
  • 點進去看一下。
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++) {
      String fieldName = headers.name(i);
      String value = headers.value(i);
      if ("Date".equalsIgnoreCase(fieldName)) {
        servedDate = HttpDate.parse(value);
        servedDateString = value;
      } else if ("Expires".equalsIgnoreCase(fieldName)) {
        expires = HttpDate.parse(value);
      } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
        lastModified = HttpDate.parse(value);
        lastModifiedString = value;
      } else if ("ETag".equalsIgnoreCase(fieldName)) {
        etag = value;
      } else if ("Age".equalsIgnoreCase(fieldName)) {
        ageSeconds = HttpHeaders.parseSeconds(value, -1);
      }
    }
  }
}
複製代碼
  • 他會根據你收到的cash,以及當前的日期、你的數據狀況之類的,去判斷你cash是否過時,若是沒有過時,就直接把數據返回了。
if (networkRequest == null) {
  return cacheResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .build();
}
複製代碼
  • 若是數據沒有過時,就直接建立一個假的響應,返回回來了。
  • 假如第三個就是CacheInterceptor,他的cash沒有過時,就直接返回了,後面的都不走了。根本就沒有進行網絡交互,這個是cash的做用,後面徹底省略了,並且不影響結果,這個就是http他所預期的。這雖然繞過了,可是沒有任何損失。這個也是intercept他的好處,你的節點,對於你的前面和後面其實都是透明的,大家互相之間只負責本身的事情。前面的節點不須要知道這個信息是來之cash仍是網絡請求。

image.png

  • 沒什麼好說的,具體代碼仍是很複雜,可是咱們知道核心在哪。

4.ConnectInterceptor

  • ConnectInterceptor咱們確定是讀不懂的,起碼說不花時間是讀不懂的。可是大體結構仍是要明白。
  • 這個地方就是他和https,ssl來作交互了。關鍵是這麼兩行。
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
複製代碼
  • 有一行是newStream,什麼是steam?

    • 就是你跟網絡這一次交互。建立一個newStream,而後返回一個HttpCodec對象。
  • 那麼什麼叫Codec?

    • Codec是編碼解碼器的意思,code是編碼,decode是解碼。對什麼編碼解碼?不是對音樂視頻,而是對咱們網絡的數據。
    • 他的接口有兩個實現,爲何有兩個?一個http1,一個是http2,http2是二進制的形式,而一、1.0、1.一、1.9他們全都是用的文本形式。因此他們編碼解碼應該徹底是兩套算法。
  • 你的newStream建立的codec只是其一,他還會建立一個connection,建立一個鏈接,這個鏈接不是下面那一行建立的,下面那一行只是拿到。其實核心的只有一行代碼。

  • newStream咱們跳轉進去看一下。咱們會跳轉好幾回,不須要懂,只須要知道咱們講到了什麼東西。我想讓你知道他是怎麼跟tcp交互,怎麼跟ssl交互。因此咱們繼續找,newStream裏面有一個findHealthyConnection。

RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
    writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
複製代碼
  • 他會嘗試去獲取一個健康的鏈接,就是如今就能夠用的鏈接。而後再點進去,他有一個findConnection,
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
    pingIntervalMillis, connectionRetryEnabled);
複製代碼
  • 先找到,再肯定他是否健康。在findConnection裏面又有一個connect
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
    connectionRetryEnabled, call, eventListener);
複製代碼
  • 再日後又有一個connectSocket,跟tcp鏈接的那玩意叫socket。
connectSocket(connectTimeout, readTimeout, call, eventListener);
複製代碼
  • 再點進來connectStart,我開始作鏈接了,而後connectSocket,到這鏈接就創建完成。而後下面會有buffer,後面說io的時候再說buffer。
  • 這個東西就是跟socket作對接的。這裏面就獲取到socket了。而且你的socket就連上對方了。不須要知道他是怎麼連上的。

總之能夠在這看到,okhttp他作了tcp鏈接。這跟咱們前幾年用的http庫徹底不同,他們全都用的要麼是Apache的HttpClient,或者是用了android自帶的HttpUrlConnection。他們都沒有本身作鏈接的,可是okhttp作了。

eventListener.connectStart(call, route.socketAddress(), proxy);
rawSocket.setSoTimeout(readTimeout);
try {
  Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
  ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
  ce.initCause(e);
  throw ce;
}

// The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
// More details:
// https://github.com/square/okhttp/issues/3245
// https://android-review.googlesource.com/#/c/271775/
try {
  source = Okio.buffer(Okio.source(rawSocket));
  sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
  if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
    throw new IOException(npe);
  }
}
複製代碼
  • 接着拐回去,connectSocket的下面有一個establishProtocol
} else {
  connectSocket(connectTimeout, readTimeout, call, eventListener);
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
複製代碼
  • Protocol是http,http1.1這些東西,點進去
connectTls(connectionSpecSelector);
複製代碼
  • 這裏就是創建https的tls鏈接的地方。再點進來。
  • SSLSocketFactory這些東西都來了,什麼ConnectionSpec,以前咱們那些設置,到這全都用上了。
  • 最終Handshak,什麼hostnameVerifier都用上了,這裏是用來創建一個ssl鏈接,或者叫tls鏈接。
sslSocket.startHandshake();
複製代碼
  • 總之,在ConnectInterceptor裏面,他乾的事情,說下來就一點,創建鏈接。創建一個tcp鏈接或者一個tcp鏈接上面再疊加一個tls鏈接。

他沒有後置工做。

5.CallServerInterceptor

  • CallServerInterceptor也是粗略看一下,瞭解代碼作了什麼。
  • 他是最終的一個,他不須要proceed。他把事情作完,返回就能夠了。
  • 他作了幾個實質工做,只須要看幾個點就知道什麼叫實質工做。

首先看這個writeRequestHeaders,

httpCodec.writeRequestHeaders(request);
複製代碼
  • writeRequestHeaders他是httpcodec作的事情,那個編碼解碼器,編碼解碼器幹嗎用的?他和socket直接溝通的,那麼writeRequestHeaders,寫請求的headers是什麼意思?點進去。是個接口,看一下他的實現,http1和2的均可以。再往下跳writeRequest。
public void writeRequest(Headers headers, String requestLine) throws IOException {
  if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
  sink.writeUtf8(requestLine).writeUtf8("\r\n");
  for (int i = 0, size = headers.size(); i < size; i++) {
    sink.writeUtf8(headers.name(i))
        .writeUtf8(": ")
        .writeUtf8(headers.value(i))
        .writeUtf8("\r\n");
  }
  sink.writeUtf8("\r\n");
  state = STATE_OPEN_REQUEST_BODY;
}
複製代碼
  • 這幾行頗有意思,你可能不知道sink什麼意思,由於他是okio的東西,不是java本身io的東西,可是光從名字就能夠看出來writeUtf8,寫一段utf8的字符串。寫requestLine,你的請求行。寫到socket,也就是往網絡上去寫,往你的tcp或者tls的端口上面去寫,最終寫到網絡對岸。okhttp他是對網絡進行直接操做。他創建鏈接,他負責去溝通。寫了這個以後,寫了個換行writeUtf8("\r\n")。
  • 而後把每個header的名字寫完,寫一個冒號,再把值寫下來,再換行。
  • 而後再去寫body。
  • 就是http的格式,這樣硬生生實現的。

回到CallServerInterceptor,寫完請求以後,就會讀,讀響應裏面的東西。

responseBuilder = httpCodec.readResponseHeaders(false);
複製代碼
  • 讀完以後,把他放到responseBody裏面。而後把response返回。
response = response.newBuilder()
    .body(httpCodec.openResponseBody(response))
    .build();
複製代碼
  • 返回以後就作其餘節點的後置方法。

image.png

  • ConnectInterceptor結束返回,
  • ConnectInterceptor的後置工做,他沒有後置工做,而後
  • CacheInterceptor,他拿到以後會嘗試把cash存下來,存完以後
  • BridgeInterceptor,他會作gzip的解壓,以及各類數據的解讀完了以後,再到
  • retryAndFollowUpInterceptor,他看這個是否是301,302或者是其餘什麼須要跳轉的,總之網絡的問題不會在這跳,

網絡問題在ConnectInterceptor出問題以後就會拐回來,不會往ConnectInterceptor走。

  • 若是出問題須要重試,他會這樣走。

image.png

6.interceptors、networkInterceptors

  • 最後說一下這兩個是什麼
interceptors.addAll(client.interceptors());
interceptors.addAll(client.networkInterceptors());
複製代碼
  • 他們的實現跟其餘interceptor是同樣的,舉個例子,若是我要加一個interceptor,可是什麼都不作。
OkHttpClient client =new OkHttpClient.Builder()
        .certificatePinner(certificatePinner)
        .addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                //前置工做
                Response response = chain.proceed(chain.request());
                //後置工做
                return response;
            }
        })
        .build();
複製代碼

至關於我在中間插了一個什麼都沒幹的中間人。若是我想作什麼事情,就能夠插入前置工做和後置工做。

  • 而interceptors、networkInterceptors有什麼區別呢?

    • 關鍵在於位置,
    • interceptors發生在最開始的位置,
    • networkInterceptors發生在結束階段,返回的數據有可能讀不懂的,數據尚未解壓縮。因此,networkInterceptors通常是跟網絡相關的,跟寫數據相關的,對數據作一些前置工做和後置工做,你想操做這個時候再去網上查,通常狀況是不用他。
  • okhttp他的角色是什麼?他跟retrofit比起來?

    • 他就是一個從鏈接創建,到tls鏈接創建,到http的傳輸,再到各類http的特性支持,好比重試,跳轉,cash,cookie。他是整個接管了http的工做,而後順便把api作得比原生舒服一點。

總結圖

image.png

相關文章
相關標籤/搜索