根據Interceptor 分析 OkHttp(一)

yangxi_001發表於2017-06-20

在介紹Interceptor前需要理解幾個概念

Requests

每個HTTP請求都包含一個URL,一個method(比如GET/POST),還有一系列的headers。Requests 還可能包含一個body:一個指定content type的data stream。

Responses

Responses是通過一個code(比如200代表請求成功、404代表資源未找到),headers還有responses自身的body對Requests的反饋。

Rewriting Requests

當你通過OkHttp傳送一個HTTP請求的時候,你就在告訴OkHttp“通過這些headers幫我獲取到這個URL下的資源”。為了正確和有效,OkHttp需要在傳送這個Requests前重寫這個Requests。

OkHttp會在原始Requests的基礎上新增一些原始Requests缺失的headers,包括Content-Length, Transfer-Encoding, User-Agent, Host, Connection, 以及 Content-Type。如果header裡缺失Accept-Encoding,OkHttp會主動新增一個可接收採用gzip壓縮response的header,requestBuilder.header("Accept-Encoding", "gzip");。另外,如果你已經有cookies,OkHttp會使用這些cookies新增一個cookie header。

Rewriting Responses

如果OkHttp在Rewriting Requests中新增了header("Accept-Encoding", "gzip"),並且response有body,OkHttp會將response headers中的Content-Encoding 和 Content-Length去掉,並對response進行解壓縮(decompressed)。所以這部分邏輯是:

  1. 開發者沒有新增Accept-Encoding時,自動新增Accept-Encoding: gzip
  2. 自動新增的request,response支援自動解壓
  3. 手動新增不負責解壓縮
  4. 自動解壓時移除Content-Length,所以上層Java程式碼想要contentLength時為-1
  5. 自動解壓時移除 Content-Encoding
  6. 自動解壓時,如果是分塊傳輸編碼,Transfer-Encoding: chunked不受影響。

Follow-up Requests

如果你請求的URL資源已經被移除,伺服器會返回一個類似於302的狀態碼來宣告新的URL地址。OkHttp會follow這個重定向來獲取最終的response。

不過OkHttp支援follow這個重定向的次數是有限制的,最多是20次,如果超過這個次數仍無法獲取到最終的response,會丟擲一個 ProtocolException("Too many follow-up requests: " + followUpCount);

Retrying Requests

有時候connections會失敗:也許是因為一個被放進連線池中的 connection 斷開連線了,也可能是伺服器無法連線。OkHttp會嘗試通過其他可用的route來重試請求。

Calls

對http的請求封裝,屬於程式設計師能夠接觸的上層高階程式碼。Calls可以通過兩種方式去執行:

  • Synchronous: 阻塞執行緒,直到response返回。
  • Asynchronous: 把request放入一個任意thread中的佇列,並通過callback在其他執行緒做回撥。

Calls可以在任意thread中被cancel,只要這個request還沒有請求完成,request都可以cancel掉。要注意的是,如果request已經被cancel,再做修改request body或者讀取response的操作會丟擲一個IOException。

Dispatch

OkHttp使用Dispatcher作為任務的派發器,有下面這幾個關鍵屬性

  1. private int maxRequests = 64;
  2. private int maxRequestsPerHost = 5;
  3. /** Executes calls. Created lazily. */
  4. private ExecutorService executorService;
  5. /** Ready async calls in the order they'll be run. */
  6. private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  7. /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  8. private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  9. /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  10. private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

如果Call(對http的請求封裝)是通過同步的方式發起(Synchronous),則Dispatcher直接將call放入runningSyncCalls佇列中,依序進行呼叫。

如果是通過非同步的方式發起(Asynchronous),則Dispatcher需要判斷是否立馬將該call放入執行佇列:

  1. synchronized void enqueue(AsyncCall call) {
  2. if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
  3. //新增正在執行的請求
  4. runningAsyncCalls.add(call);
  5. //執行緒池執行請求
  6. executorService().execute(call);
  7. } else {
  8. //新增到快取佇列排隊等待
  9. readyAsyncCalls.add(call);
  10. }
  11. }

其中maxRequests 是最大併發請求數,預設是64個;maxRequestsPerHost 是每個主機最大請求數,預設是5個,這裡所說的主機是指Dispatcher會根據call的host來進行歸類,相同host的call不能有超過5個線上程池中同時執行,否則要放入等待佇列,儘管此時執行緒池中的併發請求數沒有超過預設的64個。這個設計有點類似於服務端的SLB(Server Load Balance),目的是不要讓某個主機負載過高,平衡不同host的請求呼叫。

Connections

通過OkHttp請求一個URL的時候,大致過程是這樣的:

1.OkHttp用這個URL以及配置的OkHttpClient 生成Address。這個Address宣告瞭如何連線伺服器。 
2.OkHttp會嘗試根據這個Addressconnection pool中獲取一個connection。 
3.如果在連線池中未找到connection,會通過RouteSelector選擇一個Route生成一個新的RealConnection,並將這個新生成的connection放入connection pool。 
4.通過這個connection發起HTTP request和獲取response。

如果一個connection出現了問題,OkHttp會選擇一個其他的route進行重試。一旦已經獲取到response,這個connection會被放回connection pool以備複用。經過一段時間的休眠後,connection會被從connection pool中移除掉。

相關文章