重點
本文打算從三點來剖析OkHttp3:
- 網(wǎng)絡(luò)請求的整理流程-會使用
- 攔截器模式-易擴(kuò)展
- 緩存和連接池-高性能
整體流程
我們在閱讀某一類源碼之前欧穴,首先要學(xué)會怎么使用骤肛,其次才是去了解內(nèi)部的實現(xiàn)原理,實現(xiàn)方案上有什么技巧也殖。
okhttp的一張流程圖:
官方用例
- get使用方式
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
- post使用的方式
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
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();
}
請求過程
從上面的用例和流程圖可以總結(jié)出OkHttp的請求過程為:
- 創(chuàng)建OkHttpClient
- 創(chuàng)建Request
- 同步或者異步發(fā)出請求,并經(jīng)過Interceptors處理
- 得到Response
然后一步一步來分析
1. 創(chuàng)建OkHttpClient
通過new OkHttpClient()
來創(chuàng)建一個OkHttpClient對象务热,當(dāng)然這個是簡單的寫法忆嗜,通常情況下,我們需要設(shè)置緩存策略崎岂、超時時長捆毫、攔截器等等〕甯剩看下源碼:
public OkHttpClient() {
this(new Builder());
}
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;
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;
}
第一眼看到這么多參數(shù)绩卤,是不是被嚇到了,源碼比較長江醇,但是它本身沒有特定的功能濒憋,Builder
作為一個載體,記錄下各種初始化的信息陶夜。例如凛驮,緩存地址、超時的時長条辟、自定義攔截器等等黔夭。
這里使用了Builder設(shè)計模式,更加易讀羽嫡,關(guān)于Builder設(shè)計模式本姥,可以看看這篇文章
2. 創(chuàng)建Request
Request類是一個純粹的載體,封裝了一個請求的Header杭棵、Method婚惫、url和body。這里源碼就不貼出來了。
3. 發(fā)起請求
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
response.body().string();
OkHttpClient實現(xiàn)了Call.Factory辰妙,負(fù)責(zé)創(chuàng)建新的Call鹰祸,Call是OkHttp抽象出一個滿足請求的模型。
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override
public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
上面是OkHttpClient創(chuàng)建Call的過程密浑,可以看到蛙婴,RealCall是真正請求網(wǎng)絡(luò)的類。
- 同步請求網(wǎng)絡(luò):
execute()
@Override
public Response execute() throws IOException {
synchronized (this) {
// 從這里可以看到尔破,一個call只能執(zhí)行一次街图,如果需要多次執(zhí)行,可以使用clone()方法懒构。
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
- 異步請求網(wǎng)絡(luò):
enqueue(callBack)
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
上面的同步和異步的網(wǎng)絡(luò)請求做了四件事情:
- 判斷是否執(zhí)行過餐济,每個call請求只能請求一次,如果需要重復(fù)執(zhí)行可以使用
clone()
方法 - 獲取分發(fā)器
dispatcher
胆剧,執(zhí)行execute()
或者enqueue(new AsyncCall(callback))
- 調(diào)用
getResponseWithInterceptorChain()
方法絮姆,獲取網(wǎng)絡(luò)請求的結(jié)果,異步請求都在AsyncCall中執(zhí)行了秩霍。 - 最后通知
dispatcher
執(zhí)行完畢篙悯。
上面的四個步驟中涉及到了Dispatcher和AsyncCall兩個類,這里做個簡單的介紹:
- Dispatcher
Disaptcher是一個分發(fā)器铃绒,它持有線程池鸽照、異步任務(wù)隊列和同步任務(wù)隊列,會依照不同的策略執(zhí)行颠悬。 - AsyncCall
矮燎,AsyncCall是RealCall的內(nèi)部類,持有RealCall的引用赔癌,實質(zhì)上是一個Runnable诞外,對RealCall做了一層封裝。
攔截器
OkHttpClient中真正發(fā)出網(wǎng)絡(luò)請求灾票,解析返回結(jié)果的步驟是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);
return chain.proceed(originalRequest);
}
Interceptor
是OkHttp最核心的一個東西浅乔,采用了責(zé)任鏈的設(shè)計模式,不要誤以為它只是負(fù)責(zé)攔截請求進(jìn)行一些額外的處理(例如cookie)铝条,實際上它把時機(jī)的網(wǎng)絡(luò)請求靖苇、緩存、透明壓縮等功能都統(tǒng)一了起來班缰,每一個功能只是一個Interceptor贤壁,它們再連接成為Interceptor.Chain,環(huán)環(huán)相扣埠忘,最終圓滿完成一次網(wǎng)絡(luò)請求脾拆。interceptor的順序也很重要馒索,比如負(fù)責(zé)網(wǎng)絡(luò)請求的Interceptor必須放在最后,負(fù)責(zé)緩存策略的Inteceptor需要放到前面名船。
/**
* A concrete interceptor chain that carries the entire interceptor chain: all application
* interceptors, the OkHttp core, all network interceptors, and finally the network caller.
*/
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private int calls;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
}
@Override public Connection connection() {
return connection;
}
public StreamAllocation streamAllocation() {
return streamAllocation;
}
public HttpCodec httpStream() {
return httpCodec;
}
@Override public Request request() {
return request;
}
@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");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
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");
}
return response;
}
}
RealInterceptorChain的主要作用是傳遞參數(shù)和一個一個執(zhí)行攔截器绰上,并且設(shè)定一些條件,例如渠驼,每個攔截器只能調(diào)用一次chain.proceed這個方法蜈块。
OkHttp攔截器分析
先列出之前在getResponseWithInterceptorChain方法中添加的各Interceptor,概括一下它們分別負(fù)責(zé)什么功能:
- client.interceptors()用戶自定義的Interceptor迷扇,能攔截到所有的請求
- RetryAndFollowUpInterceptor負(fù)責(zé)失敗重連和重定向相關(guān)
- BridgeInterceptor負(fù)責(zé)配置請求的頭信息百揭,比如Keep-Alive、gzip蜓席、Cookie等可以優(yōu)化請求
- CacheInterceptor負(fù)責(zé)緩存管理器一,使用DiskLruCache做本地緩存,CacheStrategy決定緩存策略
- ConnectInterceptor開始與目標(biāo)服務(wù)器建立連接厨内,獲得RealConnection
- client.networkInterceptors()用戶自定義的Interceptor祈秕,僅在生產(chǎn)網(wǎng)絡(luò)請求時生效
- CallServerInterceptor向服務(wù)器發(fā)出一次網(wǎng)絡(luò)請求的地方。
RetryAndFollowUpInterceptor
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
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(), 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();
}
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) {
streamAllocation.release();
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()), callStackTrace);
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
- streamAllocation對象是在這個Interceptor中創(chuàng)建的
- 在followUpRequest方法中判斷了是否需要重定向以及是否需要重連踢步,需要重連時會返回一個request
- request為null說明不需要重連,則直接返回response丑掺,否則輪訓(xùn)重走網(wǎng)絡(luò)請求的流程。
BridgeInterceptor
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");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
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.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
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();
}
- BridgeInteceptor的Interceptor基本上都是添加請求的頭信息述雾,例如啟動是否使用長連接
Keep-Alive
街州,設(shè)置Cookie
,啟動壓縮與解壓gzip
等玻孟。
CacheInterceptor
public Response intercept(Chain chain) throws IOException {
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 = 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.
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;
}
- 首先根據(jù)request從cache中取response
- 將request和respnse傳入
CacheStrategy
根據(jù)緩存策略(比如僅適用網(wǎng)絡(luò)加載唆缴,僅適用緩存,緩存時效等)得到有策略處理后的networkRequest
和cacheResponse
- 若緩存策略要求僅從緩存中加載黍翎,且緩存未命中面徽,則本次請求失敗
- 若緩存策略不要求僅從網(wǎng)絡(luò)獲取數(shù)據(jù),則直接返回緩存內(nèi)容
- 以上條件不滿足匣掸,則把獲得response的任務(wù)交給下一個
Chain
趟紊,開始執(zhí)行網(wǎng)絡(luò)請求。 - 得到網(wǎng)絡(luò)請求結(jié)果后碰酝,如果已經(jīng)有緩存了霎匈,則用最新的網(wǎng)絡(luò)數(shù)據(jù)更新緩存。
- 最后將本次請求的結(jié)果response根據(jù)
cacheRequest
寫入緩存
ConnectInterceptor
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 httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
- 主要為下一步最終進(jìn)行網(wǎng)絡(luò)請求做鋪墊送爸,這里獲得了
HttpCodec
和RealConnection
铛嘱,然后將這些參數(shù)傳入下一個Chain暖释。 - 在
newStream
方法中會去先嘗試從RealConnectionPool
中尋找已經(jīng)存在的連接,若未命中則創(chuàng)建一個連接并與服務(wù)器握手對接墨吓。 - 在完成連接后會將Socket對象通過Okio封裝成BufferedSource和BufferedSink球匕,并將兩者傳入HttpCodec,在下一步網(wǎng)絡(luò)請求時會用到帖烘。
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
CallServerInterceptor
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();
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
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();
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} 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 == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
- 可以發(fā)現(xiàn)具體的實現(xiàn)都交給了
HttpCodec
亮曹,它是對Http協(xié)議操作的一種抽象,針對HTTP/1.1與HTTP2有Http1Codec
和Http2Codec
兩種實現(xiàn)蚓让。 - 方法的命名都是read和write乾忱,因為
HttpCodec
中最后的請求和響應(yīng)是由上一步封裝的BufferedSource和BufferedSink來完成的,sink負(fù)責(zé)輸出流历极,將寫入的數(shù)據(jù)交由socket發(fā)出窄瘟,source負(fù)責(zé)輸入流,從socket中讀取響應(yīng)數(shù)據(jù)趟卸。
OkHttp緩存相關(guān)
- 緩存策略
CacheStrategy
- 連接池
RealConnectPool
緩存策略
緩存策略主要是是CacheStrategy
這個類蹄葱,依賴于本地緩存Cache
和Http Header
緩存配置。
Cache
使用了DiskLruCache
作為緩存容器锄列,以request.url
作為key來存儲和讀取response
图云,這是一種很常見的緩存方式。
Http HEader
使用Http協(xié)議中約定的Cache-Control
邻邮、Expires
竣况、ETag、
Last-Modified筒严、
Date`等字段和服務(wù)端交互丹泉,由這些字段信息決定是否使用緩存,關(guān)于這些字段的含義可以查看Http協(xié)議中的定義鸭蛙。
public final class CacheStrategy {
/** The request to send on the network, or null if this call doesn't use the network. */
public final @Nullable Request networkRequest;
/** The cached response to return or validate; or null if this call doesn't use a cache. */
public final @Nullable Response cacheResponse;
CacheStrategy(Request networkRequest, Response cacheResponse) {
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
}
/** Returns true if {@code response} can be stored to later serve another request. */
public static boolean isCacheable(Response response, Request request) {
// Always go to network for uncacheable response codes (RFC 7231 section 6.1),
// This implementation doesn't support caching partial content.
switch (response.code()) {
case HTTP_OK:
case HTTP_NOT_AUTHORITATIVE:
case HTTP_NO_CONTENT:
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_NOT_FOUND:
case HTTP_BAD_METHOD:
case HTTP_GONE:
case HTTP_REQ_TOO_LONG:
case HTTP_NOT_IMPLEMENTED:
case StatusLine.HTTP_PERM_REDIRECT:
// These codes can be cached unless headers forbid it.
break;
case HTTP_MOVED_TEMP:
case StatusLine.HTTP_TEMP_REDIRECT:
// These codes can only be cached with the right response headers.
// http://tools.ietf.org/html/rfc7234#section-3
// s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
if (response.header("Expires") != null
|| response.cacheControl().maxAgeSeconds() != -1
|| response.cacheControl().isPublic()
|| response.cacheControl().isPrivate()) {
break;
}
// Fall-through.
default:
// All other codes cannot be cached.
return false;
}
// A 'no-store' directive on request or response prevents the response from being cached.
return !response.cacheControl().noStore() && !request.cacheControl().noStore();
}
public static class Factory {
final long nowMillis;
final Request request;
final Response cacheResponse;
/** The server's time when the cached response was served, if known. */
private Date servedDate;
private String servedDateString;
/** The last modified date of the cached response, if known. */
private Date lastModified;
private String lastModifiedString;
/**
* The expiration date of the cached response, if known. If both this field and the max age are
* set, the max age is preferred.
*/
private Date expires;
/**
* Extension header set by OkHttp specifying the timestamp when the cached HTTP request was
* first initiated.
*/
private long sentRequestMillis;
/**
* Extension header set by OkHttp specifying the timestamp when the cached HTTP response was
* first received.
*/
private long receivedResponseMillis;
/** Etag of the cached response. */
private String etag;
/** Age of the cached response. */
private int ageSeconds = -1;
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
/**
* Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
*/
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// 禁止使用網(wǎng)絡(luò)請求摹恨,并且沒有渠道cache
return new CacheStrategy(null, null);
}
return candidate;
}
/** 假設(shè)可以使用網(wǎng)絡(luò),返回一個策略. */
private CacheStrategy getCandidate() {
// No cached response.
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// 緩存結(jié)果缺少握手信息娶视,直接走網(wǎng)絡(luò)請求流程.
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// 根據(jù)當(dāng)前緩存Response的code值晒哄,http header等信息判斷本次緩存是否過期,是否可用肪获,如果不可用則直接走網(wǎng)絡(luò)請求流程
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//本次請求header包含`no-cache`,`If-Modified-Since`,`If-None-Match`等字段直接走網(wǎng)絡(luò)請求
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long maxStaleMillis = 0;
CacheControl responseCaching = cacheResponse.cacheControl();
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
/**
* Returns the number of milliseconds that the response was fresh for, starting from the served
* date.
*/
private long computeFreshnessLifetime() {
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.maxAgeSeconds() != -1) {
return SECONDS.toMillis(responseCaching.maxAgeSeconds());
} else if (expires != null) {
long servedMillis = servedDate != null
? servedDate.getTime()
: receivedResponseMillis;
long delta = expires.getTime() - servedMillis;
return delta > 0 ? delta : 0;
} else if (lastModified != null
&& cacheResponse.request().url().query() == null) {
// As recommended by the HTTP RFC and implemented in Firefox, the
// max age of a document should be defaulted to 10% of the
// document's age at the time it was served. Default expiration
// dates aren't used for URIs containing a query.
long servedMillis = servedDate != null
? servedDate.getTime()
: sentRequestMillis;
long delta = servedMillis - lastModified.getTime();
return delta > 0 ? (delta / 10) : 0;
}
return 0;
}
/**
* Returns the current age of the response, in milliseconds. The calculation is specified by RFC
* 2616, 13.2.3 Age Calculations.
*/
private long cacheResponseAge() {
long apparentReceivedAge = servedDate != null
? Math.max(0, receivedResponseMillis - servedDate.getTime())
: 0;
long receivedAge = ageSeconds != -1
? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds))
: apparentReceivedAge;
long responseDuration = receivedResponseMillis - sentRequestMillis;
long residentDuration = nowMillis - receivedResponseMillis;
return receivedAge + responseDuration + residentDuration;
}
/**
* Returns true if computeFreshnessLifetime used a heuristic. If we used a heuristic to serve a
* cached response older than 24 hours, we are required to attach a warning.
*/
private boolean isFreshnessLifetimeHeuristic() {
return cacheResponse.cacheControl().maxAgeSeconds() == -1 && expires == null;
}
/**
* Returns true if the request contains conditions that save the server from sending a response
* that the client has locally. When a request is enqueued with its own conditions, the built-in
* response cache won't be used.
*/
private static boolean hasConditions(Request request) {
return request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;
}
}
}
- 首先在
Factory
方法中獲取它所依賴的參數(shù)寝凌,就是我們之前提到過的Cache中渠道的緩存和Http Header配置信息。 - 然后進(jìn)入比較核心的方法
getCandidate
孝赫,在這里會根據(jù)之前拿到的依賴參數(shù)通過各種if判斷返回不同的CacheStrategy
對象 - 本職上其實返回不同的
networkRequest
和cacheResponse
硫兰,這樣上層只需要關(guān)注這兩個參數(shù)就知道下一步該如何做處理,復(fù)雜的判斷都封裝到了CacheStrategy
對外透明寒锚,具體判斷過程在代碼中做了注釋劫映。
連接池
okHttp利用連接池來復(fù)用連接违孝,避免反復(fù)握手建立連接,并且具備在合適的時候揮手連接的能力泳赋,這也是okhttp設(shè)計出彩的地方之一雌桑。