【Android】OkHttp原始碼分析
Android為我們提供了兩種HTTP互動的方式:HttpURLConnection 和 Apache HttpClient,雖然兩者都支援HTTPS,流的上傳和下載,配置超時,IPv6和連線池,已足夠滿足我們各種HTTP請求的需求。但更高效的使用HTTP 可以讓您的應用執行更快、更節省流量。而OkHttp庫就是為此而生。在開始分析OkHttp之前我們先了解一下 HttpURLConnection 和 HttpClient 兩者之間關係,以及它們和 OkHttp 之間的關係。
HttpClient vs HttpURLConnection
在Android API Level 9(Android 2.2)之前只能使用HttpClient
HttpClient
是Apache用於傳送http請求的客戶端,其提供了強大的API支援,而且基本沒有什麼bug,但是由於其太過複雜,Android團隊在保持向後相容的情況下,很難對DefaultHttpClient
進行增強。為此,Android團隊從Android API Level 9開始自己實現了一個傳送http請求的客戶端類 —— HttpURLConnection
。
相比於DefaultHttpClient
,HttpURLConnection
比較輕量級,雖然功能沒有DefaultHttpClient
那麼強大,但是能夠滿足大部分的需求,所以Android推薦使用HttpURLConnection
DefaultHttpClient
,並不強制使用HttpURLConnection
。
但從Android API Level 23(Android 6.0)開始,不能再在Android中使用HttpClient
,強制使用HttpURLConnection
。參考官網:Android 6.0 Changes - Google Developer
Android 6.0 版移除了對 Apache HTTP client的支援。如果您的應用使用該客戶端,並以 Android 2.3(API level 9)或更高版本為目標平臺,請改用
HttpURLConnection
類。此 API 效率更高,因為它可以通過透明壓縮和響應快取減少網路使用,並可最大限度降低耗電量。要繼續使用 Apache HTTP API,您必須先在build.gradle
檔案中宣告以下編譯時依賴項:
android { useLibrary 'org.apache.http.legacy' }
二者對比
HttpURLConnection
在Android2.2之前:HttpURLConnection 有個重大 Bug:呼叫 close() 函式會影響連線池,導致連線複用失效;所以Android2.2之前不建議使用HttpURLConnection。在Android2.2之後:HttpURLConnection預設開啟了 gzip 壓縮&提高了HTTPS 的效能
HttpClient
優點:相比於HttpURLConnection,更加高效簡潔
缺點:結構過於複雜;維護成本高在5.0版本後被Android官方棄用
儘管Google在大部分安卓版本中推薦使用HttpURLConnection,但是這個類相比HttpClient實在是太難用,太弱爆了。OkHttp是一個相對成熟的解決方案,我們跟蹤這篇文章:Android HttpURLConnection原始碼分析。就會發現Android4.4之後的HttpURLConnection其實是基於OkHttp
實現的。所以我們更有理由相信OkHttp的強大。
二者與網路請求庫之間的關係
網路請求框架本質上是一個將網路請求的相關方法( HttpClient或HttpURLConnection)封裝好的類庫,並實現另開執行緒進行請求和處理資料,從而實現整個網路請求模組的功能。具體的關係可看下圖:
而OkHttp
是基於http協議封裝的一套請求客戶端,雖然它也可以開執行緒,但根本上它更偏向真正的請求,跟HttpClient
,HttpUrlConnection
的職責是一樣的。
OkHttp簡介
OkHttp 庫的設計和實現的首要目標是高效。這也是選擇 OkHttp 的重要理由之一。OkHttp 提供了對最新的 HTTP 協議版本 HTTP/2 和 SPDY 的支援,這使得對同一個主機發出的所有請求都可以共享相同的Socket連線。如果 HTTP/2 和 SPDY 不可用,OkHttp 會使用連線池來複用連線以提高效率。OkHttp 提供了對 GZIP 的預設支援來降低傳輸內容的大小。OkHttp 也提供了對 HTTP 響應的快取機制,可以避免不必要的網路請求。當網路出現問題時,OkHttp 會自動重試一個主機的多個 IP 地址。
OkHttp是一個高效的HTTP庫:
- 支援 HTTP/2和SPDY ,共享同一個Socket來處理同一個伺服器的所有請求
- 如果 HTTP/2和SPDY 不可用,則通過連線池來減少請求延時
- 無縫的支援GZIP來減少資料流量
- 快取響應資料來減少重複的網路請求
如何使用
OkHttp的使用是比較簡單的,整體步驟是:
- 初始化 OkHttpClient
- 初始化一個 Request
- 由 OkHttpClient 和 Request 生成一個 Call
- Call 呼叫 enqueue (非同步)或者 execute 方法(同步)
同步Get請求
這是OkHttp 最基本的 HTTP 請求,注意別放到UI執行緒執行。
public class SyncGet {
public static void main(String[] args) throws IOException {
//1. 初始化OkHttpClient
OkHttpClient client = new OkHttpClient();
//2. 初始化一個Request
Request request = new Request.Builder()
.url("http://www.baidu.com")
.header("User-Agent", "My super agent")
.addHeader("Accept", "text/html")
.build();
//3. 由OkHttpClient和Request生成一個Call
//4. call呼叫enqueue或者execute
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("伺服器端錯誤: " + response);
}
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
}
非同步Get請求
public class AsyncGet {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
//Call 呼叫 enqueue 方法
client.newCall(request).enqueue(new Callback() {
public void onFailure(Request request, IOException e) {
e.printStackTrace();
}
public void onResponse(Response response) throws IOException {
if (!response.isSuccessful()) {
throw new IOException("伺服器端錯誤: " + response);
}
System.out.println(response.body().string());
}
});
}
}
Post請求
HTTP POST 和 PUT 請求可以包含要提交的內容。只需要在建立 Request
物件時,通過 post()
和 put()
方法來指定要提交的內容即可。下面的程式碼通過 RequestBody
的 create()
方法來建立媒體型別為application/ json 的內容並提交。
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
//post請求
public String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
具體的使用方法可以參考IBM的這篇文章:OkHttp:Java平臺上的新一代HTTP客戶端 ,下面我們轉入原始碼的分析。
原始碼分析
我們從建立 OkHttpClient
物件開始:
OkHttpClient client = new OkHttpClient();
OkHttpClient
看看其建構函式:
public OkHttpClient() {
this(new Builder());
}
原來是方便我們使用,提供了一個“快捷操作”,全部使用了預設的配置。OkHttpClient.Builder
類成員很多,後面我們再慢慢分析,這裡先暫時略過:
//OkHttpClient.java類中
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher; // 分發器
this.proxy = builder.proxy; // 代理
this.protocols = builder.protocols; // 協議
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors); // 攔截器
this.networkInterceptors = Util.immutableList(builder.networkInterceptors); // 網路攔截器
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector; // 代理選擇
this.cookieJar = builder.cookieJar; // cookie
this.cache = builder.cache; // 快取
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
boolean isTLS = false;
for (ConnectionSpec spec : connectionSpecs) {
isTLS = isTLS || spec.isTls();
}
if (builder.sslSocketFactory != null || !isTLS) {
this.sslSocketFactory = builder.sslSocketFactory;
this.certificateChainCleaner = builder.certificateChainCleaner;
} else {
X509TrustManager trustManager = systemDefaultTrustManager();
this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
}
this.hostnameVerifier = builder.hostnameVerifier;
this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
certificateChainCleaner);
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool; // 連線複用池
this.dns = builder.dns;
this.followSslRedirects = builder.followSslRedirects;
this.followRedirects = builder.followRedirects;
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
this.connectTimeout = builder.connectTimeout; // 連線超時時間
this.readTimeout = builder.readTimeout; // 讀取超時時間
this.writeTimeout = builder.writeTimeout; // 寫入超時時間
this.pingInterval = builder.pingInterval;
}
看到這,如果你還不明白的話,也沒關係,在OkHttp
中只是設定用的的各個東西。
真正的流程要從裡面的newCall()
方法中說起,因為我們使用OkHttp發起 HTTP 請求的方式一般如下:
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
//發起同步請求
Response response = client.newCall(request).execute();
當通過建造者模式建立了Request
之後(這個沒什麼好說),緊接著就通過client.newCall(request).execute()
來獲得Response
。這句程式碼就開啟了整個GET請求的流程:
那我們現在就來看看它是如何通過newCall()
建立 Call 的:
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
Call
在這裡再多說一下關於Call這個類的作用,OkHttp 使用呼叫(Call)來對傳送 HTTP 請求和獲取響應的過程進行抽象。在Call中持有一個HttpEngine。每一個不同的Call都有自己獨立的HttpEngine。在HttpEngine中主要是各種鏈路和地址的選擇,還有一個Transport比較重要。Call
介面定義如下:
public interface Call extends Cloneable {
Request request();
Response execute() throws IOException;
void enqueue(Callback responseCallback);
void cancel();
boolean isExecuted();
boolean isCanceled();
Call clone();
interface Factory {
Call newCall(Request request);
}
}
OkHttpClient
實現了 Call.Factory
,負責根據請求建立新的 Call
。
CallFactory
負責建立 HTTP 請求,HTTP 請求被抽象為了okhttp3.Call
類,它表示一個已經準備好,可以隨時執行的 HTTP 請求
RealCall
RealCall是Call
介面的實現,我們繼續接著上面看RealCall的newRealCall()
方法:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
其實就是通過建構函式new了一個RealCall
物件,建構函式如下,不用看很細,略過。我們重點看看 RealCall#execute
:
//RealCall類中
@Override
public Response execute() throws IOException {
synchronized (this) {
//1. 每個Call只能被執行一次。如果該 call 已經被執行過了,就設定 executed 為 true
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//2. 加入 runningSyncCalls 佇列中
client.dispatcher().executed(this);
//3. 得到響應 result
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
//4. 從 runningSyncCalls 佇列中移除
client.dispatcher().finished(this);
}
}
這裡我們做了 4 件事:
- 檢查這個 call 是否已經被執行了,每個 call 只能被執行一次,如果想要一個完全一樣的 call,可以利用
call#clone
方法進行克隆。 - 利用
client.dispatcher().executed(this)
來進行實際執行,dispatcher
是剛才看到的OkHttpClient.Builder
的成員之一,它的文件說自己是非同步 HTTP 請求的執行策略,現在看來,同步請求它也有摻和。 - 呼叫
getResponseWithInterceptorChain()
函式獲取 HTTP 返回結果,從函式名可以看出,這一步還會進行一系列“攔截”操作。 - 最後還要通知
dispatcher
自己已經執行完畢。
dispatcher
這裡我們不過度關注,在同步執行的流程中,涉及到 dispatcher 的內容只不過是告知它我們的執行狀態,比如開始執行了(呼叫 executed
),比如執行完畢了(呼叫 finished
),在非同步執行流程中它會有更多的參與。
真正發出網路請求,解析返回結果的,還是 getResponseWithInterceptorChain
。我們可以看到這方法是直接返回 Response
物件的,所以,在這個方法中一定做了很多很多的事情。
//RealCall類中
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());
// 利用 chain 來鏈式呼叫攔截器,最後的返回結果就是 Response 物件
return chain.proceed(originalRequest);
}
在 OkHttp 開發者之一介紹 OkHttp 的文章裡面,作者講到:
the whole thing is just a stack of built-in interceptors.
可見 Interceptor
是 OkHttp 最核心的一個東西,不要誤以為它只負責攔截請求進行一些額外的處理(例如 cookie),實際上它把實際的網路請求、快取、透明壓縮等功能都統一了起來,每一個功能都只是一個 Interceptor
,它們再連線成一個 Interceptor.Chain
,環環相扣,最終圓滿完成一次網路請求。
從 getResponseWithInterceptorChain
函式我們可以看到,Interceptor.Chain
的分佈依次是:
client.interceptors()
,首先加入interceptors
的是使用者自定義的攔截器,比如修改請求頭的攔截器等;- RetryAndFollowUpInterceptor :是用來重試和重定向的攔截器,在下面我們會講到;
- BridgeInterceptor:是用來將使用者友好的請求轉化為向伺服器的請求,之後又把伺服器的響應轉化為對使用者友好的響應;
- CacheInterceptor:是快取攔截器,若存在快取並且可用就直接返回該快取,否則會向伺服器請求;
- ConnectInterceptor:用來建立連線的攔截器;
client.networkInterceptors()
加入使用者自定義的networkInterceptors
;- CallServerInterceptor:是真正向伺服器發出請求且得到響應的攔截器;
在這裡,位置決定了功能,最後一個 Interceptor 一定是負責和伺服器實際通訊的,重定向、快取等一定是在實際通訊之前的。
責任鏈模式在這個 Interceptor
鏈條中得到了很好的實踐。
責任鏈模式在面向物件程式設計裡是一種軟體設計模式,它包含了一些命令物件和一系列的處理物件。每一個處理物件決定它能處理哪些命令物件,它也知道如何將它不能處理的命令物件傳遞給該鏈中的下一個處理物件。該模式還描述了往該處理鏈的末尾新增新的處理物件的方法。
另外參考文章:Android設計模式之責任鏈模式中相關的分析:
Android中關於責任鏈模式比較明顯的體現就是在事件分發過程中對事件的投遞,其實嚴格來說,事件投遞的模式並不是嚴格的責任鏈模式,但是其是責任鏈模式的一種變種體現。
對於把 Request
變成 Response
這件事來說,每個 Interceptor
都可能完成這件事,所以我們循著鏈條讓每個 Interceptor
自行決定能否完成任務以及怎麼完成任務(自力更生或者交給下一個 Interceptor
)。這樣一來,完成網路請求這件事就徹底從 RealCall
類中剝離了出來,簡化了各自的責任和邏輯。
最後在聚合了這些攔截器之後,利用 RealInterceptorChain
來鏈式呼叫這些攔截器。
RealInterceptorChain
RealInterceptorChain
可以說是真正把這些攔截器串起來的一個角色。一個個攔截器就像一顆顆珠子,而 RealInterceptorChain
就是把這些珠子串連起來的那根繩子。
進入 RealInterceptorChain
,主要是 proceed()
這個方法:
@Override
public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// 得到下一次對應的 RealInterceptorChain
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
// 當前的 interceptor
Interceptor interceptor = interceptors.get(index);
// 進行攔截處理,並且在 interceptor 鏈式呼叫 next 的 proceed 方法
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != 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");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
在程式碼中是一次次鏈式呼叫攔截器,
- 下一個攔截器對應的
RealIterceptorChain
物件,這個物件會傳遞給當前的攔截器 - 得到當前的攔截器:interceptors是存放攔截器的ArryList
- 呼叫當前攔截器的
intercept()
方法,並將下一個攔截器的RealIterceptorChain
物件傳遞下去
示意圖如下:
下面根據上面的責任鏈我們逐個分析一下對應的攔截器Interceptor
。
攔截器Interceptor
攔截器是 OkHttp 提供的對 HTTP 請求和響應進行統一處理的強大機制。攔截器在實現和使用上類似於 Servlet 規範中的過濾器。多個攔截器可以連結起來,形成一個鏈條。攔截器會按照在鏈條上的順序依次執行。 攔截器在執行時,可以先對請求的 Request
物件進行修改;再得到響應的 Response
物件之後,可以進行修改之後再返回。
攔截器Interceptor
介面定義如下:
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
除了在client中自定義設定的interceptor,第一個呼叫的就是RetryAndFollowUpInterceptor
。
RetryAndFollowUpInterceptor(重試和重定向攔截器)
我們著重看看它的intercept()
方法:
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
// 如果取消,就釋放資源
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
// 呼叫下一個攔截器
response = realChain.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(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
// 伺服器連線失敗,請求可能已被髮送
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, 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();
}
// 如果不需要重定向,那麼 followUp 為空,會根據響應碼判斷
Request followUp = followUpRequest(response);
// 釋放資源,返回 response
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
// 關閉 response 的 body
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 比較response 和 followUp 是否為同一個連線
// 若為重定向就銷燬舊連線,建立新連線
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
// 將重定向操作得到的新請求設定給 request
request = followUp;
priorResponse = response;
}
}
總體來說,RetryAndFollowUpInterceptor
是用來失敗重試以及重定向的攔截器。
BridgeInterceptor(請求構造攔截器)
@Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
// 將使用者友好的 request 構造為傳送給伺服器的 request
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));
}
// Keep Alive
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.
// 使用 gzip 壓縮
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 設定 cookie
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
// UA: User-Agent
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
// 構造完後,將 request 交給下一個攔截器去處理。最後又得到服務端響應 networkResponse
Response networkResponse = chain.proceed(requestBuilder.build());
// 儲存 networkResponse 的 cookie
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
// 將 networkResponse 轉換為對使用者友好的 response
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// 如果 networkResponse 使用 gzip 並且有響應體的話,給使用者友好的 response 設定響應體
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);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
在 BridgeInterceptor
這一步,
- 先把使用者友好的請求進行重新構造,變成了向伺服器傳送的請求。
- 之後呼叫
chain.proceed(requestBuilder.build())
進行下一個攔截器的處理。 - 等到後面的攔截器都處理完畢,得到響應。再把
networkResponse
轉化成對使用者友好的response
。
CacheInterceptor(快取攔截器)
@Override
public Response intercept(Chain chain) throws IOException {
// 嘗試從快取中查詢 request 對應的 response
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(Util.EMPTY_RESPONSE)
.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
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.
// 如果我們同時有快取和 networkResponse ,根據情況使用
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.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 (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
CacheInterceptor
做的事情就是根據請求拿到快取,若沒有快取或者快取失效,就進入網路請求階段,否則會返回快取。
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");
// 建立 httpCodec (抽象類),分別對應著 http1.1 和 http 2
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
// 交給下一個攔截器,得到返回的 Response
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
實際上建立連線就是呼叫了 streamAllocation.newStream
建立了一個 HttpCodec
物件,它將在後面的步驟中被使用,那它又是何方神聖呢?它是對 HTTP 協議操作的抽象,有兩個實現:Http1Codec
和 Http2Codec
,顧名思義,它們分別對應 HTTP/1.1 和 HTTP/2 版本的實現。
在 Http1Codec
中,它利用 Okio 對 Socket
的讀寫操作進行封裝,Okio 以後有機會再進行分析,現在讓我們對它們保持一個簡單地認識:它對 java.io
和 java.nio
進行了封裝,讓我們更便捷高效的進行 IO 操作。
而建立 HttpCodec
物件的過程涉及到 StreamAllocation
、RealConnection
,程式碼較長,這裡就不展開,這個過程概括來說,就是找到一個可用的 RealConnection
,再利用 RealConnection
的輸入輸出(BufferedSource
和 BufferedSink
)建立 HttpCodec
物件,供後續步驟使用。
我們來看下 streamAllocation.newStream()
的程式碼:
// 位於StreamAllocation類
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
// 在連線池中找到一個可用的連線,然後創建出 HttpCodec 物件
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
findHealthyConnection()
:先在連線池中找到可用的連線resultConnection
。
這一步會使用Platform.get().connectSocket()
建立TCP連線,完成三次握手。resultConnection.newCodec()
:再結合sink
和source
創建出HttpCodec
的物件。HttpCodec
負責對HTTP請求和響應進行編解碼。註釋如下:
/** Encodes HTTP requests and decodes HTTP responses. */
public interface HttpCodec { ... }
CallServerInterceptor
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
realChain.eventListener().requestHeadersStart(realChain.call());
// 整理請求頭並寫入
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
// 檢查是否為有 body 的請求方法
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
// 寫入請求體 request body
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
// 得到響應頭
if (responseBuilder ==