淺析OkHttp3

OkHttp3

前言

做React Native的時(shí)候遇到業(yè)務(wù)線反饋的一個(gè)Bug:在使用Charles做代理的時(shí)候淘菩,將reactTimeout值改小的時(shí)候渠牲,有時(shí)候會(huì)發(fā)現(xiàn)在Charles沒有捕獲到Http請求的時(shí)候搂擦,仍然返回?cái)?shù)據(jù)了口锭。這是一個(gè)比較詭異的問題胀溺,出現(xiàn)問題的原因可能有以下兩點(diǎn):

  1. Http請求通過緩存直接返回;
  2. Http請求并未通過設(shè)置的代理請求幢痘。
    針對(duì)這個(gè)問題唬格,用了STFW沒有找到什么明確的答案,既然如此颜说,那就直接從源碼著手分析购岗,通過一些筆記的整理,就有了這篇文章门粪。由于精力能力有限藕畔,對(duì)于OkHttp3的分析不會(huì)在細(xì)節(jié)處深入,如果有錯(cuò)誤指出煩請拍磚庄拇,共同進(jìn)步。
    (另源碼分析基于OkHttp3-3.4.1版本)

概述

本文會(huì)先簡單說下OkHttp3的工作流程,然后介紹OkHttp3的一些核心類(如連接池StreamAllocation以及各式各樣的Interceptor)措近,接著從源碼角度分析一次HTTP請求在OkHttp3中所經(jīng)歷的過程溶弟,在不同的Interceptor(攔截器)可以看到一些OkHttp3設(shè)計(jì)的一些巧妙思想,最后對(duì)上述分析做個(gè)簡單的總結(jié)瞭郑。

Okhttp3是Square公司開源的強(qiáng)大高效的Java網(wǎng)絡(luò)請求庫辜御,具有以下特性:

  • 支持Http2/SPDY;
  • 默認(rèn)啟用長連接屈张,使用連接池管理擒权,支持Cache(目前僅支持GET請求的緩存);
  • 路由節(jié)點(diǎn)管理阁谆,提升訪問速度碳抄;
  • 透明的Gzip處理,節(jié)省網(wǎng)絡(luò)流量场绿。
  • 靈活的攔截器剖效,行為類似Java EE的Filter或者函數(shù)編程中的Map。

旅程開始

OkHttp3支持同步和異步兩種請求方式焰盗,異步請求會(huì)經(jīng)過Dispatcher在線程池中執(zhí)行璧尸。同步請求沒有線程池這一個(gè)過程,由于同步請求很簡單熬拒,這里僅分析異步請求方式爷光。OkHttp3一次完整的請求過程是從構(gòu)造一個(gè)Request對(duì)象開始,接著調(diào)用OkHttpClient.newCall()返回一個(gè)RealCall對(duì)象并調(diào)用RealCall.enqueue()方法澎粟,最后會(huì)進(jìn)入Dispacher.enqueue()方法中蛀序,這里會(huì)將RealCall對(duì)象放入線程池中調(diào)度執(zhí)行。

OkHttp3請求流程圖

OkHttp3的核心類

這部分會(huì)簡單介紹一些OkHttp3的核心類捌议,這些核心類共同支持了OkHttp3的一些基礎(chǔ)功能哼拔。粗略的分成了Interceptor(攔截器)、Router(路由)和Stream(流)三部分瓣颅,OkHttpClient類是初始化時(shí)候就配置的倦逐,比較簡單就不說了。

OkHttp3核心類
Interceptor(攔截器)

OkHttp3的Interceptor是Request -> Response請求過程中的一個(gè)"節(jié)點(diǎn)"單位宫补,通過一連串有序的Interceptor攔截器"節(jié)點(diǎn)"組成一條加工鏈檬姥,加工鏈中的任意一個(gè)"節(jié)點(diǎn)"都可以去攔截加工Request和Response。OkHttp默認(rèn)提供了一套完善的Interceptor集合粉怕,當(dāng)然也支持自定義一個(gè)Interceptor來實(shí)現(xiàn)一個(gè)上傳/下載的進(jìn)度更新器或者黑白名單攔截等等個(gè)性化的功能健民。

閱讀OkHttp3的源碼建議從Interceptor開始。

OkHttp默認(rèn)提供了如下Interceptor:

  • RetryAndFollowUpInterceptor:默認(rèn)情況下位于OkHttp3加工鏈的首位贫贝,顧名思義秉犹,具有失敗-重試機(jī)制蛉谜,支持頁面重定向和一些407之類的代理驗(yàn)證等,此外負(fù)責(zé)StreamAllocation對(duì)象的創(chuàng)建(稍后介紹)崇堵。

  • BridgeInterceptor:橋攔截器型诚,配置Request的Headers頭信息:讀取Cookie,默認(rèn)啟用Gzip鸳劳,默認(rèn)加入Keep-Alive長連接狰贯,如果不想讓OkHttp3擅自使用長連接,只需在Request的Header中預(yù)設(shè)Connection字段即可赏廓。

  • CacheInterceptor: 管理OkHttp3的緩存涵紊,目前僅支持GET類型的緩存,使用文件形式的Lru緩存管理策略幔摸,CacheStrategy類負(fù)責(zé)了緩存相關(guān)的策略管理摸柄。

  • ConnectInterceptor:OkHttp3打開一個(gè)Socket連接的地方,OkHttp3相關(guān)的Router路由切換策略也可以從這里開始跟蹤抚太。

  • CallServerInterceptor:處于OkHttp3加工鏈的末尾塘幅,通過HttpStream往Socket中寫入Request報(bào)文信息,并回讀Response報(bào)文信息尿贫。

OkHttp3的攔截器執(zhí)行順序依次是:自定義Interceptors(暫且稱作A) -> RetryAndFollowUpInterceptor -> BridgeInterceptor -> CacheInterceptor -> ConnectInterceptor -> 自定義NetInterceptors(暫且稱作B) -> CallServerInterceptor

  1. B僅在非WebSocket情況下被調(diào)用电媳。
  2. AB的區(qū)別是,A能攔截所有類型的請求庆亡,包括緩存命中的請求匾乓;而B僅攔截非WebSocket的情況下產(chǎn)生真正網(wǎng)絡(luò)訪問的請求。因此在B上做網(wǎng)絡(luò)上傳和下載進(jìn)度的監(jiān)聽器是比較合適的又谋。
Stream(流相關(guān)類)

OkHttp3并沒有直接操作Socket拼缝,而是通過okio庫進(jìn)行了封裝,okio庫的設(shè)計(jì)也是非常贊的彰亥,它的Sink對(duì)應(yīng)輸入流咧七,Source對(duì)應(yīng)輸出流,okio已經(jīng)實(shí)現(xiàn)了與之對(duì)應(yīng)的緩沖相關(guān)的包裝類任斋,采用了Segment切片和循環(huán)鏈表結(jié)構(gòu)實(shí)現(xiàn)緩沖處理继阻,有興趣還是可以看看okio的源碼。

  • StreamAllocation:OkHttp3管理物理連接的對(duì)象废酷,負(fù)責(zé)連接流的創(chuàng)建關(guān)閉等管理工作瘟檩,通過池化物理連接來減少Hand-shake握手過程以提升請求效率,另外StreamAllocation通過持有RouteSelector對(duì)象切換路由澈蟆。關(guān)于路由切換墨辛,這里有個(gè)場景,在Android系統(tǒng)中趴俘,如果你配置了代理睹簇,當(dāng)代理服務(wù)器訪問超時(shí)的時(shí)候奏赘,OkHttp3在進(jìn)行請求重試時(shí)候會(huì)切換到下個(gè)代理或者采用無代理直連形式請求。因此并非設(shè)置了代理带膀,OkHttp3就會(huì)"老實(shí)"的跟著你的規(guī)則走志珍。這也是本文一開始提到的問題產(chǎn)生的原因。

  • HttpStream: 這是一個(gè)抽象類垛叨,其子類實(shí)現(xiàn)了各類網(wǎng)絡(luò)協(xié)議流格式。 HttpStream在OkHttp3中有兩個(gè)實(shí)現(xiàn)類Http1xStream和Http2xStream柜某,Http1xStream實(shí)現(xiàn)了HTTP/1.1協(xié)議流嗽元,Http2xStream則實(shí)現(xiàn)了HTTP/2SPDY協(xié)議流。

  • ConnectionPool:OkHttp連接池喂击,由OkHttpClient持有該對(duì)象剂癌,ConnectionPool持有一個(gè)0核心線程數(shù)的線程池(與Executors.newCachedThreadPool()提供的線程池行為完全一樣)用于清理一些超時(shí)的RealConnection連接對(duì)象,持有一個(gè)Deque對(duì)象緩存OkHttp3的RealConnection連接對(duì)象翰绊。

Router(路由)

OkHttp3要求每個(gè)連接都需要指明一個(gè)Router路由對(duì)象佩谷,當(dāng)然這個(gè)Router路由對(duì)象可以是直連類型的,意即你不使用任何的代理服務(wù)器监嗜。當(dāng)嘗試使用某個(gè)路由請求失敗的時(shí)候谐檀,OkHttp3會(huì)在允許請求重試的情況下通過RouterSelector切換到下個(gè)路由繼續(xù)請求,并將失敗的路由記錄到黑名單中裁奇,這樣在OkHttp3重復(fù)請求一個(gè)目標(biāo)地址的時(shí)候能夠優(yōu)先選擇成功的路由進(jìn)行網(wǎng)絡(luò)請求桐猬。

  • Router:包含代理與Socket地址信息。
  • RouteDatabase:記錄請求失敗的Router路由對(duì)象的"黑名單"刽肠。
  • RouteSelector:負(fù)責(zé)指派Router路由溃肪。持有RouteDatabase對(duì)象。

源碼角度解析OkHttp3請求

分析查看源碼一般抓主干音五,本文也不例外惫撰,我們接下來就直接從代碼來看OkHttp3是如何進(jìn)行一次完整的網(wǎng)絡(luò)請求。限于篇幅躺涝,僅僅分析異步情況下的網(wǎng)絡(luò)請求厨钻,同步方式的網(wǎng)絡(luò)請求更加簡單,核心部分都是一樣的诞挨。另外為了方便閱讀莉撇,部分無關(guān)語句會(huì)被省略。

//所在類 okhttp3.RealCall

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

在調(diào)用client.newCall(request).enqueue(...)方法開始請求之后惶傻,就會(huì)進(jìn)入上面的enqueue()方法棍郎,可以看到最后是調(diào)用了Dispatcher.enqueue(),繼續(xù)跟蹤源碼到如下地方:

//所在類 okhttp3.Dispatcher

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

可以看到OkHttp3使用了一個(gè)線程池來執(zhí)行這個(gè)AsyncCall银室,AsyncCall本質(zhì)上是一個(gè)Runnable對(duì)象涂佃,最后會(huì)調(diào)用AsyncCall.execute()方法励翼。

//所在類 okhttp3.RealCall.AsyncCall

    @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) {
        ...
      } finally {
        client.dispatcher().finished(this);
      }
    }

通過getResponseWithInterceptorChain()獲取Response報(bào)文,繼續(xù)跟蹤下去辜荠。

//所在類 okhttp3.RealCall

private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(
        retryAndFollowUpInterceptor.isForWebSocket()));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

OkHttp3將Interceptor(攔截器)放到一個(gè)集合里汽抚,通過自增遞歸的方式調(diào)用RealInterceptorChain.proceed()方法依次執(zhí)行集合里的每一個(gè)Interceptor(攔截器),這個(gè)Chain的傳遞過程剛開始看有點(diǎn)繞伯病,這里簡單畫個(gè)流程圖方便理解造烁。

RealInterceptor關(guān)鍵源碼如下:

// 所在類 okhttp3.internal.http.RealInterceptorChain

public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpStream httpStream, Connection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpStream = httpStream;
    this.index = index;
    this.request = request;
  }

public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,
      Connection connection) throws IOException {
    
    // 省略部分源碼
    ...
    
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpStream, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpStream != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

RealInterceptorChain的構(gòu)造函數(shù)中的indexinterceptors是匹配的,用來索引接下來需要執(zhí)行的Intercetpor攔截器午笛。
通過調(diào)用interceptor.intercept(next)方法惭蟋,在各個(gè)Intercetpor攔截器里又能通過next參數(shù)繼續(xù)調(diào)用proceed()方法,完成遞歸操作药磺。
接下來我們按照執(zhí)行順序依次看下這些interceptors具體都干了什么告组,從RetryAndFollowUpInterceptor開始。

// 所在類 okhttp3.internal.http.RetryAndFollowUpInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()));

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

      Response response = null;
      boolean releaseConnection = true;
      try {
        response = ((RealInterceptorChain) chain).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(), true, request)) throw e.getLastConnectException();
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        if (!recover(e, false, 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);

      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) {
        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()));
      } else if (streamAllocation.stream() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }
  }

代碼有點(diǎn)長癌佩,不過主要還是做了這幾個(gè)操作木缝,首先StreamAllocation對(duì)象在這里被創(chuàng)建,接著調(diào)用proceed()方法執(zhí)行了一次請求围辙,并拿到一個(gè)Response報(bào)文我碟,在followUpRequest()方法中對(duì)Response報(bào)文進(jìn)行了各種判斷(驗(yàn)證了407,判斷需不需要重定向等)確定是否需要再次請求酌畜,如果需要持續(xù)請求會(huì)在followUpRequest()返回一個(gè)新的Request對(duì)象并重新請求怎囚。followUpRequest()的代碼有點(diǎn)長,可以自行查閱源碼桥胞,這里就不貼了恳守。繼續(xù)看執(zhí)行的下一個(gè)攔截器BridgeInterceptor。

// 所在類 okhttp3.internal.http.BridgeInterceptor 
 
@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) {
      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);
      responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }

可以看出來BridgeInterceptor對(duì)Request和Response報(bào)文加工的具體步驟贩虾,默認(rèn)對(duì)Request報(bào)文增加了gzip頭信息催烘,并在Response報(bào)文中對(duì)gzip進(jìn)行解壓縮處理。另外CookieJar類也是在這里處理的缎罢。接下來就是CacheInterceptor攔截器伊群。

// 所在類 okhttp3.internal.cache.CacheInterceptor

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

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (validate(cacheResponse, networkResponse)) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

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

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (HttpHeaders.hasBody(response)) {
      CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
      response = cacheWritingResponse(cacheRequest, response);
    }

    return response;
  }

CacheInterceptor.intercept()方法也挺長的,首先從cache緩存中獲取一個(gè)匹配的Response報(bào)文并賦給cacheCandidate變量策精,cache是一個(gè)InternalCache對(duì)象舰始,里面持有DiskLruCache這個(gè)對(duì)象,以文件流的形式存儲(chǔ)Response報(bào)文咽袜,采用LRU原則管理這些緩存丸卷;接著使用CacheStrategy.Factory工廠類生成一個(gè)緩存策略類CacheStrategy,通過該類拿到兩個(gè)關(guān)鍵變量networkRequestcacheResponse询刹,這里針對(duì)cacheCandidate谜嫉、networkRequestcacheResponse這三個(gè)變量的賦值情況依次進(jìn)行了以下處理:

  1. cacheCandidate不為空萎坷,cacheResponse為空,說明緩存過期沐兰,將cacheCandidatecache中清除哆档;
  2. networkRequestcacheResponse同時(shí)為空,說明Request要求只使用緩存住闯,而緩存并不存在或者已經(jīng)失效瓜浸,直接返回504的錯(cuò)誤報(bào)文,請求結(jié)束比原;
  3. networkRequest為空斟叼,說明cacheResponse不為空,命中緩存春寿,直接返回cacheResponse報(bào)文;
  4. 未命中緩存忽孽,開啟網(wǎng)絡(luò)請求绑改,繼續(xù)執(zhí)行下一個(gè)Interceptor攔截器。

分析完Request兄一,對(duì)請求回來的Response報(bào)文處理就很簡單了厘线,就是針對(duì)Response報(bào)文情況決定是否使用緩存特性。

OkHttp3的Cache相關(guān)策略可參考RFC 2616, 14.9

當(dāng)緩存未命中時(shí)候OkHttp3就開始執(zhí)行真正的網(wǎng)絡(luò)請求出革,CacheInterceptor的下一個(gè)就是ConnectInterceptor攔截器造壮。

// 所在類 okhttp3.internal.connection.ConnectInterceptor

@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");
    HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

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

ConnectInterceptor做的事情很簡單,先獲取了在RetryAndFollowUpInterceptor中創(chuàng)建的StreamAllocation對(duì)象骂束,接著執(zhí)行streamAllocation.newStream()打開一個(gè)物理連接并返回一個(gè)HttpStream的對(duì)象耳璧,HttpStream在前文提到了是網(wǎng)絡(luò)協(xié)議流(HTTP/1.1HTTP/2SPDY)的具體實(shí)現(xiàn)展箱。這時(shí)候調(diào)用realChain.proceed()方法的時(shí)候旨枯,四個(gè)參數(shù)均不為空,這是集齊了所有的"龍珠"召喚最終的"神龍"CallServerInterceptor攔截器了混驰。

在ConnectInterceptor的下一個(gè)攔截器并非絕對(duì)是CallServerInterceptor攀隔,如果有自定義NetInterceptors則會(huì)被優(yōu)先執(zhí)行,不過絕大部分情況下CallServerInterceptor在最后也是會(huì)被調(diào)用的栖榨。

// 所在類 okhttp3.internal.http.CallServerInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();

    long sentRequestMillis = System.currentTimeMillis();
    httpStream.writeRequestHeaders(request);

    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
      BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
      request.body().writeTo(bufferedRequestBody);
      bufferedRequestBody.close();
    }

    httpStream.finishRequest();

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

    if (!forWebSocket || response.code() != 101) {
      response = response.newBuilder()
          .body(httpStream.openResponseBody(response))
          .build();
    }

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

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

    return response;
  }

CallServerInterceptor攔截器里先調(diào)用httpStream協(xié)議流對(duì)象寫入Request的Header部分炬转,接著寫入Body部分纳令,這樣就完成了Request的請求,從httpStream里回讀Response報(bào)文,并根據(jù)情況讀取Response的Body部分贼涩,當(dāng)Response響應(yīng)報(bào)文的頭信息中Connection字段為close時(shí),將streamAllocation設(shè)置成noNewStreams狀態(tài),標(biāo)識(shí)其當(dāng)前Connection對(duì)象不再被復(fù)用,將在流請求結(jié)束之后被回收掉本冲。

總結(jié)

通過三四天對(duì)OkHttp3源碼的閱讀,佩服框架設(shè)計(jì)的巧妙劫扒,不光在于類的封裝上檬洞,里面對(duì)設(shè)計(jì)模式的實(shí)踐也挺好的。其中Prototype原型模式和Builder建造者模式被廣泛使用沟饥,關(guān)于Interceptor的概念使得全新設(shè)計(jì)一個(gè)私有請求協(xié)議不無可能添怔,而okio對(duì)于流的封裝也是很巧妙的。推薦好好學(xué)習(xí)下贤旷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末广料,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子幼驶,更是在濱河造成了極大的恐慌艾杏,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盅藻,死亡現(xiàn)場離奇詭異购桑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)氏淑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門勃蜘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人假残,你說我怎么就攤上這事缭贡。” “怎么了辉懒?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵阳惹,是天一觀的道長。 經(jīng)常有香客問我耗帕,道長穆端,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任仿便,我火速辦了婚禮体啰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗽仪。我一直安慰自己荒勇,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布闻坚。 她就那樣靜靜地躺著沽翔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仅偎,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天跨蟹,我揣著相機(jī)與錄音,去河邊找鬼橘沥。 笑死窗轩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的座咆。 我是一名探鬼主播痢艺,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼介陶!你這毒婦竟也來了堤舒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤哺呜,失蹤者是張志新(化名)和其女友劉穎舌缤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體某残,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡友驮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驾锰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡走越,死狀恐怖椭豫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旨指,我是刑警寧澤赏酥,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站谆构,受9級(jí)特大地震影響裸扶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搬素,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一呵晨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧熬尺,春花似錦摸屠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春胯舷,著一層夾襖步出監(jiān)牢的瞬間刻蚯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工桑嘶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炊汹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓不翩,卻偏偏與公主長得像兵扬,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子口蝠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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