OkHttp解析系列
OkHttp解析(一)從用法看清原理
OkHttp解析(二)網絡連接
OkHttp解析(三)關于Okio
認識使用
一系列是對OkHttp的源碼解析,就從大家使用方法入手
常用方法
OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS) .cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies)
{
//...
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
//...
return null;
}
}).build();
Request request = new Request.Builder().url("www.baidu.com").build();
Call call = okHttpClient.newCall(request);
//異步
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
//同步
try {
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
可以看到常用的用法,是創建OKHttpClient對象,構建請求Request,請求調用Call,而Call里面對應有同步異步兩種方法,同步下則要在線程中去進行。這就是大家常用的方法,我們先來看看OkHttpClient
OkHttpClient
我們先看注釋說明
/**
* Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their responses.
OkHttpClient算是執行調用請求Call的工廠,這個工廠將會被用來發送Http請求和讀取他們的返回
*
* ...
*
* <p>OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a client for each request wastes resources on idle pools.
這里強調OkHttp的使用最好創建一個單例OkHttpClient實例,并且重復使用。這是因為每一個Client都有自己的一個連接池connection pool和線程池thread pools。重用這些連接池和線程池可以減少延遲和節約內存。
*
...
*
* <p>Or use {@code new OkHttpClient.Builder()} to create a shared instance with custom settings:
* <pre> {@code
* 創建實例說明,可以看到是通過Builder模式創建的
* // The singleton HTTP client.
* public final OkHttpClient client = new OkHttpClient.Builder()
* .addInterceptor(new HttpLoggingInterceptor())
* .cache(new Cache(cacheDir, cacheSize))
* .build();
* }</pre>
*
* <h3>Customize your client with newBuilder()</h3>
*
* 可以調用newBuilder方法來定制自己的client,調用后創建的client會保存上次的連接池和線程池以及之前一些配置
* <p>You can customize a shared OkHttpClient instance with {@link #newBuilder()}. This builds a client that shares the same connection pool, thread pools, and configuration. Use the builder methods to configure the derived client for a specific purpose.
*
* <p>This example shows a call with a short 500 millisecond timeout: <pre> {@code
*
* OkHttpClient eagerClient = client.newBuilder()
* .readTimeout(500, TimeUnit.MILLISECONDS)
* .build();
* Response response = eagerClient.newCall(request).execute();
* }</pre>
*
* ...
*/
OkHttpClient算是執行調用請求Call的工廠,這個工廠將會被用來發送Http請求和讀取他們的返回這里強調OkHttp的使用最好創建一個單例OkHttpClient實例,并且重復使用。這是因為每一個Client都有自己的一個連接池connection pool和線程池thread pools。重用這些連接池和線程池可以減少延遲和節約內存。
我們看下里面的源碼,由于使用Builder,看OkHttpClient的也就相當于看Builder
public static final class Builder {
Dispatcher dispatcher; //調度器,里面包含了線程池和三個隊列(readyAsyncCalls:保存等待執行的異步請求
Proxy proxy; //代理類,默認有三種代理模式DIRECT(直連),HTTP(http代理),SOCKS(socks代理),這三種模式,折騰過科學上網的或多或少都了解一點吧。
List<Protocol> protocols; //協議集合,協議類,用來表示使用的協議版本,比如`http/1.0,`http/1.1,`spdy/3.1,`h2等
List<ConnectionSpec> connectionSpecs; //連接規范,用于配置Socket連接層。對于HTTPS,還能配置安全傳輸層協議(TLS)版本和密碼套件
final List<Interceptor> interceptors = new ArrayList<>(); //攔截器,用來監聽請求
final List<Interceptor> networkInterceptors = new ArrayList<>();
ProxySelector proxySelector; //代理選擇類,默認不使用代理,即使用直連方式,當然,我們可以自定義配置,以指定URI使用某種代理,類似代理軟件的PAC功能。
CookieJar cookieJar; //Cookie的保存獲取
Cache cache; //緩存類,內部使用了DiskLruCache來進行管理緩存,匹配緩存的機制不僅僅是根據url,而且會根據請求方法和請求頭來驗證是否可以響應緩存。此外,僅支持GET請求的緩存。
InternalCache internalCache; //內置緩存
SocketFactory socketFactory; //Socket的抽象創建工廠,通過`createSocket來創建Socket
。
SSLSocketFactory sslSocketFactory; //安全套接層工廠,HTTPS相關,用于創建SSLSocket。一般配置HTTPS證書信任問題都需要從這里著手。對于不受信任的證書一般會提示javax.net.ssl.SSLHandshakeException異常。
CertificateChainCleaner certificateChainCleaner; //證書鏈清潔器,HTTPS相關,用于從[Java]的TLS API構建的原始數組中統計有效的證書鏈,然后清除跟TLS握手不相關的證書,提取可信任的證書以便可以受益于證書鎖機制。
HostnameVerifier hostnameVerifier; //主機名驗證器,與HTTPS中的SSL相關,當握手時如果URL的主機名不是可識別的主機,就會要求進行主機名驗證
CertificatePinner certificatePinner; // 證書鎖,HTTPS相關,用于約束哪些證書可以被信任,可以防止一些已知或未知的中間證書機構帶來的攻擊行為。如果所有證書都不被信任將拋出SSLPeerUnverifiedException異常。
Authenticator proxyAuthenticator; //身份認證器,當連接提示未授權時,可以通過重新設置請求頭來響應一個新的Request。狀態碼401表示遠程服務器請求授權,407表示代理服務器請求授權。該認證器在需要時會被RetryAndFollowUpInterceptor觸發。
Authenticator authenticator;
ConnectionPool connectionPool; //連接池
Dns dns;
boolean followSslRedirects; //是否遵循SSL重定向
boolean followRedirects; //是否重定向
boolean retryOnConnectionFailure; //失敗是否重新連接
int connectTimeout; //連接超時
int readTimeout; //讀取超時
int writeTimeout; //寫入超時
...
}
注釋中說明的很清楚了
可以看到在OkHttpClient這里就設置了這么多的字段,常用的讀寫時間,延遲請求,緩存都在這里設置了。
看下構造器的賦值
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS; //默認支持的協議
connectionSpecs = DEFAULT_CONNECTION_SPECS; //默認的連接規范
proxySelector = ProxySelector.getDefault(); //默認的代理選擇器,直連
cookieJar = CookieJar.NO_COOKIES; //默認不進行管理Cookie
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE; //主機驗證
certificatePinner = CertificatePinner.DEFAULT; //證書鎖,默認不開啟
proxyAuthenticator = Authenticator.NONE; //默認不進行授權
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool(); //連接池
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
//超時時間
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
}
Dispatcher
它是一個異步請求執行政策,當我們用OkHttpClient.newCall(request)進行execute/enenqueue時,實際是將請求Call放到了Dispatcher中,okhttp使用Dispatcher進行線程分發,它有兩種方法,一個是普通的同步單線程;另一種是使用了隊列進行并發任務的分發(Dispatch)與回調。另外,在Dispatcher中每一個請求都是使用 ExecutorService 來執行的。
public final class Dispatcher {
private int maxRequests = 64; //最大并發數為64,同時請求
private int maxRequestsPerHost = 5; //每個主機的最大請求數為5
private Runnable idleCallback; //閑置接口
/** Executes calls. Created lazily. */
private ExecutorService executorService; //線程池
//緩存好的異步調用,都是放在隊列里保存
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//運行中的異步調用,都是放在隊列里保存
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//運行中的同步調用,都是放在隊列里保存
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
...
}
前面說到,當我們用OkHttpClient.newCall(request)進行execute/enqueue時,實際是將請求Call放到了Dispatcher中。
我們先看回之前的用法
Call call = okHttpClient.newCall(request);
//異步
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
//同步
try {
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
通過調用okHttpClient.newCall將請求Request構造成Call,進行發起請求。
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
newCall這里則是創建了一個RealCall對象
Call
首先對于Call,大家比較熟悉,它是一個接口,定義了各種Http連接請求的方法
public interface Call {
Request request();
...
Response execute() throws IOException;
void enqueue(Callback responseCallback);
void cancel();
boolean isExecuted();
boolean isCanceled();
interface Factory {
Call newCall(Request request);
}
}
可以通過request()方法獲取自己的請求體,調用enqueue發起異步請求,調用execute發起同步請求
RealCall
RealCall則是Call的實現類
final class RealCall implements Call {
private final OkHttpClient client;
private final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
// Guarded by this.
private boolean executed;
/** The application's original request unadulterated by redirects or auth headers. */
Request originalRequest;
protected RealCall(OkHttpClient client, Request originalRequest) {
this.client = client;
this.originalRequest = originalRequest;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client);
}
@Override public Request request() {
return originalRequest;
}
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
...
}
RealCall中實現了Execute和enqueue等方法。而在RealCall的execute和enqueue方法中都調用到了dispatcher.enqueue/execute。
我們先看下同步方法RealCall.execute
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
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
),在異步執行流程中它會有更多的參與。
這里同步請求,只是把當前請求添加到隊列而已
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
真正發出網絡請求,解析返回結果的,還是 getResponseWithInterceptorChain
,這個下面說,最后再調用了dispatch.finish方法
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
在finish方法中,會把當前請求從running隊列中移除,然后把緩存隊列的請求添加到running隊列。
接下來看下異步請求
RealCall.enqueue
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
先判斷當前Call是否在執行,再調用dispatch.enqueue方法
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
方法中,這里先有個判斷,如果當前運行的異步請求隊列長度小于最大請求數,也就是64,并且主機的請求數小于每個主機的請求數也就是5,則把當前請求添加到 運行隊列,接著交給線程池ExecutorService處理,否則則放置到readAsyncCall進行緩存,等待執行。
可以看到同步與異步一點區別就是,異步的執行交給了線程池去操作。
我們看下OkHttp里面的線程池ExecutorService
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
這里則是通過ThreadPoolExecutor來創建線程池
參數說明如下:
int corePoolSize: 最小并發線程數,這里并發同時包括空閑與活動的線程,如果是0的話,空閑一段時間后所有線程將全部被銷毀。
int maximumPoolSize: 最大線程數,當任務進來時可以擴充的線程最大值,當大于了這個值就會根據丟棄處理機制來處理
long keepAliveTime: 當線程數大于
corePoolSize
時,多余的空閑線程的最大存活時間,類似于HTTP中的Keep-aliveTimeUnit unit: 時間單位,一般用秒
BlockingQueue<Runnable> workQueue: 工作隊列,先進先出,可以看出并不像Picasso那樣設置優先隊列。
ThreadFactory threadFactory: 單個線程的工廠,可以打Log,設置
Daemon
(即當JVM退出時,線程自動結束)等
可以看出,在Okhttp中,構建了一個閥值為[0, Integer.MAX_VALUE]的線程池,它不保留任何最小線程數,隨時創建更多的線程數,當線程空閑時只能活60秒,它使用了一個不存儲元素的阻塞工作隊列,一個叫做"OkHttp Dispatcher"的線程工廠。
也就是說,在實際運行中,當收到10個并發請求時,線程池會創建十個線程,當工作完成后,線程池會在60s后相繼關閉所有線程。
添加到線程池后就交給ThreadPoolExecutor去調用,最終則是調用到我們的請求AsyncCall的execute方法。回看上面的代碼,異步請求中,我們傳遞了個Callback接口進來,而在RealCall的enqueue方法中,Callback回調接口被封裝到AsyncCall中,而AsyncCall繼承與NamedRunnable,而NamaedRunnable則實現了Runnable方法。
AsyncCall
AsyncCall繼承于NamedRunnable,而NamaedRunnable則實現了Runnable方法
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
private AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl().toString());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@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) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
可以看到在它的構造器中,Callback就是我們設置的創建的,帶有onFail和onResponse方法。
而線程池中最終調用到的則是我們的Runnable。
這里通過
Response response = getResponseWithInterceptorChain();
方法來進行連接訪問,這里跟同步請求一樣。最后根據返回值調用callback.onFailure/onResponse
我們關鍵還是看OkHttp如何連接返回的,我們看下這個方法
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);
}
可以看到這里全都是關于Interceptor攔截器的使用
Interceptor
/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*/
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
Connection connection();
}
}
Interceptor
是 OkHttp 最核心的一個東西,不要誤以為它只負責攔截請求進行一些額外的處理(例如 cookie),實際上它把實際的網絡請求、緩存、透明壓縮等功能都統一了起來,每一個功能都只是一個 Interceptor
,它們再連接成一個 Interceptor.Chain
,環環相扣,最終圓滿完成一次網絡請求。
從 getResponseWithInterceptorChain
函數我們可以看到,Interceptor.Chain
的分布依次是:

在配置
OkHttpClient
時設置的interceptors
;負責失敗重試以及重定向的
RetryAndFollowUpInterceptor
;負責把用戶構造的請求轉換為發送到服務器的請求、把服務器返回的響應轉換為用戶友好的響應的
BridgeInterceptor
;負責讀取緩存直接返回、更新緩存的
CacheInterceptor
;負責和服務器建立連接的
ConnectInterceptor
;配置
OkHttpClient
時設置的networkInterceptors
;負責向服務器發送請求數據、從服務器讀取響應數據的
CallServerInterceptor
。
[責任鏈模式]在這個 Interceptor
鏈條中得到了很好的實踐
在這里,位置決定了功能,最后一個 Interceptor 一定是負責和服務器實際通訊的,重定向、緩存等一定是在實際通訊之前的。
對于把 Request
變成 Response
這件事來說,每個 Interceptor
都可能完成這件事,所以我們循著鏈條讓每個 Interceptor
自行決定能否完成任務以及怎么完成任務(自力更生或者交給下一個Interceptor
)。這樣一來,完成網絡請求這件事就徹底從 RealCall
類中剝離了出來,簡化了各自的責任和邏輯。
講解其他攔截器前,先認識幾個類
HttpStream
public interface HttpStream {
//超時漸漸
int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
//返回一個output stream(如果RequestBody可以轉為流)
Sink createRequestBody(Request request, long contentLength);
//寫入請求頭
void writeRequestHeaders(Request request) throws IOException;
// Flush the request
void finishRequest() throws IOException;
//讀取請求頭
Response.Builder readResponseHeaders() throws IOException;
//返回ResponseBody
ResponseBody openResponseBody(Response response) throws IOException;
void cancel();
}
可以看到HttpStream是一個接口,里面提供了很多類似的流操作,比如Sink。
而HttpStream對應的實現類有Http1xStream、Http2xStream。分別對應HTTP/1.1、HTTP/2和SPDY協議。我們可以大約知道,通過writeRequestHeaders
開始寫入請求頭到服務器,createRequestBody
用于獲取寫入流來寫入請求體。readResponseHeaders
用于讀取響應頭,openResponseBody
用于打開一個響應體。關于相應實現的源碼這里就不分析了,比較簡單,無非就是讀寫操作。
StreamAllocation
流分配器,該類用于協調連接、流和請求三者之間的關系。通過調用newStream
可以獲取一個HttpStream實現
public HttpStream newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
//獲取設置的超時時間
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpStream resultStream;
if (resultConnection.framedConnection != null) {
resultStream = new Http2xStream(client, this, resultConnection.framedConnection);
} else {
resultConnection.socket().setSoTimeout(readTimeout);
resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
resultStream = new Http1xStream(
client, this, resultConnection.source, resultConnection.sink);
}
synchronized (connectionPool) {
stream = resultStream;
return resultStream;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
可以看到在newStream這里,通過RealConnection建立Socket連接,接著獲取連接對應的流。
而在RealConnection的connectSocket方法中
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
rawSocket.setSoTimeout(readTimeout);
try {
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
throw new ConnectException("Failed to connect to " + route.socketAddress());
}
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
可以看到Socket和Okio的連接使用
重試與重定向攔截器 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;
}
}
RetryAndFollowUpInterceptor在intercept()中首先從client取得connection pool,用所請求的URL創建Address對象,并以此創建StreamAllocation對象。
Address描述某一個特定的服務器地址。StreamAllocation對象則用于分配一個到特定的服務器地址的流HttpStream,這個HttpStream可能是從connection pool中取得的之前沒有釋放的連接,也可能是重新分配的。RetryAndFollowUpInterceptor這里算是為后面的操作準備執行條件StreamAllocation。
隨后利用Interceptor鏈中后面的Interceptor來獲取網絡響應。并檢查是否為重定向響應。若不是就將響應返回,若是則做進一步處理。
對于重定向的響應,RetryAndFollowUpInterceptor.intercept()會利用響應的信息創建一個新的請求。并檢查新請求的服務器地址與老地址是否相同,若不相同則會根據新的地址創建Address對象及StreamAllocation對象。
RetryAndFollowUpInterceptor對重定向的響應也不會無休止的處理下去,它處理的最多的重定向級數為20次,超過20次時,它會拋異常出來。
RetryAndFollowUpInterceptor通過followUpRequest()從響應的信息中提取出重定向的信息,接著通過sameConnection來判斷是否需要重定向連接
RetryAndFollowUpInterceptor主要做了
創建StreamAllocation,以此傳入到后續的Interceptor中
處理重定向的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");
}
}
...
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中,主要用于用于完善請求頭,比如Content-Type、Content-Length、Host、Connection、Accept-Encoding、User-Agent等等,這些請求頭不用用戶一一設置,如果用戶沒有設置該庫會檢查并自動完善。此外,這里會進行加載和回調cookie。
緩存攔截器 CacheInterceptor
@Override public Response intercept(Chain chain) throws IOException {
//根據Request獲取緩存中的Response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根據請求頭獲取用戶指定的緩存策略,并根據緩存策略來獲取networkRequest,cacheResponse。
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
...
//如果不需要網絡則直接返回從緩存中讀取的Response
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
}
...
// 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;
}
緩存攔截器,首先根據Request中獲取緩存的Response,然后根據用于設置的緩存策略來進一步判斷緩存的Response是否可用以及是否發送網絡請求。如果從網絡中讀取,此時再次根據緩存策略來決定是否緩存響應。
這塊代碼比較多,但也很直觀,主要涉及 HTTP 協議緩存細節的實現,而具體的緩存邏輯 OkHttp 內置封裝了一個 Cache 類,它利用 DiskLruCache,用磁盤上的有限大小空間進行緩存,按照 LRU 算法進行緩存淘汰,這里也不再展開。
我們可以在構造 OkHttpClient 時設置 Cache 對象,在其構造函數中我們可以指定目錄和緩存大小:
public Cache(File directory, long maxSize);
而如果我們對 OkHttp 內置的 Cache 類不滿意,我們可以自行實現 InternalCache 內置緩存接口,在構造OkHttpClient 時進行設置,這樣就可以使用我們自定義的緩存策略了。
建立連接 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獲取到StreamAllocation,而StreamAllocation的創建則是在 RetryAndFollowUpInterceptor重定向攔截器這里。
接著調用到了streamAllocation.newStream,前面介紹到,在newStream方法中會通過RealConnection建立與服務器之間的連接
實際上建立連接就是創建了一個 HttpCodec 對象,它將在后面的步驟中被使用,那它又是何方神圣呢?它是對 HTTP 協議操作的抽象,有兩個實現:Http1Codec 和 Http2Codec,顧名思義,它們分別對應 HTTP/1.1 和 HTTP/2 版本的實現。
在 Http1Codec 中,它利用 Okio 對 Socket 的讀寫操作進行封裝,Okio 以后有機會再進行分析,現在讓我們對它們保持一個簡單地認識:它對 java.io 和 java.nio 進行了封裝,讓我們更便捷高效的進行 IO 操作。
發送和接收數據 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;
}
我們抓住主干部分:
- 向服務器發送 request header;
- 如果有 request body,就向服務器發送;
- 讀取 response header,先構造一個 Response 對象;
- 如果有 response body,就在 3 的基礎上加上 body 構造一個新的 Response 對象;
CallServerInterceptor首先將http請求頭部發給服務器,如果http請求有body的話,會再將body發送給服務器,繼而通過httpStream.finishRequest()結束http請求的發送。
請求完成之后,我們就可以從 Response 對象中獲取到響應數據了,包括 HTTP status code,status message,response header,response body 等。這里 body 部分最為特殊,因為服務器返回的數據可能非常大,所以必須通過數據流的方式來進行訪問(當然也提供了諸如 string() 和 bytes() 這樣的方法將流內的數據一次性讀取完畢),而響應中其他部分則可以隨意獲取。
響應 body 被封裝到 ResponseBody 類中,該類主要有兩點需要注意:
- 每個 body 只能被消費一次,多次消費會拋出異常;
- body 必須被關閉,否則會發生資源泄漏;
小結
RetryAndFollowUpInterceptor : 創建StreamAllocation對象,處理http的重定向,出錯重試。對后續Interceptor的執行的影響:修改request及StreamAllocation。
BridgeInterceptor:補全缺失的一些http header,Cookie設置。對后續Interceptor的執行的影響:修改request。
CacheInterceptor:處理http緩存。對后續Interceptor的執行的影響:若緩存中有所需請求的響應,則后續Interceptor不再執行。
ConnectInterceptor:借助于前面分配的StreamAllocation對象建立與服務器之間的連接(具體建立是在newStream方法中),并選定交互所用的協議是HTTP 1.1還是HTTP 2。對后續Interceptor的執行的影響:創建了httpStream和connection。
CallServerInterceptor:處理IO,與服務器進行數據交換。對后續Interceptor的執行的影響:為Interceptor鏈中的最后一個Interceptor,沒有后續Interceptor。
在文章最后我們再來回顧一下完整的流程圖:
