根據Interceptor 分析 OkHttp(一)
在介紹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)。所以這部分邏輯是:
- 開發者沒有新增Accept-Encoding時,自動新增Accept-Encoding: gzip
- 自動新增的request,response支援自動解壓
- 手動新增不負責解壓縮
- 自動解壓時移除Content-Length,所以上層Java程式碼想要contentLength時為-1
- 自動解壓時移除 Content-Encoding
- 自動解壓時,如果是分塊傳輸編碼,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作為任務的派發器,有下面這幾個關鍵屬性
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
/** Executes calls. Created lazily. */
private ExecutorService executorService;
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** 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<>();
如果Call(對http的請求封裝)是通過同步的方式發起(Synchronous),則Dispatcher直接將call放入runningSyncCalls佇列中,依序進行呼叫。
如果是通過非同步的方式發起(Asynchronous),則Dispatcher需要判斷是否立馬將該call放入執行佇列:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//新增正在執行的請求
runningAsyncCalls.add(call);
//執行緒池執行請求
executorService().execute(call);
} else {
//新增到快取佇列排隊等待
readyAsyncCalls.add(call);
}
}
其中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會嘗試根據這個Address
從connection 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中移除掉。