Okhttp初探

116 阅读15分钟

?OKHTTP和httpclient 现在默认的网络请求底层就是okhttp? 缓存拦截器详解?

文字描述整个流程

  • Okhttpclient类主要进行网络配置,和配置eventListenerFactory,用来生产回调
  • client.newBuilder()可以为特定的请求进行单独的配置,还是使用共同的线程池,连接池和其他配置
  • client.newCall是创建一个在未来将要被执行的request,并为这个request创建一个eventListener,eventListener用来回调请求过程中的几乎所有状态,eventListener中的每一个回调都有一个Call参数,其中包含Request,Request中包含这个请求的方法参数和path等信息,可以用来区分不同的request
  • client.dispatcher().enqueue(new AsyncCall(responseCallback))
    • enqueue 一个call不能被执行两次,会抛出异常,有一个synchronized方法监控

      • ==eventListener.callStart(this);==
      • Dispatcher 有关何时请求异步网络的策略
      • client.dispatcher().enqueue 判断当前请求数是否达到最大值或者当前host请求数是否达到最大值,没有的话加入runnig 列表并交给线程池执行
    • AsyncCall--executorService().execute(call)-- enqueue方法中调用线程池执行方法,真正执行的传入参数是一个runnable ;这里的AsyncCall的父类就是runnable,父类进行了封装,run方法调用了AsyncCall中的 execute方法

  • ==AsyncCall== 的execute方法,==核心==
    • 最外部调用enqueue方法时传入成功失败回调responseCallback,这个回调的调用地方就在execute方法中
    • execute方法核心行 Response response = ==getResponseWithInterceptorChain==();
  • OkHttpClient整个是一个配置类,用来配置各种数据和参数
    • 日了狗,写了一个多小时没有了
  • ==getResponseWithInterceptorChain==
  • ==在拦截器中 以cChain==.proceed为分割线,

所有连接值的都是http连接或者ssl连接,没有http连接 ctrl+alt+b 接口

google收了okhttp 主要做下层支持

OkHttpClient client = new OkHttpClient();
        client.newCall(new Request.Builder()
                .url("")
                .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 {

            }
        });

看newcall

@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
  //websocket 对http做了扩展 , 可以让服务器对客户端做推送
  //用的少,一般做股票交易平台需要用,
   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;
  }

所有的连接都是tcp连接,或ssl连接,没有所谓的http连接,因为连接指的是双方能把对方记住,发消息时不需要声明自己是谁,http本身是无连接的 eventListener是连接建立,开始请求,返回响应等状态的响应回调

下面看enqueue

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {//一个call只能被执行一次
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

下面看dispatcher、enqueue、AsyncCall

Dispatcher 是管理线程的,每个request和response需要使用单独的线程,这样才不会互相影响,这里用的ExecutorService 线程池

public final class Dispatcher {
  private int maxRequests = 64;//同一时间最大请求数,达到这个上限时,不做新的请求了,等一等,在队列里存一村
  private int maxRequestsPerHost = 5;//同一个主机(服务器)最大同时请求数,达到这个数时也等一等,避免对同一个主机过大的压力
  这个数是可以自己设置的

回到上面的enqueue

 client.dispatcher().enqueue(new AsyncCall(responseCallback));

Dispatcher中的enqueue 没超限制,继续执行,超限制后加入队列

  synchronized void enqueue(AsyncCall call) {
  //数量没有超过上方的限制是,我就直接去执行。 executorService().execute(call);
  //如果我超过限制了,就把自己加到ready队列, readyAsyncCalls.add(call);
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    //记录正在进行的请求
      runningAsyncCalls.add(call);
      //注意这里,把call放到线程池中去执行,也就是说会调用call的run方法,也就到了下方提到的excute方法中
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

下面看AsyncCall做了什么,线程控制都是在Runnable中执行,都有run方法,它的run方法在父类中,

 client.dispatcher().enqueue(new AsyncCall(responseCallback));
 
 //NamedRunnable 是AsyncCall 的父类  ,run方法实际调用了NamedRunnable的execute方法
 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();
}

在外部调用enqueue,最终会调用到下方的execute中的getResponseWithInterceptorChain(),返回Response

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
      //唯一重点行 请求和响应都在这个方法里 getResponseWithInterceptorChain(), 通过拦截器链获取相应,
        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);
      }
    }
  }

==到这里第一部分就完整了==

这时候如果我们已经在后台线程,可以使用同步的网络请求,使用excute方法,不切换线程

 Response response = okHttpClient.newCall(new Request.Builder().url().build()).execute();
        response.body()
        
        
  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();//注意这里,不需要切线程,直接请求
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      e = timeoutExit(e);
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

为了理解okHttp http等,都需要先了解下Okhttpclient中的配置项,然后是去解读 getResponseWithInterceptorChain()这个方法,是技术上的核心 getResponseWithInterceptorChain是面试核心

OkHttpClient的配置

public class OkHttpClient{

//调度控制线程,平衡性能
 final Dispatcher dispatcher;
 
 //自己设置的代理,中介机器,比如国外有一个网站我无法直接访问到,但是可以通过访问其他机器,把自己想要做的事情交给中间机器,由中间机器去访问目标服务器
  final @Nullable Proxy proxy;
  
  //所支持的http版本列表 http1.0 http1.1 http2等,
  final List<Protocol> protocols;

  //配置是使用http还是https,也是个列表(如果使用https的话,这里有支持的tls版本、算法等,这些算法是和服务端进行加密通讯使用)
  //  CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
  //        非对称加密算法  堆成加密算法  hash算法
  //加密、解密、hash 这些okhttp都给做了
  // public static final ConnectionSpec CLEARTEXT = new Builder(false).build();   明文,相当于使用http
  final List<ConnectionSpec> connectionSpecs;
  
  //可以在chain中插入的拦截器,插在最前面
  final List<Interceptor> interceptors;
  
  //可以在chain中插入的拦截器,插在倒数第二个,就是数据回来的进行处理的第一层
  final List<Interceptor> networkInterceptors;
  
  //请求状态监听  ????????????
  final EventListener.Factory eventListenerFactory;
  
  //没有用过,注释说明是代理服务器
  final ProxySelector proxySelector;
  
  //cookie浏览器和服务器之间一个完整的机制  jar 罐子  cookie 饼干
  //就是一个cookie的存储器, okhttp本身没有实现,可以自己实现,因为okhttp默认客户端本身不需要实现cookie
  //可以自己选择存在本地还是内存,存取都自己来定,内部有存取两个接口
  final CookieJar cookieJar;
  
  //就是http的cache,下面两个都是做cache的
  final @Nullable Cache cache;
  final @Nullable InternalCache internalCache;
  
  //socket 是tcp的端口 比如8080(android用的不多)
  //192.168.1.1:8080  前面的是ip那一层的(从下往上数第二层的地址),后面端口是tcp层的(从下往上数第三层的,从上数第二层的)
  //socketFactory就是用来创建端口的,获取到那个可以往服务器写东西,可以从服务器读东西的端口,socket本身是tcp的东西,tcp是三次握手,ssl的建立是https那一大堆东西
  final SocketFactory socketFactory;
  
  //ssl是ssl的东西,和上方的都不是一层,原理也不一样
  //http没有端口这个东西,因为http本身不面向连接,端口实际上指的是tcp端口
  final @Nullable SSLSocketFactory sslSocketFactory;
  
  //整理证书,把下发的证书整理成一个证书链,第一个为服务器的证书,最后一个为信任的本地根证书
  final @Nullable CertificateChainCleaner certificateChainCleaner;
  
  //主机名验证器,给https用的,验证主机host是否是要访问的host,验证第一个证书的host是否是自己要访问的服务器域名,防止中间人攻击
  final HostnameVerifier hostnameVerifier;
  
  //用来做自签名 ,因为服务器的证书可能没有在证书机构登记。这样就不会使用本地根证书验证。这样也可以防止证书被hack的风险。但服务器的证书不能更换了。
  //只需要把公钥保存下来,不需要都保存下来,在tls连接的时候(还没到http)就可以比较公钥和服务端的证书的公钥是否一致
  //证书固定器  可以把证书的信息记录在本地,到时候服务端下发证书后可以直接和本地证书进行对比,如果一样,就选择相信,这样可以不考虑根证书
  final CertificatePinner certificatePinner;
  
  //用来写授权header的 authenization  ,如果权限不足会报错,前面有讲,
  final Authenticator proxyAuthenticator;
  
  final Authenticator authenticator;
  
  //线程池 连接池  有空闲时不需要现场创建即可直接使用,但有数量限制
  //默认此池最多可容纳 5 个空闲连接,这些连接将在 5 分钟不活动后被驱逐。
  //private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
  //    Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
   //   new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
  final ConnectionPool connectionPool;
  
  //根据域名拿到ip列表,是一个列表,一个域名可能对应多个ip
  final Dns dns;
  
  //followRedirects重定向时是否需要跳转  默认是true, followSslRedirects是否允许http https互跳,
  final boolean followSslRedirects;
  final boolean followRedirects;
  
  //失败时是否重连(不只是tcp连接,其他情况也算)
  final boolean retryOnConnectionFailure;
  
  //tcp连接时间,超过就报错
  final int connectTimeout;
  
  //下载 响应时的等待时间  作业帮是30秒
  final int readTimeout;
  
  //写入请求时的等待时间
  final int writeTimeout;
  
  //websocket是一个可以双向通信的通道,需要长连接,需要发送小的心跳包,
  //这个是ping的间隔  ping  pong 是回复的
  final int pingInterval;

自签名证书验证的使用,担心证书机构被hack也可以使用

虽然对方签了个假证书,但是假证书的hash和本地的不一致

但风险是服务器的证书不能更换了,一旦本地配置了,就会放弃了本地根证书的验证,全部使用

image.png authenticator使用,没有权限时,会回调,需要去网络请求新的token,然后添加进request 的header

image.png

getResponseWithInterceptorChain

这里可以看到responseCallback回调请求成功和失败

 @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);
    }
  }
}

getResponseWithInterceptorChain()方法是整体的一个请求流程, 这个方法时整个技术上的核心 ,每一行就是核心的一部分。

final class RealCall implements Call {
...
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    
    //自己配,在OkhttpClient中配置
    interceptors.addAll(client.interceptors());
    
    //重试 跟进重定向的连接 关注每一个interceptor的intercept方法
    //每一个intercept方法都是做三件事,1事情准备,2交给下一个并等待回来,3回来后做后续处理
    interceptors.add(retryAndFollowUpInterceptor);
    
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterc
    eptor(client));
    if (!forWebSocket) {
    
    //自己配,在OkhttpClient中配置
      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);
  }
  ...
}

先看下最后一行chain.proceed(),确认下执行的流程。在proceed方法中,获取了index+1开始的新的RealInterceptorChain,获取了当前index的Interceptor, interceptors.get(index);然后执行当前Interceptord intercept方法,并把chain当做参数传入。这样每一个Interceptor都持有这从他开始后面的所有Interceptor。

RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

下面看下getResponseWithInterceptorChain中具体的Interceptor

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));

先看下RealInterceptorChain的proceed方法

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private final Call call;
  private final EventListener eventListener;
  private final int connectTimeout;
  private final int readTimeout;
  private final int writeTimeout;
  private int calls;

...
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
   ...

    // Call the next interceptor in the chain.
    //注意这里的index + 1
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
        
    //获取当前拦截器
    Interceptor interceptor = interceptors.get(index);
    
    //执行当前拦截器的intercept方法,并传进去新创建的RealInterceptorChain,指针指向下一个拦截器
    Response response = interceptor.intercept(next);

   ...

    return response;
  }
}

看具体的拦截器时,重点关注intercept方法的执行,及proceed方法的调用。

1. retryAndFollowUpInterceptor

interceptors.add(retryAndFollowUpInterceptor);

Okhttp有三个关键概念
Call: 访问url时,进行一个request,返回一个response,这就是一个call
connection:我现在和服务器之间的一个socket连接
stream:我现在做一次请求可能给服务端发消息,可能从服务端接收消息,这样一个对是stream

连接:到远程服务器的物理套接字连接。
这些连接建立起来可能很慢,因此有必要能够取消当前正在连接的连接。

流:位于连接上的逻辑HTTP请求/响应对。
每个连接都有其自己的分配限制,该限制定义了该连接可以承载的并发流数。 HTTP / 1.x连接一次只能传送1个流,HTTP / 2通常会传送多个流。

调用:流的逻辑顺序,通常是初始请求及其后续请求。
我 们希望将单个呼叫的所有流都保留在同一连接上,以实现更好的行为和局部性。

retryAndFollowUpInterceptor 不需要前置工作,所以直接调用proceed
realChain.proceed前是前置工作,后面是后置工作是一个链式工作结构。

一层一层的对Request进行包装并发给下一层,返回时一层一层的对Response进行处理并返回给上一层

Response proceed(Request request)

RetryAndFollowUpInterceptor: 主要是处理后置逻辑,处理失败重试

@Override public Response intercept(Chain chain) throws IOException {
    ...
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
   ...
   //在满足重试条件的情况,会一直触发重试
    while (true) {
     ...
      try {
          //调用下一个拦截器的intercept方法
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will ...
        //如果重定向返回的不为空,则把request进行替换,进行循环
      Request followUp = followUpRequest(response, streamAllocation.route());

      ...
      request = followUp;
      priorResponse = response;
      
    }
  }

2.BridgeInterceptor

interceptors.add(new BridgeInterceptor(client.cookieJar()));

BridgeInterceptor ,是从应用程序代码到网络代码的桥梁,准备要发送的数据,和后续返回数据的解压缩。
数据发送前的准备工作,根据发送请求时创建的Request对象中配置的内容,给request添加header,对于没有配置的header字段添加默认字段。

给request添加header:

  • Content-Type
  • Content-Length
  • Transfer-Encoding
  • Host
  • Connection keep-alive
  • Accept-Encoding gzip 支持的压缩方式
  • Cookie 从Cookiejar中取的cookie
  • User-Agent

proceed方法后是对返回回来的数据进行处理,会处理返回的cookie, 解压缩数据,放到body中。

@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) {
    //添加host
      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;
      //编码格式,默认添加gzip,表示可以接收gzip格式的数据,okhttp本身对gzip做了支持,节省带宽
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
    //需要自己配置cookiejar
      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);

//如果是gzip ,接收数据后,判断格式后,支持解压缩,如果我们要增加自己的压缩方式,需要添加新的intercepter
    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();
  }

[待]自己实现CookieJar

CacheInterceptor

这个需要细看,面试会问

处理来自缓存的请求并将响应写入缓存,缓存的数据是解压缩前的

public final class CacheInterceptor implements Interceptor {
  final InternalCache cache;

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

  @Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

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

    if (cache != null) {
      cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

先看 CacheStrategy

CacheStrategy.Factory

会根据收到的cache的日期阿,最后修改时间

 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);
          }
        }
      }
    }

only-if-cached

CacheStrategy类

public CacheStrategy get() {
  CacheStrategy candidate = getCandidate();

  if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
    // We're forbidden from using the network and the cache is insufficient.
    return new CacheStrategy(null, null);
  }

  return candidate;
}

onlyIfCached:该字段的名称“only-if-cached”具有误导性。它实际上意味着“不要使用网络”。它是由客户端设置的,该客户端只希望在缓存完全满足时才发出请求。如果设置了此标头,则不允许需要验证(即条件获取)的缓存响应。


CacheInterceptor
//配置了onlyIfCached 为true
// If we're forbidden from using the network and the cache is insufficient, fail.
//如果我们被禁止使用网络并且缓存不足,失败。
 if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }
//满足缓存条件,缓存不为空,则返回缓存的数据
// If we don't need the network, we're done.
if (networkRequest == null) {
  return cacheResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .build();
}
Response networkResponse = null;
try {
  networkResponse = chain.proceed(networkRequest);
} finally {
...

如果满足缓存条件,则不继续进行网络请求了,直接将缓存的数据进行返回。

ConnectInterceptor

不花时间读不懂。这里是和tcp/https/ssl做交互了
总结 ConnectInterceptor 中干的事情就是建立tcp连接或者在tcp连接上面在叠加一个tls连接。没有后置工作

public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    
    //我们需要网络来满足这个要求。可能用于验证有条件的GET。
    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //下方两行为关键  
    //HttpCodec  编码解码器  
    //创建连接 
    //主要核心就是下面这一行 newStream
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

看streamAllocation.newStream 和tcp/ssl 交互 httpCodec是编码解码器

newStream方法中 的findHealthyConnection 尝试获取一个可用的connection
再进入findConnection方法中的result.connect line258
进入,找到connectSocket 和tcp连接的就是socket

// Do TCP + TLS handshakes. This is a blocking operation.
 result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());

进入connect

//socket 和tcp连接的
connectSocket(connectTimeout, readTimeout, call, eventListener);

==进入connectSocket==,到这步连接就建立完成了 这里可以看出okhttp自己做了**==tcp连==**接,没有使用httpclient httpurlconnection

 private void connectSocket(int connectTimeout, int readTimeout, Call call,
      EventListener eventListener) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    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);
      }
    }
  }

往回倒退 可以看到是先连接socket,再https握手

接着看刚才的connect方法

//protocal 是http http1.1
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
 private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
      int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
    if (route.address().sslSocketFactory() == null) {
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
      return;
    }

    eventListener.secureConnectStart(call);
    //看这行 真正建立https连接的地方  进入
    connectTls(connectionSpecSelector);
    eventListener.secureConnectEnd(call, handshake);

    if (protocol == Protocol.HTTP_2) {
      socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
      http2Connection = new Http2Connection.Builder(true)
          .socket(socket, route.address().url().host(), source, sink)
          .listener(this)
          .pingIntervalMillis(pingIntervalMillis)
          .build();
      http2Connection.start();
    }
  }

之前的配置在这里很多都用到了,建立一个tls连接 (https连接)

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    Address address = route.address();
    SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
    boolean success = false;
    SSLSocket sslSocket = null;
    try {
      // Create the wrapper over the connected socket.
      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // Configure the socket's ciphers, TLS versions, and extensions.
      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
      if (connectionSpec.supportsTlsExtensions()) {
        Platform.get().configureTlsExtensions(
            sslSocket, address.url().host(), address.protocols());
      }

      // Force handshake. This can throw!
      //关注
      sslSocket.startHandshake();
      // block for session establishment
      SSLSession sslSocketSession = sslSocket.getSession();
      if (!isValid(sslSocketSession)) {
        throw new IOException("a valid ssl session was not established");
      }
      Handshake unverifiedHandshake = Handshake.get(sslSocketSession);

      // Verify that the socket's certificates are acceptable for the target host.
      if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
        X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
        throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
            + "\n    certificate: " + CertificatePinner.pin(cert)
            + "\n    DN: " + cert.getSubjectDN().getName()
            + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
      }

      // Check that the certificate pinner is satisfied by the certificates presented.
      address.certificatePinner().check(address.url().host(),
          unverifiedHandshake.peerCertificates());

      // Success! Save the handshake and the ALPN protocol.
      String maybeProtocol = connectionSpec.supportsTlsExtensions()
          ? Platform.get().getSelectedProtocol(sslSocket)
          : null;
      socket = sslSocket;
      source = Okio.buffer(Okio.source(socket));
      sink = Okio.buffer(Okio.sink(socket));
      handshake = unverifiedHandshake;
      protocol = maybeProtocol != null
          ? Protocol.get(maybeProtocol)
          : Protocol.HTTP_1_1;
      success = true;
    } catch (AssertionError e) {
      if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
      throw e;
    } finally {
      if (sslSocket != null) {
        Platform.get().afterHandshake(sslSocket);
      }
      if (!success) {
        closeQuietly(sslSocket);
      }
    }
  }

总结 ConnectInterceptor 中干的事情就是建立tcp连接或者在tcp连接上面在叠加一个tls连接。没有后置工作


CallServerInterceptor

最终的,不需要proceed交给下一个节点了,只需要返回
这里做的都是**==实质工作==**,写完数据再读响应的数据,最后返回response,执行前面的后置方法

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    realChain.eventListener().requestHeadersStart(realChain.call());
    //编码解码器的工作  写请求头  看着行 GET/1.1  
    //直接写到网络上,发送到了网络对岸
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
        // from being reused. Otherwise we're still obligated to transmit the request body to
        // leave the connection in a consistent state.
        streamAllocation.noNewStreams();
      }
    }

    httpCodec.finishRequest();

    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (code == 100) {
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      responseBuilder = httpCodec.readResponseHeaders(false);

      response = responseBuilder
              .request(request)
              .handshake(streamAllocation.connection().handshake())
              .sentRequestAtMillis(sentRequestMillis)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();

      code = response.code();
    }

    realChain.eventListener()
            .responseHeadersEnd(realChain.call(), response);

    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

public void writeRequest(Headers headers, String requestLine) throws IOException {
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    //往socket里写请求行  往网络 
    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;
  }

retryAndFollowUpInterceptor中有try/catch ,底层出现错误可以直接抛异常,由这个拦截器接收到会,回调给上层

retry
把每个拦截器用I 表示,整体就是一个chain 前置proceed执行完,执行后置的 生产汉堡的例子, image.png ssl和tls只是换了主人之后不同的名字

image.png