淺析OkHttp3

OkHttp3

前言

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

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

概述

本文會先簡單說下OkHttp3的工作流程,然后介紹OkHttp3的一些核心類(如連接池StreamAllocation以及各式各樣的Interceptor),接著從源碼角度分析一次HTTP請求在OkHttp3中所經(jīng)歷的過程,在不同的Interceptor(攔截器)可以看到一些OkHttp3設(shè)計的一些巧妙思想,最后對上述分析做個簡單的總結(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支持同步和異步兩種請求方式,異步請求會經(jīng)過Dispatcher在線程池中執(zhí)行。同步請求沒有線程池這一個過程,由于同步請求很簡單,這里僅分析異步請求方式。OkHttp3一次完整的請求過程是從構(gòu)造一個Request對象開始,接著調(diào)用OkHttpClient.newCall()返回一個RealCall對象并調(diào)用RealCall.enqueue()方法,最后會進(jìn)入Dispacher.enqueue()方法中,這里會將RealCall對象放入線程池中調(diào)度執(zhí)行。

OkHttp3請求流程圖

OkHttp3的核心類

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

OkHttp3核心類
Interceptor(攔截器)

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

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

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

  • RetryAndFollowUpInterceptor:默認(rèn)情況下位于OkHttp3加工鏈的首位,顧名思義,具有失敗-重試機(jī)制,支持頁面重定向和一些407之類的代理驗(yàn)證等,此外負(fù)責(zé)StreamAllocation對象的創(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打開一個Socket連接的地方,OkHttp3相關(guān)的Router路由切換策略也可以從這里開始跟蹤。

  • CallServerInterceptor:處于OkHttp3加工鏈的末尾,通過HttpStream往Socket中寫入Request報文信息,并回讀Response報文信息。

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è)計也是非常贊的,它的Sink對應(yīng)輸入流,Source對應(yīng)輸出流,okio已經(jīng)實(shí)現(xiàn)了與之對應(yīng)的緩沖相關(guān)的包裝類,采用了Segment切片和循環(huán)鏈表結(jié)構(gòu)實(shí)現(xiàn)緩沖處理,有興趣還是可以看看okio的源碼。

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

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

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

Router(路由)

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

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

源碼角度解析OkHttp3請求

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

//所在類 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(...)方法開始請求之后,就會進(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使用了一個線程池來執(zhí)行這個AsyncCall,AsyncCall本質(zhì)上是一個Runnable對象,最后會調(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報文,繼續(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(攔截器)放到一個集合里,通過自增遞歸的方式調(diào)用RealInterceptorChain.proceed()方法依次執(zhí)行集合里的每一個Interceptor(攔截器),這個Chain的傳遞過程剛開始看有點(diǎn)繞,這里簡單畫個流程圖方便理解。

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)方法,在各個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)長,不過主要還是做了這幾個操作,首先StreamAllocation對象在這里被創(chuàng)建,接著調(diào)用proceed()方法執(zhí)行了一次請求,并拿到一個Response報文,在followUpRequest()方法中對Response報文進(jìn)行了各種判斷(驗(yàn)證了407,判斷需不需要重定向等)確定是否需要再次請求,如果需要持續(xù)請求會在followUpRequest()返回一個新的Request對象并重新請求。followUpRequest()的代碼有點(diǎn)長,可以自行查閱源碼,這里就不貼了。繼續(xù)看執(zhí)行的下一個攔截器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對Request和Response報文加工的具體步驟,默認(rèn)對Request報文增加了gzip頭信息,并在Response報文中對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緩存中獲取一個匹配的Response報文并賦給cacheCandidate變量,cache是一個InternalCache對象,里面持有DiskLruCache這個對象,以文件流的形式存儲Response報文,采用LRU原則管理這些緩存;接著使用CacheStrategy.Factory工廠類生成一個緩存策略類CacheStrategy,通過該類拿到兩個關(guān)鍵變量networkRequestcacheResponse,這里針對cacheCandidatenetworkRequestcacheResponse這三個變量的賦值情況依次進(jìn)行了以下處理:

  1. cacheCandidate不為空,cacheResponse為空,說明緩存過期,將cacheCandidatecache中清除;
  2. networkRequestcacheResponse同時為空,說明Request要求只使用緩存,而緩存并不存在或者已經(jīng)失效,直接返回504的錯誤報文,請求結(jié)束;
  3. networkRequest為空,說明cacheResponse不為空,命中緩存,直接返回cacheResponse報文;
  4. 未命中緩存,開啟網(wǎng)絡(luò)請求,繼續(xù)執(zhí)行下一個Interceptor攔截器。

分析完Request,對請求回來的Response報文處理就很簡單了,就是針對Response報文情況決定是否使用緩存特性。

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

當(dāng)緩存未命中時候OkHttp3就開始執(zhí)行真正的網(wǎng)絡(luò)請求,CacheInterceptor的下一個就是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對象,接著執(zhí)行streamAllocation.newStream()打開一個物理連接并返回一個HttpStream的對象,HttpStream在前文提到了是網(wǎng)絡(luò)協(xié)議流(HTTP/1.1、HTTP/2SPDY)的具體實(shí)現(xiàn)。這時候調(diào)用realChain.proceed()方法的時候,四個參數(shù)均不為空,這是集齊了所有的"龍珠"召喚最終的"神龍"CallServerInterceptor攔截器了。

在ConnectInterceptor的下一個攔截器并非絕對是CallServerInterceptor,如果有自定義NetInterceptors則會被優(yōu)先執(zhí)行,不過絕大部分情況下CallServerInterceptor在最后也是會被調(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é)議流對象寫入Request的Header部分,接著寫入Body部分,這樣就完成了Request的請求,從httpStream里回讀Response報文,并根據(jù)情況讀取Response的Body部分,當(dāng)Response響應(yīng)報文的頭信息中Connection字段為close時,將streamAllocation設(shè)置成noNewStreams狀態(tài),標(biāo)識其當(dāng)前Connection對象不再被復(fù)用,將在流請求結(jié)束之后被回收掉。

總結(jié)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容