讀okhttp源碼(一)

okhttp對于android開發來說是很常用的庫,最近有時間,就準備仔細的讀一下其源碼

準備

okhttp源碼不是android項目,我也是搜了一下,最終采用intelliJ IEDA導入項目,畢竟android studio也是基于它的開源版本二次開發的,用起來沒有什么學習成本,導入過程網上都有說明,就不贅述了。

開始

一開始看到okhttp源碼,其中包含了很多module,我們還是從okhttp這個最根本的module看起。根據官網的示例代碼說明,最簡單的使用如下:

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

這段代碼涉及到OkHttpClient,Request,Call,Response,那我們一個個看,首先是OkHttpClient。

OkHttpClient實現Call.Factory和WebSocket.Factory接口,以實現其基本功能,構造用于發送請求和解析響應的Call對象。在OkHttpClient類中,基本就是定義各種字段,以支持網絡通信,比如ConnectionPool,DNS等,以及一些配置字段,比如readTimeOut,writeTimeOut等,然后就是我們熟知的Builder模式的實現,用以構造OkHttpClient。

Request類則是抽象了http request,其也是用builder進行構造,builder模式在源碼的很多地方都有用到。Request包含method,url,headers,body,tag,cacheControl字段,其中url是HttpUrl對象,其也是使用builder模式構造的,并且根據類注釋,為了解決java類庫提供的URL和URI兩個類的使用限制,而重新構造了這個模型。headers是對http header的抽象的模型,也需要使用builder模式構造,此類底層使用了一個數組來存儲name-value對,采用name,value,name,value這種方式。body類型是RequestBody,這是一個抽象類,包含抽象方法用于定義ContentType,ContentLength,以及關鍵的writeTo方法,writeTo方法接收BufferedSink類(這個類定義在okio中,這也是okhttp少數幾個依賴庫之一),實現將字節數組寫到流中。tag字段是一個map映射表,主要是用于攔截器或者監聽器上,可以使用這些tag做日志生成等。最后是cacheControl,這其實最后也是放到header中,用于緩存控制,也是使用builder模式構造,主要是設置各種緩存控制指令配置。
  
Call是okhttp中定義的一個接口,代表一個已經準備好執行的request請求,并且只能執行一次,如果需要再次執行,可以使用定義的clone方法創建一個相同的Call,另外Call中還定義了同步執行方法execute和異步執行方法enqueue。異步執行的本質,就是講這個Call加入隊列中,由調度器選擇合適的時間執行。
  
Reponse則是代表http reponse,并且此類實現Closeable接口,以使用java7引入的try with resource語法糖,在close方法中,就是檢查body是否為null,不是的話就調用body.close(為null的情況包括從cacheResponse,networkResponse,priorResponse返回的response),那我們就接著看一下resonseBody。
  
ResponseBody是一次使用的數據流的抽象表示,提供方法獲取響應體的字節流,字符流,字節數組(全部讀取到內存中),字符串(全部讀取到內存中),此類也實現了Closeable,有多種關閉的方法。
  
了解了這些之后,我們看看newCall方法的實現,如何通過一個request構造一個Call。OkHttpClient中newCall的實現,是調用RealCall.newRealCall方法,并將okhttpclient實例和request作為參數傳遞過去,看來RealCall才是真正包含執行邏輯的地方,讓我們順著看下去。在newRealCall方法中,先是創建一個RealCall實例,它是Call接口的實現類,然后通過okhttpclient的eventlistenerFactory通過這個RealCall實例構造一個EventListener,實現監控網絡請求生命周期內各個節點事件。
  
創建完Call之后,示例代碼調用execute執行調用,獲取response,根據上一步的分析,execute方法實際是RealCall上調用的,讓我們來看一下。

  @Override public Response execute() throws IOException {
    if (originalRequest.body instanceof DuplexRequestBody) {
      DuplexRequestBody duplexRequestBody = (DuplexRequestBody) originalRequest.body;
      return duplexRequestBody.awaitExecute();
    }
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      e = timeoutExit(e);
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

首先判斷requestBody是不是DuplexRequestBody,根據這個類的注釋,這個類實現一個請求體和響應體雙工模式,只支持http/2,我們這里不做分析。然后檢查executed變量防止多次執行。captureCallStackTrace方法,獲取調用response.body().close時的堆棧信息,再設置在retryAndFollowUpInterceptor中,這里這個retryAndFollowUpInterceptor是一個提供從錯誤中恢復及必要時重定向功能的類實例,這里就涉及到interceptor攔截器了,我們后面再說。后面timeout是okio里的類,應該是提供timeout功能,后面對照著okio源碼可以再看看,然后eventListener執行callStart方法,提供監控,然后使用okhttpclient中的dispatcher調度器執行executed方法,跟蹤到其實現,就是將這個call加入到dispatcher內部維護的一個runningSyncCalls,那我們再來看
getResponseWithInterceptorChain方法:

    Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

這里我們看到,okhttp攔截器,是通過責任鏈模式執行的,查看這里添加的interceptor,retryAndFollowUpInterceptor之前已經說明,BridgeInterceptor類注釋里提到,它的作用,是把用戶的request轉化為網絡request,然后繼續網絡請求,最后將網絡response轉化為用戶response,CacheInterceptor類的注釋則說明此類對來自緩存的請求提供支持,并將響應寫到緩存中,ConnectInterceptor則是提供連接到目標服務器的功能,最后CallServerInterceptor則是真正發送一個網絡請求給服務器的地方。 這些Interceptor類都實現了Interceptor接口,在這個接口內部定義了Interceptor.Chain這個攔截器鏈的抽象接口。在攔截器鏈的具體實現類RealInterceptorChain的proceed方法實現中,關鍵執行代碼的邏輯是先重新構造一個RealInterceptorChain,跟當前這個的區別只是其中一個index參數,這個參數控制RealInterceptorChain內部的interceptors列表,獲取第幾個interceptor,執行其intercept方法,并將新構造的RealInterceptorChain作為參數傳遞給intercept方法,然后就可以在intercept方法內部再次調用RealInterceptorChain的proceed方法,以執行下一個interceptor的intercept方法。
  
我們先來看一下RetryAndFollowUpInterceptor的intercept方法實現,先聲明一些變量,其中StreamAllocation是我們新遇到的類,根據類注釋,它是負責協調Connection,Stream,Call。聲明完之后是一個while死循環,在內部,會執行chain.proceed,然后在異常處理中,都會調用recover方法,判斷是否可以恢復,能的話就往下走,否則就拋出異常,當然在finally塊中,會調用streamAllocation的streamFailed方法和release方法以釋放資源。streamFail方法內部,先是以線程池對象同步代碼塊,在其中,根據異常類型和http連接屬性(是否http/2),判斷connection上是否需要創建新連接,還有根據條件判斷是否調用routeSelector的connectFail方法,標記失敗的route(內部會維護一個LinkedHashSet,失敗的route會加入其中,使得使用route時可以避免使用這些已標記的route),同步塊之后會調用方法關閉socket,這個socket是通過RealConnection類獲取的。
  
這塊我們先看到這,回到RetryAndFollowUpInterceptor的intercept實現,在處理完chain.proceed方法之后,后面還有一個關鍵方法followUpRequest,應該對應著類名的FollowUp處理重定向及其相關邏輯。這個方法根據response code執行不同邏輯:如果是401或407這類認證不通過,則調用authenticator或proxyAuthenticator的authenticate方法,返回一個可以通過認證的request;如果是307,308等表示需要重定向的code,則,則根據header里面的Location重新構造httpUrl,并根據條件判斷是否要添加requestBody和是否刪除認證頭信息,最后構造一個新的request并返回;如果是408(很少見),表明直接使用原request重試,當然代碼中還會判斷okhttpclient中配置是否重試標志位,否的話不重試,還有請求體是不能重試的(StreamedRequestBody),還有如果已經重試過一次并且依然返回408,則不再重試,最后我們會檢查response header里的Retry-After,如果這個延遲重試的值大于0,自然現在不需要重試,這樣這段邏輯結束;然后如果是503,其邏輯是如果已經重試過并且依然返回503,則不再重試,然后如果Retry-After頭的值是0,則返回原request重試,否則不重試;最后就是如果code都不是這些,則也不重試。這樣followUpRequest方法就結束了,在這個方法之后,后面就是釋放資源,以及如果不是相同的connection,則構造一個新的streamAllcation,使得下一次循環會使用這個新對象。
  
RetryAndFollowUpInterceptor的intercept的方法大致看到這里,后面我們看到CallServerInterceptor,會再次和這里產生關聯。下面我們看一下BridgeInterceptor實現,其接收一個cookieJar作為構造方法參數(cookieJar是一個提供cookie策略和持久化方法的接口,默認配置是no cookie,需要自己實現),然后我們看看它的intercept方法。方法實現可以分為三個部分,調用chain.proceed之前的構造request部分,調用chain.proceed之后的構造response部分,以及chain.proceed方法。構造request部分,主要就是在初始的request上再補充header配置,比如Content-Type,Content-Length,Host,Collection,Cookie(由參數cookie構造出value),User-Agent以及Accept-Encoding(配置gzip,并且在之后的構造response時,還負責解壓縮數據),這樣完成之后,就可以用這個新的request調用chain.proceed,后面真正處理請求的時候,header已經是完整的了。然后我們看看reponse構造的過程,首先是調用保存響應里的cookie方法,然后通過response構造一個builder,用之前構造的request賦值給builder力的request,然后就是有沒有response body以及是否啟用了gzip,如果條件滿足,那么就要使用GzipSource這個responsebody構造一個RealResponseBody并賦值給builder的body,這樣就完成了response的處理,可以用builder構造一個新的response并返回。
  
然后我們看一下CacheInterceptor,接收一個InternalCache的參數,這是一個接口,作為內部緩存的定義,我們要擴展的話,需要使用Okhttp.Cache,是個類,其中有內部類實現InternalCache。在intercept方法實現中,首先會遇到CacheStrategy類,構造其實例并獲取其內部域networkRequest和cacheResponse,如果這兩者都為null,說明緩存未命中,通過網絡請求被禁止,則返回新的reponse,code是504以說明這種情況,如果networkRequest為null,cacheReponse不為null,說明緩存命中,并且不需要網絡請求,那么只要用這個cacheReponse構造一個新的response返回即可,如果都不是,則執行chain.proceed,這樣,就會走到鏈上后面真正執行網絡請求的interceptor,在通過這個方法獲取到network reponse之后,如果cacheResponse不為null以及http code為304,則通過networkReponse和cacheReponse生成一個新的response,用于更新緩存并返回這個reponse,否則就判斷是否能創建緩存,可以的話,創建一個新的response,用其創建緩存,并返回這個response。這樣CacheInterceptor的intercept方法大致講到這里。

順帶著我們看一下Cache類,之前已經提到有實現了InternalCache的內部類,其方法實現就是調用Cache中同名方法。在大致瀏覽了一下實現之后,我們可以看到,Cache內有一個DiskLruCache實例,用其將request url進行處理之后作為key存儲緩存,緩存的數據分為body和除了body以外的其他信息(meta_data)。

然后我們看一下ConnectInterceptor,它的intercept方法實現就比較簡單,得到chain的streamAllocation域后(在RetryAndFollowUpInterceptor的intercept方法里內部構造然后進行了賦值),調用streamAllocation.newStream得到HttpCodec實例,調用streamAllocation.connection得到RealConnection實例之后,就調用chain.proceed(request, streamAllocation, httpCodec, connection)方法(轉換chain為具體實現類型然后調用此方法,非接口方法),方法到此結束。

這里又遇到了streamAllocation,我們看一下newStream方法實現,其調用findHealthyConnection去尋找可用的流,此方法調用findConnection,根據這個方法的注釋,優先選擇已經存在的collection(streamAllcation類的域Connection),如果這個collection不能用來創建新的流,那么就在collection pool中尋找,這里有兩次,一次不使用Route,如果沒找到,則使用Route再找一次,如果還是找不到,那么就創建一個新的RealConnection,使用這個RealConnection進行tcp和tls握手之后,將這個connection加入到connection pool中,最后返回這個connection。findCollection返回之后,還要再進行一些檢查以最終確定這個connection是healthy,如果不是,則循環再次執行上述步驟。然后就到了newCodec方法,比較簡單,就是根據RealConnection的內部域http2Connection是否為null,是的話,那么就構造Http1Codec實例,否則構造Http2Codec實例并返回,這兩個實例都是HttpCodec這個接口的實現,這個接口根據注釋,是為了編碼http request以及解碼http response。newStream返回值是HttpCodec,到此返回構造的實例,方法就結束了。

然后看一下streamAllocation.connection實現,由于上一步已經生成了RealConnection,直接返回這個實例,方法就結束了。另外注意一下,connection是底層socket的封裝,然后stream是基于connection,最后call則是基于stream,這些關系由StreamAllocation協調。

最后我們看一下CallServerInterceptor的intercept方法實現。主要分為下面幾個步驟:
1)httpCodec.writeRequestHeaders(request),寫入請求頭
2)如果需要寫入請求體并且其不為null,則通過下面的代碼:
···
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

      request.body().writeTo(bufferedRequestBody);
      bufferedRequestBody.close();

···
這樣就完成了寫入請求體的工作
3)httpCodec.finishRequest(),將數據推入到底層socket中,然后就可以開始讀取response了。
4)httpCodec.readResponseHeaders(false),讀取response的header部分
5)通過上一步得到responseBuilder,然后設置handshake,sentRequestMillis等域,構造出response
6)調用httpCodec.openResponseBody(response)讀取responseBody并設置

基本流程就像這樣,這里httpCodec用到很多次,之前已經提到過,httpCodec是個借口,有兩個實現:Http1Codec和Http2Codec,分別對應http1.1和http/2,上面在httpCodec上調用的方法,在這兩個類中實現都是不同的。我這里主要看了一下Http1Codec的實現,內部用到了BufferedSource和BufferedSink,這兩個是okio里的類,BufferedSource包裝Source,附加buffer功能,同樣,BufferedSink包裝Sink,附加buffer功能,而Source是okio實現的替代InputStream的提供讀取數據的接口類,Sink是okio實現的替代OutputStream的提供寫入數據的接口類,由此可知,底層的讀寫數據,都是借助okio來實現,

看到這里,我們總算了解了RealCall的getResponseWithInterceptorChain方法實現,得到了其返回值response,execute方法就可以直接返回它,然后我們看到在finally塊中調用了client.dispatcher().finished(this)方法,那么我們來看一下它的實現。

首先在用Dispatcher自身synchronized的塊,執行runningSyncCalls.remove(call),移除之前執行dispatcher().executed方法時加入到隊列的call,然后會執行promoteAndExecute方法。按照promoteAndExecute方法的注釋,它執行把readyAsyncCalls隊列中的call提升到runningAsyncCalls,換句話說就是,遍歷readyAsyncCalls,只要runningAsyncCalls的size沒有達到maxRequests以及共享這個call的host的所有call的數量不超過maxRequestsPerHost,那么我們就會把這個call加入到runningAsyncCalls,并且在內部線程池中run,同時這個方法返回runningAsyncCalls和runningSyncCalls兩者總大小是否為0,如果為0,為0就說明dispatcher閑置,此時就會調用idleCallback.run()(如果這個idleCallback不為null)。

然后我們可以看看RealCall的enqueue(Callback responseCallback)異步執行方法。代碼很短,核心代碼是client.dispatcher().enqueue(new AsyncCall(responseCallback)),那我們接著看看Dispatcher的enqueue方法,首先是synchronized的調用readyAsyncCalls.add(call),然后調用promoteAndExecute(),這個方法剛才看過了,我們這里看一下AsyncCall,它繼承NamedRunnable,主要看一下其execute方法,核心還是調用getResponseWithInterceptorChain方法,得到結果后根據條件調用responseCallback對應成功或失敗的回調方法,以及在finally中調用client.dispatcher().finished(this)。

這樣我們也就知道異步執行是怎么實現的。接著我們回過頭來,再來看看RealConnection。connect方法,之前已經提到負責tcp和tls握手連接,其實現中有幾個關鍵方法,connectSocket方法根據條件創建底層的socket實例,滿足條件則調用Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout)方法建立連接,然后使用這個底層socket實例,我們稱之為rawSocket,創建BufferedSink和BufferedSource實例,注意這里用到了Platform類,其有很多子類,分別對應不同平臺,比如android,jdk9,等待。然后還有establishProtocol方法,根據條件把不同的值賦給socket和protocol域,這個socket不一定是之前的rawSocket,根據注釋說明,如果不需要tls,那么直接把通過connectSocket創建的rawSocket賦值給socket,否則會調用connnectTls方法創建一個SSLSocket(其中包裝了rawSocket),然后賦值給socket,這里還涉及到tls握手,建立session,驗證證書等操作。然后如果是http2,我們會創建一個Http2Connection實例,將剛得到的socket實例,BufferedSink,BufferedSource等賦值給Http2Connection內部域,這個Http2Connection是給Http2Codec提供支持的,同樣的還有Http2Stream類。

這樣就大致了解了RealConnection,其實connect方法里還有涉及到http proxy處理的邏輯,http2相關類的邏輯我們后面有時間再看。

我們再回過頭來看看Http1Codec類,其中有幾個Sink接口的內部實現類,分別是FixedLengthSink(固定大小),ChunkedSink(分塊處理),以及幾個Source的內部實現類,分別是抽象基類AbstractSource(作為后面幾個類的基類,提供read和endOfInput方法的實現),FixedLengthSource(固定大小),ChunkedSource(分塊處理),UnknownLengthSource(未知大小,通過底層流結束來判斷結束)。

這篇就先寫到這里,下一篇我就從okhttp post請求談起。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。