从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

相关文章
相关标签/搜索