Android之網(wǎng)絡—第三篇(解讀OkHttp)

前言:這篇網(wǎng)絡系列的初衷是分享下網(wǎng)絡相關的知識趟大,文章屬于個人的學習總結(jié)博客。部分內(nèi)容來源網(wǎng)絡铣焊,如有不適逊朽,請私聊。

Android之網(wǎng)絡—第一篇(Http原理)
Android之網(wǎng)絡—第二篇(Https原理)
Android之網(wǎng)絡—第三篇(解讀OkHttp)
Android之網(wǎng)絡—第四篇(解讀Retrofit)

結(jié)合Http原理解讀OkHttp庫

當前android最流行的網(wǎng)絡請求最火的框架應該就屬OkHttp曲伊。而且也被google官方收錄到android系統(tǒng)中叽讳,當做底層的網(wǎng)絡請求庫了。本文嘗試從Http原理的角度來解讀下OkHttp是如何實現(xiàn)的坟募。
okhttp源碼地址

簡單介紹下OkHttp的使用

可以看看官網(wǎng)的實例介紹:GET 和 POST使用岛蚤。鏈接地址:https://square.github.io/okhttp/

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();
  }
}
public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

簡單總結(jié)來看:

  • 1、創(chuàng)建OkHttpClient對象
  • 2懈糯、拼接請求信息new Request.Builder()
  • 3涤妒、發(fā)送請求client.newCall(request).execute()并解析返回結(jié)果

但是要結(jié)合之前Http原理注意下GET和POST拼接請求時的用法有些區(qū)別的。
可以參考這篇博客的一些使用說明加深下理解:Android OkHttp3簡介和使用詳解

第一部分:OkHttp網(wǎng)絡請求流程分析

通過上面的例子可以看出赚哗,OkHttpClient 和 Request都是配置和管理整個網(wǎng)絡請求的她紫,真正的請求開始是從client.newCall(request).execute()開始的。

(1)蜂奸、創(chuàng)建一個client.newCall(request)犁苏,但其實真正new出來的對象是Call的子類RealCall對象
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
    .....
    @Override 
    public Call newCall(Request request) {
      return RealCall.newRealCall(this, request, false /* for web socket */);
    }
    .....
}
final class RealCall implements 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); //創(chuàng)建一個RealCall對象
      call.eventListener = client.eventListenerFactory().create(call); //創(chuàng)建網(wǎng)絡請求的監(jiān)聽對象
      return call;
    }
    
     private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        this.client = client;
        this.originalRequest = originalRequest; //初始的請求信息
        this.forWebSocket = forWebSocket; //默認設置不支持websocket
        this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
        this.timeout = new AsyncTimeout() {
          @Override 
          protected void timedOut() { //設置超時
            cancel();
          }
        };
        this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
    }   
    ......
}

這里簡單說明下兩個需要注意的地方

  • 1、forWebSocket :代表當前請求是否是給WebSocket來用的扩所,默認設置不支持。WebSocket也是一種交互協(xié)議朴乖,特點是服務端是可以主動給客戶端發(fā)消息的祖屏。可以看看這篇文章理解下:看完讓你徹底搞懂Websocket原理
  • 2买羞、retryAndFollowUpInterceptor:是OkHttp默認的第一個攔截器袁勺,用于處理請求錯誤重試和重定向的。后面會講解
(2)畜普、發(fā)送異步請求RealCall.enqueue(Callback) 或者 同步請求RealCall.execute()
@Override 
public void enqueue(Callback responseCallback) {
    synchronized (this) {
        //每個請求只能之執(zhí)行一次
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this); //監(jiān)聽器開始
    client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback)); //添加到異步隊列里
}

@Override 
public Response execute() throws IOException {
    synchronized (this) {
        //每個請求只能之執(zhí)行一次
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    timeout.enter(); //超時計時開始
    eventListener.callStart(this); //監(jiān)聽器開始
    try {
        client.dispatcher().executed(this);  //添加到同步隊列里
        Response result = getResponseWithInterceptorChain(); //返回結(jié)果
        if (result == null) throw new IOException("Canceled");
        return result;
    } catch (IOException e) {
        e = timeoutExit(e);
        eventListener.callFailed(this, e); //監(jiān)聽結(jié)束
        throw e;
    } finally {
        client.dispatcher().finished(this); //從隊列移除并執(zhí)行下一個任務
    }
}

這兩段代碼前面是很相似的期丰,都是將Call添加到client.dispatcher().enqueue()里。差異在于new RealCall.AsyncCall(responseCallback),這里做了什么呢钝荡?其實這里繼承NamedRunnable 創(chuàng)建了一個線程去做異步請求了街立。先看看源碼實現(xiàn):

public abstract class NamedRunnable implements Runnable {
   ......
  @Override 
    public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      //采用模板方法讓子類將具體的操作放到此execute()方法
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
  ......
}


final class AsyncCall extends NamedRunnable {
    ......
    @Override 
    protected void execute() {
        boolean signalledCallback = false;  //這個標記為主要是避免異常時2次回調(diào)
        timeout.enter();
        try {
            Response response = getResponseWithInterceptorChain(); //返回結(jié)果
            if (retryAndFollowUpInterceptor.isCanceled()) {
                signalledCallback = true;
                responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
            } else {
                signalledCallback = true;
                responseCallback.onResponse(RealCall.this, response);
            }
        } catch (IOException e) {
            e = timeoutExit(e);
            if (signalledCallback) {
                // Do not signal the callback twice!
                Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
            } else {
                eventListener.callFailed(RealCall.this, e); //監(jiān)聽結(jié)束
                responseCallback.onFailure(RealCall.this, e);
            }
        } finally {
            client.dispatcher().finished(this);//從隊列移除并執(zhí)行下一個任務
        }
    }
    ...
}
(3)、Dispatcher 請求任務調(diào)度器

在剛才的分析中埠通,發(fā)現(xiàn)請求的開始都會調(diào)用client.dispatcher().executed() 和 請求的結(jié)束client.dispatcher().finished()赎离。思考下,那這個dispatcher()在扮演什么角色呢端辱?

public final class Dispatcher {
    private int maxRequests = 64; //最大請求數(shù)量
    private int maxRequestsPerHost = 5; //每臺主機最大的請求數(shù)量
    private @Nullable Runnable idleCallback;
    
    /** Executes calls. Created lazily. */
    private @Nullable ExecutorService executorService; //線程池
    
    /** Ready async calls in the order they'll be run. */
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); //準備執(zhí)行的請求隊列
    
    /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); // 正在運行的異步請求隊列
    
    /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
    private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); // 正在運行的同步請求隊列
    
    /** 初始化了一個線程池梁剔,核心線程的數(shù)量為0 ,最大的線程數(shù)量為Integer.MAX_VALUE(無限制)舞蔽,
        空閑線程存在的最大時間為60秒荣病,空閑60s就會被回收*/
    public synchronized ExecutorService executorService() {
        if (executorService == null) {
          executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
              new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
        }
        return executorService;
    }
}  

先看看,同步請求時渗柿,Dispatcher 做了什么众雷?其實就僅僅是將當期的同步請求做了一個隊列的管理而已。

/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call); //添加到隊列中
}

/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
    finished(runningSyncCalls, call); //從隊列中移除
}

再看看異步請求時做祝,Dispatcher 又做了什么砾省?

void enqueue(AsyncCall call) {
    synchronized (this) {
        readyAsyncCalls.add(call); //添加到準備執(zhí)行的請求隊列
    }
    promoteAndExecute();  //挑選合適的請求并執(zhí)行
}

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>(); //符合條件的請求
    boolean isRunning;
    synchronized (this) {
        //遍歷沒執(zhí)行過的請求
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
            //正在執(zhí)行的請求,沒有超負載:最大請求數(shù)不超過64個混槐,單個Host請求不超出5個编兄。
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

            //將符合條件的請求拿出來
            i.remove();
            executableCalls.add(asyncCall); 
            runningAsyncCalls.add(asyncCall);
        }
        isRunning = runningCallsCount() > 0;
    }

    //將符合的請求執(zhí)行
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
        AsyncCall asyncCall = executableCalls.get(i);
        asyncCall.executeOn(executorService());  //放到線程池執(zhí)行
    }

    return isRunning;
}

final class AsyncCall extends NamedRunnable {
    ......
    void executeOn(ExecutorService executorService) {
        assert (!Thread.holdsLock(client.dispatcher()));
        boolean success = false;
        try {
            executorService.execute(this); //線程池執(zhí)行
            success = true;
        } catch (RejectedExecutionException e) {
            InterruptedIOException ioException = new InterruptedIOException("executor rejected");
            ioException.initCause(e);
            eventListener.callFailed(RealCall.this, ioException);
            responseCallback.onFailure(RealCall.this, ioException);
        } finally {
            if (!success) { //如果失敗了移除
                client.dispatcher().finished(this); // This call is no longer running!
            }
        }
    }
    ...
}

/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
    finished(runningAsyncCalls, call);  //從隊列中移除
}

最后來看看finished()具體做了什么事情。

private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) { 
        //從隊列中移除當前的任務
        if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
        idleCallback = this.idleCallback;
    }

    //推動下一個任務的執(zhí)行
    boolean isRunning = promoteAndExecute();

    if (!isRunning && idleCallback != null) {
        idleCallback.run();
    }
}

總結(jié)的來說声登,Dispatcher 就是個請求任務調(diào)度器狠鸳,其實準確的來說主要是做異步請求線程管理的,管理多個網(wǎng)絡請求悯嗓。

(4)件舵、獲取響應信息getResponseWithInterceptorChain()

其實從剛才的分析,到getResponseWithInterceptorChain()獲得請求返回信息脯厨,整體的網(wǎng)絡請求流程就分析完畢了铅祸。
但是,分析完也沒有發(fā)現(xiàn)有涉及到Http原理的內(nèi)容呀合武。你是不是又吹牛啦临梗。請看第二部分內(nèi)容

第二部分:OkHttp網(wǎng)絡請求與Http的原理結(jié)合

(1)、OkHttpClient 大雜燴解析
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
    ......
    final Dispatcher dispatcher;  //請求任務調(diào)度器
    final @Nullable Proxy proxy;  //代理
    final List<Protocol> protocols; //支持的協(xié)議
    final List<ConnectionSpec> connectionSpecs; //連接配置稼跳,就是Https連接時的加密套件Cipher Suite
    final List<Interceptor> interceptors; //攔截器集合盟庞,主要用于自定義攔截器
    final List<Interceptor> networkInterceptors; 
    final EventListener.Factory eventListenerFactory; //監(jiān)聽器,整個網(wǎng)絡請求過程的監(jiān)聽器
    final ProxySelector proxySelector;
    final CookieJar cookieJar;  //Cookie機制,存放服務器的cookie
    final @Nullable Cache cache; //緩存, 核心是DiskLruCache
    final @Nullable InternalCache internalCache; //緩存接口汤善,配合Cache使用
    final SocketFactory socketFactory; //Socket
    final SSLSocketFactory sslSocketFactory; // TLS連接的Socket
    final CertificateChainCleaner certificateChainCleaner; //證書清潔器什猖,可以理解為只包含證書重要信息
    final HostnameVerifier hostnameVerifier; //主機名驗證器
    final CertificatePinner certificatePinner; //證書固定器票彪,主要用于自簽名證書的驗證
    final Authenticator proxyAuthenticator;
    final Authenticator authenticator; //授權驗證,比如說登錄密碼錯誤訪問返回401不狮,需要重新授權降铸。
    final ConnectionPool connectionPool; //連接池,
    final Dns dns; //Dns解析荤傲,查找域名
    final boolean followSslRedirects; //是否允許切換重定向垮耳,就是當訪問http后,返回https重定向是否支持
    final boolean followRedirects; //是否允許重定向
    final boolean retryOnConnectionFailure; //連接失敗是否重試
    final int callTimeout; //
    final int connectTimeout; //連接超時
    final int readTimeout; //讀超時
    final int writeTimeout; //寫超時
    final int pingInterval; 和WebSocket有關遂黍。為了保持長連接终佛,必須間隔一段時間發(fā)送一個ping指令進行保活

    public OkHttpClient() {
        this(new Builder());
    }

    ......

    public Builder() {
      dispatcher = new Dispatcher(); //默認創(chuàng)建一個任務調(diào)度器
      protocols = DEFAULT_PROTOCOLS;  //默認支持的協(xié)議為HTTP_1.1 和 HTTP_2
      connectionSpecs = DEFAULT_CONNECTION_SPECS; //默認支持TLS1.0~1.3 雾家,加密方式SHA/AES/DES等
      eventListenerFactory = EventListener.factory(EventListener.NONE); //默認創(chuàng)建一個監(jiān)聽器
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      cookieJar = CookieJar.NO_COOKIES;  //默認沒有具體實現(xiàn)铃彰,需要自定義實現(xiàn)
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE; //默認OkHostnameVerifier()實現(xiàn)取證書的第一個host
      certificatePinner = CertificatePinner.DEFAULT; //默認為空
      proxyAuthenticator = Authenticator.NONE; //默認為空
      authenticator = Authenticator.NONE; //默認為空
      connectionPool = new ConnectionPool(); //連接池默認支持5個并發(fā)socket連接,默認keepalive時間為5分鐘
      dns = Dns.SYSTEM; //默認使用系統(tǒng)的域名解析
      followSslRedirects = true;  //默認支持http 和 https 切換重定向
      followRedirects = true; //默認支持重定向
      retryOnConnectionFailure = true; // 默認支持失敗重試
      callTimeout = 0;
      connectTimeout = 10_000; //默認10秒
      readTimeout = 10_000; //默認10秒
      writeTimeout = 10_000; //默認10秒
      pingInterval = 0;
    }
    ......
}

通過簡單分析OkHttpClient 芯咧,發(fā)現(xiàn)OkHttp可配置的東西很多牙捉,涉及到的知識點也很復雜。結(jié)合前文解析的原理分析發(fā)現(xiàn)敬飒,OkHttp底層實現(xiàn)了網(wǎng)絡請求的線程調(diào)度邪铲,域名解析,緩存機制无拗,證書校驗带到,Socket連接等功能。而對應各個功能點的實現(xiàn)又是由不同的攔截器實現(xiàn)的英染。

(2)揽惹、OkHttp 攔截器 (最核心部分)

回頭看看之前分析,最終分析到getResponseWithInterceptorChain獲取到返回的信息就結(jié)束了四康。但是這里面到底發(fā)生了什么事呢搪搏?

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>(); //創(chuàng)建一個攔截器鏈列表
    interceptors.addAll(client.interceptors());  // 1、添加自定義攔截器闪金,忽略
    interceptors.add(retryAndFollowUpInterceptor); // 2疯溺、添加重試和重定向的攔截器
    interceptors.add(new BridgeInterceptor(client.cookieJar())); // 3、添加處理請求頭和響應體的攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));// 4毕泌、添加處理緩存邏輯的攔截器
    interceptors.add(new ConnectInterceptor(client));// 5喝检、添加選擇請求連接的攔截器
    if (!forWebSocket) { //忽略,不太涉及到WebSocket部分
        interceptors.addAll(client.networkInterceptors());
    }
    // 6撼泛、添加向服務器發(fā)送請求報文、從服務器讀取并解析響應報文的攔截器
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //將攔截器設置成鏈
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
            originalRequest, this, eventListener, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());

    //攔截器鏈從第一個攔截器啟動執(zhí)行澡谭,并將結(jié)果返回
    return chain.proceed(originalRequest);
}

通過上面的代碼簡單注釋愿题,發(fā)現(xiàn)這個代碼其實很簡單损俭,分三個部分,但是理解起來可不簡單潘酗,而且每一個攔截器里面做的核心邏輯也不簡單杆兵,請看下面的分析。

攔截器鏈整體結(jié)構(gòu)設計

攔截器的基本代碼結(jié)構(gòu)

public interface Interceptor {
    //攔截器的核心邏輯方法
    //在這里方法里面可以額外的前置動作處理
    //也可以做額外的后置動作處理
    Response intercept(Chain chain) throws IOException;

    //攔截器鏈對象
    interface Chain {
        .....
        //方法名直譯是“前進”仔夺。換句話說就是調(diào)用下一個攔截器
        Response proceed(Request request) throws IOException;
        ......
    }
}

先拋開攔截器列表里各個攔截器的具體實現(xiàn)琐脏,整體理解下攔截器鏈結(jié)構(gòu)是如果實現(xiàn)的。

public final class RealInterceptorChain implements Interceptor.Chain {

    ......
    // 創(chuàng)建真正的攔截器鏈對象缸兔,傳入初始化信息
    public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
                                HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
                                EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
        this.interceptors = interceptors;
        this.connection = connection;
        this.streamAllocation = streamAllocation;
        this.httpCodec = httpCodec;
        this.index = index;
        this.request = request;
        this.call = call;
        this.eventListener = eventListener;
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
        this.writeTimeout = writeTimeout;
    }

    ......

    @Override public Response proceed(Request request) throws IOException {
        return proceed(request, streamAllocation, httpCodec, connection);
    }

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

        // Call the next interceptor in the chain.
        //創(chuàng)建一個新鏈對象日裙,但可以理解為還是之前的攔截器鏈,只是這個鏈起始攔截器變成了下一個攔截器惰蜜, 
        //index + 1就是關鍵信息昂拂,而且getResponseWithInterceptorChain中的起始點為0, 這段代碼理解為
        // 1抛猖、新建了一個攔截器鏈格侯,但起始還是之前的鏈,只是鏈起始點變成下一個
        // 2财著、取出下一個攔截器联四,執(zhí)行
        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); //每一個攔截器都持有鏈對象,推動下一個攔截器啟動,同時將結(jié)果返回撑教。

        ......

        return response;
    }
}

再來看看具體攔截器的代碼結(jié)構(gòu)是如何實現(xiàn)的

public class CustomInterceptor implements Interceptor {
    
    @Override
    public Response intercept(Chain chain) throws IOException {

        //前置動作,處理請求信息
        Request newRequest = oldRequestToNew(oldRequest);

        // 推動下一個攔截器執(zhí)行并返回結(jié)果
        Response oldResponse = chain.proceed(newRequest);

        //后置動作,處理返回信息
        Response newResponse = oldResponseToNew(oldResponse);

        return newResponse;
    }
}

總結(jié)來說就是:
OkHttp通過定義很多攔截器一步一步地對Request進行攔截處理朝墩,直到真正地發(fā)起請求向族,并獲取數(shù)據(jù)悬嗓,然后有一步一步地對Response進行攔截處理桐智,最后攔截的結(jié)果就是回調(diào)的最終Response得糜。

image.png

好了诫肠,整體攔截器結(jié)構(gòu)就分析到這里躁锡,后面分解動作煤伟,詳細解析下OkHttp默認定義的攔截器都做了什么事瞭吃。

RetryAndFollowUpInterceptor詳解:重試和重定向攔截器
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; //重試次數(shù)
        Response priorResponse = null;
        while (true) { //死循環(huán)
            if (canceled) {
                streamAllocation.release();
                throw new IOException("Canceled");
            }

            Response response;
            boolean releaseConnection = true; //標記位
            try {
                response = realChain.proceed(request, streamAllocation, null, null); //推動下一個攔截器執(zhí)行
                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.getFirstConnectException();
                }
                releaseConnection = false;
                continue; //繼續(xù)重試
            } 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; //繼續(xù)重試
            } 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,可能當前proceed返回空矫夷,以之前的繼續(xù)
                response = response.newBuilder()
                        .priorResponse(priorResponse.newBuilder()
                                .body(null)
                                .build())
                        .build();
            }

            Request followUp;
            try {
                followUp = followUpRequest(response, streamAllocation.route());  //根據(jù)返回的請求碼進行處理得到新的Request
            } catch (IOException e) {
                streamAllocation.release();
                throw e;
            }

            //注意不同的請求碼返回的結(jié)果不一樣葛闷,當為空時,就直接返回結(jié)果双藕。
            //默認200直接將結(jié)果返回淑趾,但是當一些特殊情況導致請求失敗時,重試了也沒用忧陪,也直接將錯誤結(jié)果返回扣泊。
            if (followUp == null) { 
                streamAllocation.release();
                return response;
            }

            closeQuietly(response.body());

            if (++followUpCount > MAX_FOLLOW_UPS) { //最多重試和重定向20次
                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; //將得到處理后的request賦值給
            priorResponse = response; //記錄當前重試的結(jié)果
        }
    }
    ......
}

總結(jié)來說:

  • 1近范、沒有前置動作,直接推動下一個攔截器獲取結(jié)果延蟹,如果下一個攔截器請求異常评矩,直接continue重試
  • 2、針對返回的response 做后置動作阱飘,根據(jù)狀態(tài)碼處理獲取新的Request斥杜,如果為狀態(tài)碼200或其他特殊情況直接返回response ,如果是其他就繼續(xù)重試沥匈。
  • 3蔗喂、重試的次數(shù)做多為20次。
BridgeInterceptor詳解:處理請求頭和響應頭攔截器
public final class BridgeInterceptor implements Interceptor {
    ......
    @Override 
    public Response intercept(Chain chain) throws IOException {
        //獲取鏈傳進來的信息
        Request userRequest = chain.request();
        Request.Builder requestBuilder = userRequest.newBuilder();

        //根據(jù)配置傳進來的RequestBody配置請求頭
        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"); //默認開啟Keep-Alive模式
        }

        // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
        // the transfer stream.
        //添加默認的編碼類型為gzip
        boolean transparentGzip = false;
        if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
            transparentGzip = true;
            requestBuilder.header("Accept-Encoding", "gzip");
        }

        //設置Cookie緩存
        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());
        }

        //推動下一個攔截器執(zhí)行
        Response networkResponse = chain.proceed(requestBuilder.build());

        //保存服務端返回的cookie
        HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

        Response.Builder responseBuilder = networkResponse.newBuilder()
                .request(userRequest);
    
        //對數(shù)據(jù)解壓縮咐熙,并處理響應頭
        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();
    }
    ......
}

總結(jié)來說:

  • 1弱恒、前置動作,處理設置請求頭棋恼,并設置默認的User-Agent,Host,Keep-alive報頭信息返弹,和cookie信息
  • 2、針對返回的response 做后置動作爪飘,保存cookie并做gzip數(shù)據(jù)解壓處理义起。
CacheInterceptor詳解:處理緩存邏輯攔截器
public final class CacheInterceptor implements Interceptor {
    ......
    @Override
    public Response intercept(Chain chain) throws IOException {
        //根據(jù)請求獲取緩存結(jié)果
        Response cacheCandidate = cache != null
                ? cache.get(chain.request())
                : null;
        
        long now = System.currentTimeMillis();//當前時間

        //緩存策略類,該類決定了是使用緩存還是進行網(wǎng)絡請求
        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
        Request networkRequest = strategy.networkRequest;
        Response cacheResponse = strategy.cacheResponse;

        if (cache != null) {
            cache.trackResponse(strategy); //緩存統(tǒng)計
        }

        //cacheResponse 為null 表示緩存根據(jù)緩存策略不可用
        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.
        // 根據(jù)緩存策略返回的結(jié)果都為空师崎,說明當前的請求非法默终,直接返回504,Unsatisfiable Request
        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.
        // networkRequest 為空犁罩,而cacheResponse 不為空齐蔽,直接使用緩存返回
        if (networkRequest == null) {
            return cacheResponse.newBuilder()
                    .cacheResponse(stripBody(cacheResponse))
                    .build();
        }

        Response networkResponse = null;
        try {
            networkResponse = chain.proceed(networkRequest); //推動下一個攔截器執(zhí)行
        } finally {
            // If we're crashing on I/O or otherwise, don't leak the cache body.
            if (networkResponse == null && cacheCandidate != null) {
                closeQuietly(cacheCandidate.body());
            }
        }

        // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {
            if (networkResponse.code() == HTTP_NOT_MODIFIED) { //根據(jù)狀態(tài)碼,合并更新緩存信息
                Response response = cacheResponse.newBuilder()
                        .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                        .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                        .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                        .cacheResponse(stripBody(cacheResponse))
                        .networkResponse(stripBody(networkResponse))
                        .build();
                networkResponse.body().close();

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

        //轉(zhuǎn)換下response
        Response response = networkResponse.newBuilder()
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();

        //如果設置了緩存
        if (cache != null) {
            // 有響應體 && 緩存策略可緩存
            if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
                // Offer this request to the cache.
                CacheRequest cacheRequest = cache.put(response);
                return cacheWritingResponse(cacheRequest, response);
            }

            //移除符合條件的method的Request緩存
            if (HttpMethod.invalidatesCache(networkRequest.method())) {
                try {
                    cache.remove(networkRequest);
                } catch (IOException ignored) {
                    // The cache cannot be written.
                }
            }
        }

        return response;
    }
    ......
}

總結(jié)來說:
1床估、前置動作含滴,根據(jù)是否設置緩存來判斷緩存策略,然后根據(jù)緩存策略返回的兩個信息networkRequest 和cacheResponse 做判斷處理丐巫。

  • 如果網(wǎng)絡不可用并且無可用的有效緩存谈况,則返回504錯誤,提示Unsatisfiable Request递胧;
  • 如果網(wǎng)絡不可用碑韵,則直接使用緩存;
  • 如果網(wǎng)絡可用,則進行網(wǎng)絡請求

2缎脾、針對返回的response 做后置動作祝闻,并跟cacheResponse 是否可用做邏輯判斷

  • 如果根據(jù)狀態(tài)碼為HTTP_NOT_MODIFIED,說明緩存信息還有效遗菠,合并更新緩存信息治筒;
  • 如果如果沒有緩存屉栓,則根據(jù)緩存策略寫入新的緩存舷蒲,并判斷該緩存是否要移除耸袜;

3、注意還有一個比較重要的CacheStrategy緩存策略類牲平,是緩存判斷邏輯的核心堤框。(PS:緩存讀寫使用DiskLruCache實現(xiàn)的)

public final class 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;
    }

    /** Returns a strategy to use assuming the request can use the network. */
    private CacheStrategy getCandidate() {
        // No cached response.
        if (cacheResponse == null) { //沒有緩存,直接網(wǎng)絡請求
            return new CacheStrategy(request, null);
        }

        // Drop the cached response if it's missing a required handshake.
        if (request.isHttps() && cacheResponse.handshake() == null) { //請求為https但沒有握手纵柿,直接網(wǎng)絡請求
            return new CacheStrategy(request, null);
        }

        // If this response shouldn't have been stored, it should never be used
        // as a response source. This check should be redundant as long as the
        // persistence store is well-behaved and the rules are constant.
        if (!isCacheable(cacheResponse, request)) { //不可緩存蜈抓,直接網(wǎng)絡請求
            return new CacheStrategy(request, null);
        }

        CacheControl requestCaching = request.cacheControl();
        //請求頭nocache或者請求頭包含If-Modified-Since或者If-None-Match
        //請求頭包含If-Modified-Since或者If-None-Match意味著本地緩存過期,需要服務器驗證
        if (requestCaching.noCache() || hasConditions(request)) { 
            return new CacheStrategy(request, null);
        }

        CacheControl responseCaching = cacheResponse.cacheControl();

        //獲取一系列標記時間戳
        long ageMillis = cacheResponseAge();
        long freshMillis = computeFreshnessLifetime();

        if (requestCaching.maxAgeSeconds() != -1) {
            freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
        }

        long minFreshMillis = 0;
        if (requestCaching.minFreshSeconds() != -1) {
            minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
        }

        long maxStaleMillis = 0;
        if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
            maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
        }

        //responseCaching 可緩存昂儒,并且ageMillis + minFreshMillis < freshMillis + maxStaleMillis
        if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
            Response.Builder builder = cacheResponse.newBuilder();
            if (ageMillis + minFreshMillis >= freshMillis) {
                builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
            }
            long oneDayMillis = 24 * 60 * 60 * 1000L;
            if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
                builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
            }
            //這里對比時間后沟使,在響應頭添加warning。
            //意味著雖過期渊跋,但可用腊嗡,只是會在響應頭添加warning
            return new CacheStrategy(null, builder.build());
        }

        // Find a condition to add to the request. If the condition is satisfied, the response body
        // will not be transmitted.
        //根據(jù)請求頭設置的信息判斷etag 、lastModified 拾酝、servedDate 等是否為空燕少,
        String conditionName;
        String conditionValue;
        if (etag != null) {
            conditionName = "If-None-Match";
            conditionValue = etag;
        } else if (lastModified != null) {
            conditionName = "If-Modified-Since";
            conditionValue = lastModifiedString;
        } else if (servedDate != null) {
            conditionName = "If-Modified-Since";
            conditionValue = servedDateString;
        } else {
            return new CacheStrategy(request, null); // No condition! Make a regular request.
        }

        Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
        Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

        //請求頭添加If-None-Match、If-Modified-Since蒿囤、If-Modified-Since等信息
        Request conditionalRequest = request.newBuilder()
                .headers(conditionalRequestHeaders.build())
                .build();
        return new CacheStrategy(conditionalRequest, cacheResponse);
    }
    ......
}

總結(jié)來說:緩存策略根據(jù)緩存是否存在客们、請求頭設置字段NoCache 、If-Modified-Since或者If-None-Match等信息及緩存時間來結(jié)合判斷是否使用緩存材诽。

講解后面兩個攔截器之前底挫,這里插播一些知識點方便理解。

keep-alive機制
image.png

在HTTP/1.0 之前正常發(fā)送一個請求都需要經(jīng)過三次握手建立一個TCP連接脸侥,然后進行數(shù)據(jù)交互建邓,最后再經(jīng)過四次握手釋放連接。但是在復雜的網(wǎng)絡請求中湿痢,重復的創(chuàng)建和釋放連接極大地影響了網(wǎng)絡效率涝缝,同時也增加了系統(tǒng)開銷。為了有效地解決這一問題譬重,HTTP/1.1提出了Keep-Alive機制:當一個HTTP請求的數(shù)據(jù)傳輸結(jié)束后拒逮,TCP連接不立即釋放,有一段存活時間臀规,如果此時有新的HTTP請求滩援,則可以直接復用TCP連接,從而省去了TCP的釋放和再次創(chuàng)建的開銷塔嬉,減少了網(wǎng)絡延時玩徊。http 1.0中默認是關閉的租悄,需要在http頭加入"Connection: Keep-Alive",才能啟用Keep-Alive恩袱;http 1.1中默認啟用Keep-Alive泣棋,如果加入"Connection: close ",才關閉畔塔。

管道機制(pipelining)

額外補充下潭辈,在HTTP/1.1的時候還引入了一個概念叫管道機制(pipelining),即在同一個TCP連接里面澈吨,客戶端可以同時發(fā)送多個請求把敢,只是服務端響應是按順序來響應數(shù)據(jù)。舉例來說谅辣,客戶端需要請求兩個資源修赞。以前的做法是,在同一個TCP連接里面桑阶,先發(fā)送A請求柏副,然后等待服務器做出回應,收到后再發(fā)出B請求联逻。管道機制則是允許瀏覽器同時發(fā)出A請求和B請求搓扯,但是服務器還是按照順序,先回應A請求包归,完成后再回應B請求锨推。

總結(jié)下兩者的區(qū)別,下面通過圖來加深下keep-alive機制 和 管道機制的理解:

當未開啟時公壤,每次請求都需要建立TCP和關閉TCP换可,而開啟之后,只需要開啟和關閉一次就好了厦幅。


微信圖片_20190507170849.jpg

有了pipelining的支持后沾鳄,就可以一次性發(fā)送多次請求,而不需要一次一次的發(fā)送了确憨。


微信圖片_20190507171629.jpg
總結(jié)來說就是译荞,Keep-Alive可以讓一個tcp連接保持不關閉,但是每次的請求都必須在上一次響應之后休弃,是為了解決每次http請求都要求建立和釋放的過程吞歼;pipelining是為了解決每次http請求必須一次請求一次響應(但是注意這里的隱形條件就是tcp是處于連接狀態(tài)的)。

但是管道機制有自身的很多限制,默認都是關閉的塔猾。參考:HTTP管線化

多路復用機制

通過上述概念理解篙骡,可以發(fā)現(xiàn)在http1.1中更多的解決了請求連接的問題,但是數(shù)據(jù)的傳輸和交互還是按順序的發(fā)送和響應。那這個傳輸數(shù)據(jù)的過程是否可以優(yōu)化呢糯俗?
舉個例子:客戶端要向服務器發(fā)送Hello尿褪、World兩個單詞,只能是先發(fā)送Hello再發(fā)送World得湘,沒辦法同時發(fā)送這兩個單詞杖玲。不然服務器收到的可能就是HWeolrllod(注意是穿插著發(fā)過去了,但是順序還是不會亂)忽刽。這樣服務器就懵b了,“發(fā)的這是啥玩意我不認識”跪帝。

假設一個場景:在同一個TCP連接里面缤谎,客戶端給服務端發(fā)送一個請求坷澡,但是數(shù)據(jù)不是按順序的來發(fā)送過去托呕,服務端能不能正確的解析出來呢?答案是肯定的频敛,發(fā)送數(shù)據(jù)時只要給每個數(shù)據(jù)打上順序標簽项郊,服務端接收后按順序拼接就可以了。同樣的斟赚,服務端給客戶端發(fā)送數(shù)據(jù)也可以類似的着降。

基于上述的結(jié)果再假設一個場景:在同一個TCP連接里面,客戶端給服務端發(fā)送多個請求拗军,多個請求同時發(fā)送的多個數(shù)據(jù)任洞,而數(shù)據(jù)也不是按順序的來發(fā)送過去,服務端能不能正確的解析出來呢发侵?答案也是肯定的交掏。只要給每一個請求打上標簽,比如請求1標記位A器紧,發(fā)送的數(shù)據(jù)包分a_1耀销、a_2、...、a_n熊尉。然后服務端就根據(jù)標簽就知道數(shù)哪個請求發(fā)送了并及時響應對應的數(shù)據(jù)罐柳。

而Http2.0的就是基于這樣場景引入二進制數(shù)據(jù)幀和流的概念,其中幀對數(shù)據(jù)進行順序標識狰住。進而基于這樣的概念就可以實現(xiàn)多路復用张吉,進而實現(xiàn)請求的并發(fā)處理。
詳細的說明可參考:HTTP 2.0 原理詳細分析

ConnectInterceptor詳解:請求連接攔截器(最核心最底層的地方)
public final class ConnectInterceptor implements Interceptor {
    ......
    @Override 
    public Response intercept(Chain chain) throws IOException {
        //獲取鏈傳進來的信息
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Request request = realChain.request();
        StreamAllocation streamAllocation = realChain.streamAllocation();

        // We need the network to satisfy this request. Possibly for validating a conditional GET.
        boolean doExtensiveHealthChecks = !request.method().equals("GET");
        //獲取兩個對象httpCodec  和 connection 
        HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
        RealConnection connection = streamAllocation.connection();

        return realChain.proceed(request, streamAllocation, httpCodec, connection); //調(diào)用下一個攔截器
    }
}

總結(jié)來說:

  • 1催植、無后置動作肮蛹,只有前置動作,最核心的代碼就是streamAllocation.newStream()创南,返回Connection和HttpCodec對象伦忠。這兩個對象是什么含義呢?從字面的理解應該就是分配一個網(wǎng)絡請求的連接 和 數(shù)據(jù)的編碼解碼器稿辙,而且要注意HttpCodec有Http1Codec和Http2Codec兩個子類實現(xiàn)昆码,這有什么區(qū)別呢?

回頭再來看看streamAllocation.newStream()代碼

public HttpCodec newStream(
        OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    ......
    try {
        RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
                writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
        HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
        ......
    } catch (IOException e) {
        throw new RouteException(e);
    }
}

再來看看尋找連接過程的核心代碼

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
                                             int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
                                             boolean doExtensiveHealthChecks) throws IOException {
    while (true) { //找到一個可用的連接(如果連接不可用邻储,這個過程會一直持續(xù)哦)
        RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
                pingIntervalMillis, connectionRetryEnabled);

        // If this is a brand new connection, we can skip the extensive health checks.
        synchronized (connectionPool) {
            if (candidate.successCount == 0) {//如果是一個新的連接赋咽,直接返回就好
                return candidate;
            }
        }

        // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
        // isn't, take it out of the pool and start again.
        if (!candidate.isHealthy(doExtensiveHealthChecks)) {
            noNewStreams(); //連接不好使的話,從移除連接池吨娜,并持續(xù)尋找
            continue;
        }

        return candidate;
    }
}

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
        if (released) throw new IllegalStateException("released");
        if (codec != null) throw new IllegalStateException("codec != null");
        if (canceled) throw new IOException("Canceled");

        // Attempt to use an already-allocated connection. We need to be careful here because our
        // already-allocated connection may have been restricted from creating new streams.
        // 通過releaseIfNoNewStreams()判斷該鏈接是否可以創(chuàng)建新的Stream脓匿,如果不能就關閉當前流
        // 經(jīng)過判斷后如果connection不為null,則連接是可用的
        releasedConnection = this.connection;
        toClose = releaseIfNoNewStreams();  
        if (this.connection != null) { 
            // We had an already-allocated connection and it's good.
            result = this.connection;
            releasedConnection = null;
        }
        if (!reportedAcquired) {
            // If the connection was never reported acquired, don't report it as released!
            releasedConnection = null;
        }

        // 試圖從連接池中找到可用的連接宦赠,注意最后一個參數(shù)是null陪毡,代表沒有路由信息
        if (result == null) {
            // Attempt to get a connection from the pool.
            Internal.instance.get(connectionPool, address, this, null);
            if (connection != null) {
                foundPooledConnection = true;
                result = connection;
            } else {
                selectedRoute = route;
            }
        }
    }
    //根據(jù)上面的邏輯做后續(xù)的處理
    closeQuietly(toClose);

    if (releasedConnection != null) {
        eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
        eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
        // If we found an already-allocated or pooled connection, we're done.
        return result;
    }

    // If we need a route selection, make one. This is a blocking operation.
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
        newRouteSelection = true;
        routeSelection = routeSelector.next();
    }

    synchronized (connectionPool) {
        if (canceled) throw new IOException("Canceled");

        if (newRouteSelection) {
            // Now that we have a set of IP addresses, make another attempt at getting a connection from
            // the pool. This could match due to connection coalescing.
            List<Route> routes = routeSelection.getAll();
            for (int i = 0, size = routes.size(); i < size; i++) {
                Route route = routes.get(i);
                Internal.instance.get(connectionPool, address, this, route); //通過路由配置,再次從連接池查找是否有可復用連接
                if (connection != null) {
                    foundPooledConnection = true;
                    result = connection;
                    this.route = route;
                    break;
                }
            }
        }

        if (!foundPooledConnection) {
            if (selectedRoute == null) {
                selectedRoute = routeSelection.next();
            }

            // Create a connection and assign it to this allocation immediately. This makes it possible
            // for an asynchronous cancel() to interrupt the handshake we're about to do.
            route = selectedRoute;
            refusedStreamCount = 0;
            result = new RealConnection(connectionPool, selectedRoute); //實在找不到合適的才新建一個連接
            acquire(result, false);
        }
    }

    // If we found a pooled connection on the 2nd time around, we're done.
    if (foundPooledConnection) {
        eventListener.connectionAcquired(call, result);
        return result;
    }

    // Do TCP + TLS handshakes. This is a blocking operation.
    // 給新建的連接建立連接過程
    //封裝socket袱瓮,建立TCP + TLS的過程
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
            connectionRetryEnabled, call, eventListener); 
    routeDatabase().connected(result.route()); 

    Socket socket = null;
    synchronized (connectionPool) {
        reportedAcquired = true;

        // Pool the connection.
        Internal.instance.put(connectionPool, result); //放到連接池

        // If another multiplexed connection to the same address was created concurrently, then
        // release this connection and acquire that one.
        // 如果是一個http2連接缤骨,由于http2連接應具有多路復用特性,
        // 如果同時存在多個連向同一個地址的多路復用連接尺借,則關閉多余連接绊起,只保留一個
        if (result.isMultiplexed()) {
            socket = Internal.instance.deduplicate(connectionPool, address, this);
            result = connection;
        }
    }
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    return result;
}

總結(jié)來說:

  • 查看當前streamAllocation是否有之前已經(jīng)分配過的連接,有則直接使用
  • 從連接池中查找可用的連接燎斩,有則返回該連接
  • 配置路由虱歪,配置后再次從連接池中查找是否有可用連接,有則直接返回
  • 新建一個連接栅表,并修改其StreamAllocation標記計數(shù)笋鄙,將其放入連接池中
  • 查看連接池是否有重復的多路復用連接,有則清除怪瓶。

接下來看看HttpCodec具體代碼實現(xiàn)

public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
                          StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
        return new Http2Codec(client, chain, streamAllocation, http2Connection); //創(chuàng)建Http2Codec對象
    } else {
        socket.setSoTimeout(chain.readTimeoutMillis());
        source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
        sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
        return new Http1Codec(client, streamAllocation, source, sink); //創(chuàng)建Http1Codec對象
    }
}

總結(jié)來說:HttpCodec只是針對不同的Http版本創(chuàng)建不同的對象萧落,而這個對應有什么區(qū)別呢?其實就是前面多路復用機制提及的,Http的數(shù)據(jù)格式的區(qū)別找岖,http1.x的數(shù)據(jù)格式還是文本格式陨倡,而http2則是基于數(shù)據(jù)幀的形式。

CallServerInterceptor詳解:負責向服務器發(fā)起真正的訪問請求许布,并接收服務器返回的響應
public final class CallServerInterceptor implements Interceptor {
@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());
        httpCodec.writeRequestHeaders(request);  //根據(jù)不同的http版本寫請求頭信息
        realChain.eventListener().requestHeadersEnd(realChain.call(), request);

        Response.Builder responseBuilder = null;
        //若請求方法允許傳輸請求體兴革,且request的請求體不為空
        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.
            //如果在請求頭中存在"Expect:100-continue",
            //則請求需要等待服務器回復是否能夠處理請求體蜜唾,服務器若不接受請求體則會返回一個非空的編碼
            if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
                httpCodec.flushRequest();
                realChain.eventListener().responseHeadersStart(realChain.call());
                //接收服務器的返回請求杂曲,服務器若不接受請求體則會返回一個非空的響應
                responseBuilder = httpCodec.readResponseHeaders(true);
            }

            //若responseBuilder為null,則Expect不為100-continue或服務器接收請求體,開始寫入請求體
            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.
                // 如果服務器拒絕接收請求體,且不是http2袁余,則禁止此連接被重新使用
                streamAllocation.noNewStreams();
            }
        }

        httpCodec.finishRequest(); //完成請求寫入

        //通過httpCodec獲取響應頭
        if (responseBuilder == null) {
            realChain.eventListener().responseHeadersStart(realChain.call());
            responseBuilder = httpCodec.readResponseHeaders(false);
        }

        //通過responseBuilder填入信息創(chuàng)建Response
        Response response = responseBuilder
                .request(request)
                .handshake(streamAllocation.connection().handshake())
                .sentRequestAtMillis(sentRequestMillis)
                .receivedResponseAtMillis(System.currentTimeMillis())
                .build();

        int code = response.code(); //獲取返回碼
        if (code == 100) { //如果是101(升級到Http2協(xié)議)
            // 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);

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

         //若請求或者服務器要求斷開連接擎勘,則斷開
        if ("close".equalsIgnoreCase(response.request().header("Connection"))
                || "close".equalsIgnoreCase(response.header("Connection"))) {
            streamAllocation.noNewStreams();
        }

        //若返回204/205(服務器均未返回響應體)且響應體長度大于0)則拋出異常
        if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
            throw new ProtocolException(
                    "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
        }

        return response;
    }
}

總結(jié)來說:整個請求的發(fā)送和數(shù)據(jù)響應都是通過HttpCodec 對象完成。而HttpCodec 實際上利用的是 Okio本質(zhì)還是通過Socket完成請求泌霍。

  • 向服務器發(fā)送 request header
  • 如果有 request body货抄,就向服務器發(fā)送
  • 讀取 response header,先構(gòu)造一個 Response對象朱转;
  • 如果有 response body,就在 3 的基礎上加上 body 構(gòu)造一個新的 Response對象积暖;

最后通過圖解來總結(jié)整個OkHttp的請求流程圖:


image.png
后序:這篇文章從源碼角度整體分析OkHttp藤为,攔截器的責任鏈模式設計非常優(yōu)雅,并且框架從底層一步一步封裝實現(xiàn)整個網(wǎng)絡請求夺刑,并針對Http的版本也做了完美的適配缅疟,期待Http2的推廣。

如果覺得我的文章對你有幫助遍愿,請隨意贊賞存淫。您的支持將鼓勵我繼續(xù)創(chuàng)作!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載沼填,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者桅咆。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坞笙,隨后出現(xiàn)的幾起案子岩饼,更是在濱河造成了極大的恐慌,老刑警劉巖薛夜,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件籍茧,死亡現(xiàn)場離奇詭異,居然都是意外死亡梯澜,警方通過查閱死者的電腦和手機寞冯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吮龄,你說我怎么就攤上這事俭茧。” “怎么了螟蝙?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵恢恼,是天一觀的道長。 經(jīng)常有香客問我胰默,道長场斑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任牵署,我火速辦了婚禮漏隐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奴迅。我一直安慰自己青责,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布取具。 她就那樣靜靜地躺著脖隶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪暇检。 梳的紋絲不亂的頭發(fā)上产阱,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音块仆,去河邊找鬼构蹬。 笑死,一個胖子當著我的面吹牛悔据,可吹牛的內(nèi)容都是我干的庄敛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼科汗,長吁一口氣:“原來是場噩夢啊……” “哼藻烤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肛捍,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤隐绵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拙毫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體依许,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年缀蹄,在試婚紗的時候發(fā)現(xiàn)自己被綠了峭跳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膘婶。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蛀醉,靈堂內(nèi)的尸體忽然破棺而出悬襟,到底是詐尸還是另有隱情,我是刑警寧澤拯刁,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布脊岳,位于F島的核電站,受9級特大地震影響垛玻,放射性物質(zhì)發(fā)生泄漏割捅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一帚桩、第九天 我趴在偏房一處隱蔽的房頂上張望亿驾。 院中可真熱鬧,春花似錦账嚎、人聲如沸莫瞬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疼邀。三九已至,卻和暖如春召锈,著一層夾襖步出監(jiān)牢的瞬間檩小,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工烟勋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筐付。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓卵惦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓦戚。 傳聞我的和親對象是個殘疾皇子沮尿,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容