在上一篇文章中,主要梳理了 OKHttp 請求的整體流程,了解到攔截器在其中有非常重要的意義,那么本篇文章就重點(diǎn)介紹一下 OKHttp 中的精髓 ---- 攔截器。本文中涉及到的自定義類的源碼都在 Github 上的 OkHttpPractice 工程中。
- 攔截器的使用
- 源碼中攔截器的應(yīng)用及分析
1. 攔截器的使用
攔截器是 OKHttp 設(shè)計(jì)的精髓所在,每個攔截器負(fù)責(zé)不同的功能,使用責(zé)任鏈模式,通過鏈?zhǔn)秸{(diào)用執(zhí)行所有的攔截器對象中的 Response intercept(Chain chain) 方法。攔截器在某種程度上也借鑒了網(wǎng)絡(luò)協(xié)議中的分層思想,請求時從最上層到最下層,響應(yīng)時從最下層到最上層。
一個攔截器可以攔截請求和響應(yīng),獲取或修改其中的信息,這在編程中是非常有用的。不僅在源碼中攔截器使用的很廣泛,開發(fā)者也可以根據(jù)自己的需求自定義攔截器,并將其加入到 OkHttpClient 對象中。
1.1 自定義攔截器
攔截器的源碼如下:
/**
* 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;
/**
* 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();
}
}
其中最重要的方法便是 Response intercept(Chain chain) 方法。自定義攔截器只要實(shí)現(xiàn) interceptor 接口,重寫其中的 Response intercept(Chain chain) 方法便基本完成。
在開發(fā)中,經(jīng)常想查看服務(wù)器響應(yīng)中的內(nèi)容,如果使用攔截器的話,則可以非常方便的實(shí)現(xiàn),只要自定義攔截器并加入到 OKHttpClient 對象中,那么使用此 OKHttpClient 對象進(jìn)行的網(wǎng)絡(luò)請求都會將其響應(yīng)信息打印出來。自定義的攔截器 LogInterceptor,源碼如下所示:
public class LogInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response;
long startTime = System.currentTimeMillis();
response = chain.proceed(request);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
BufferedSource source = response.body().source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
String log = "\n================="
.concat("\nnetwork code ==== " + response.code())
.concat("\nnetwork url ===== " + request.url())
.concat("\nduration ======== " + duration)
.concat("\nrequest duration ============ " + (response.receivedResponseAtMillis() - response.sentRequestAtMillis()))
.concat("\nrequest header == " + request.headers())
.concat("\nrequest ========= " + bodyToString(request.body()))
.concat("\nbody ============ " + buffer.clone().readString(UTF8));
Log.i("lijk", "log is " + log);
return response;
}
/**
* 請求體轉(zhuǎn)String
*
* @param request 請求體
* @return String 類型的請求體
*/
private static String bodyToString(final RequestBody request) {
try {
final Buffer buffer = new Buffer();
request.writeTo(buffer);
return buffer.readUtf8();
} catch (final Exception e) {
return "did not work";
}
}
}
1.2 注意事項(xiàng)
這里有個坑需要注意一下:response.body().string(); 方法只能被調(diào)用一次,如果多次調(diào)用 response.body().string(); 則會拋出如下異常:

可以看到 string() 的源碼如下所示:
/**
* Returns the response as a string decoded with the charset of the Content-Type header. If that
* header is either absent or lacks a charset, this will attempt to decode the response body in
* accordance to <a >its BOM</a> or UTF-8.
* Closes {@link ResponseBody} automatically.
*
* <p>This method loads entire response body into memory. If the response body is very large this
* may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a
* possibility for your response.
*/
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
因?yàn)樵趫?zhí)行完讀取數(shù)據(jù)之后,IO 流被關(guān)閉,如果再次調(diào)用此方法,就會拋出上面的異常。
而且從注釋中可以看到,此方法將響應(yīng)報(bào)文中的主體全部都讀到了內(nèi)存中,如果響應(yīng)報(bào)文主體較大,可能會導(dǎo)致 OOM 異常。所以更推薦使用流的方式獲取響應(yīng)體的內(nèi)容。如下所示:
BufferedSource source = response.body().source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
String response = buffer.clone().readString(UTF8);
2. 源碼中攔截器的應(yīng)用及分析
在上一篇分析整體流程的文章中,在 RealCall 中有一個方法非常重要,不論是異步請求還是同步請求,都是通過該方法獲取服務(wù)器響應(yīng)的,源碼如下所示:
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()));
// 連接服務(wù)器攔截器
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
// 用戶定義的網(wǎng)絡(luò)攔截器
interceptors.addAll(client.networkInterceptors());
}
// 請求服務(wù)器攔截器
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
// 通過 RealInterceptorChain 對象鏈?zhǔn)降恼{(diào)用攔截器,從而得到響應(yīng)。
return chain.proceed(originalRequest);
}
其中的每個攔截器負(fù)責(zé)具體不同的功能,接下來就分析每個攔截器的功能,因?yàn)閿r截器中最重要的方法便是 Response intercept(Chain chain),所以我們也重點(diǎn)分析 Response intercept(Chain chain) 方法的實(shí)現(xiàn)。
2.1 RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor 攔截器主要負(fù)責(zé)實(shí)現(xiàn) HTTP 協(xié)議中的認(rèn)證質(zhì)詢、重定向和超時重試等協(xié)議機(jī)制。
RetryAndFollowUpInterceptor 攔截器的主要功能如下所示:
- 初始化連接對象
StreamAllocation - 通過
RealInterceptorChain調(diào)用鏈對象得到響應(yīng) - 通過得到的響應(yīng),根據(jù) HTTP 協(xié)議做認(rèn)證質(zhì)詢、重定向和超時重試等處理,通過
followUpRequest()方法創(chuàng)建后續(xù)新的請求 - 若沒有后續(xù)請求,即
followUpRequest()方法返回為null,則說明當(dāng)前請求結(jié)束,返回響應(yīng)
,若后續(xù)請求不為空,則繼續(xù)進(jìn)行請求
源碼如下:
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 創(chuàng)建一個 StreamAllocation 對象
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
// 啟動一個 While 死循環(huán)
while (true) {
// 判斷是否已經(jīng)取消,若已取消則拋出 IO 異常
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
// 通過 RealInterceptorChain 對象調(diào)用下一個攔截器,并從中得到響應(yīng)
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.
// 如果進(jìn)入 RouteException 路由異常,則嘗試是否可以重新進(jìn)行請求,若可以則從頭開始新的請求
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.
// 若是進(jìn)入 IOException IO異常,若可以重新嘗試請求,則從頭開始新的請求
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.
// 如果之前發(fā)生過重定向,并且 priorResponse 不為空,則創(chuàng)建新的 響應(yīng)對象,并將其 body 置位空
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
// 添加認(rèn)證需要的頭部,處理重定向或超時重試,得到新的請求
// followUpRequest() 方法很重要,涉及到 HTTP 中認(rèn)證質(zhì)詢、重定向和重試等協(xié)議的實(shí)現(xiàn)
Request followUp = followUpRequest(response);
// 若 followUp 重試請求為空,則當(dāng)前請求結(jié)束,并返回當(dāng)前的響應(yīng)
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
// 關(guān)閉響應(yīng)結(jié)果
closeQuietly(response.body());
// 若重定向、認(rèn)證質(zhì)詢、重試次數(shù)超過 MAX_FOLLOW_UPS,則拋出 ProtocolException 異常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
// 若請求中的主體為 UnrepeatableRequestBody 不可被重復(fù)使用的請求體類型,則拋出異常
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 判斷是否是相同的連接,若不相同則釋放 streamAllocation,并重新創(chuàng)建新的 streamAllocation 對象
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;
}
}
2.2 BridgeInterceptor
BridgeInterceptor 攔截器主要功能是:
- 設(shè)置一些請求和響應(yīng)首部,如:
Content-Type、Content-Length、Host等常見的請求和響應(yīng)首部。 - 處理 HTTP 請求和響應(yīng)中的 Cookie
- 如果在請求中設(shè)置了編碼,要從響應(yīng)流中解碼
源碼如下:
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
// 在請求中設(shè)置實(shí)體首部 Content-Type
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
// 在請求中設(shè)置實(shí)體首部 Content-Length 和 Transfer-Encoding
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");
}
}
// 設(shè)置請求首部 `Host`
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
// 設(shè)置請求首部 Connection,若 `Connection` 為空,則打開長連接
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.
// 如果在請求中設(shè)置了 "Accept-Encoding: gzip",要記得從響應(yīng)流中解碼
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));
}
// 設(shè)置請求首部 "User-Agent"
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
// 從響應(yīng)中得到 Cookie,并交給傳入的 CookieJar 對象處理
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// 如果之前在請求中設(shè)置了 "Accept-Encoding: gzip" 編碼,則需要對響應(yīng)流進(jìn)行解碼操作并移除響應(yīng)中的首部字段 “Content-Encoding” 和 “Content-Length”
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();
}
這里涉及到 OKHttp 中對 Cookie 的處理,其中有一個接口十分重要 ---- CookieJar,源碼如下:
public interface CookieJar {
/** A cookie jar that never accepts any cookies. */
CookieJar NO_COOKIES = new CookieJar() {
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
}
@Override public List<Cookie> loadForRequest(HttpUrl url) {
return Collections.emptyList();
}
};
// 從響應(yīng)中獲取 Cookie
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
// 為請求添加 Cookie
List<Cookie> loadForRequest(HttpUrl url);
}
OKHttp 對 Cookie 的管理還是十分方便簡潔的。創(chuàng)建一個類實(shí)現(xiàn) Cookiejar 接口,并將其對象設(shè)置給 OKHttpClient,那么使用此 OKHttpClient 對象進(jìn)行的網(wǎng)絡(luò)請求都會自動處理 Cookie。這里有一個我實(shí)現(xiàn)的自動管理 Cookie 的類 CustomCookieManager,可以實(shí)現(xiàn) Cookie 的持久化,Github 上還有其他很好的實(shí)現(xiàn)在 OKHttp 中管理 Cookie 的類,也可以參考。
2.3 CacheInterceptor
CacheInterceptor 實(shí)現(xiàn)了 HTTP 協(xié)議中的緩存機(jī)制,其主要功能如下:
- 從緩存中讀取緩存,并創(chuàng)建緩存策略對象
- 根據(jù)創(chuàng)建的緩存策略對象,從緩存、網(wǎng)絡(luò)獲取響應(yīng)并生成最終的響應(yīng)對象
- 更新緩存內(nèi)容,并返回響應(yīng)對象
源碼如下:
public final class CacheInterceptor implements Interceptor {
final InternalCache cache;
public CacheInterceptor(InternalCache cache) {
// 從 OKHttpClient 中傳入的 InternalCache(內(nèi)部緩存)對象
this.cache = cache;
}
@Override public Response intercept(Chain chain) throws IOException {
// 獲取候選緩存
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 創(chuàng)建緩存策略對象,并從中得到請求和響應(yīng)
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.
// 如果不走網(wǎng)絡(luò)進(jìn)行請求,并且緩存響應(yīng)為空,則創(chuàng)建狀態(tài)碼為 504 的響應(yīng)并返回
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.
// 如果不走網(wǎng)絡(luò)進(jìn)行請求,并且緩存響應(yīng)不為空,則返回從緩存中獲取的響應(yīng)
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 如果前面的條件都不滿足,從攔截器鏈中進(jìn)行網(wǎng)絡(luò)請求并得到響應(yīng)
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) {
// 如果緩存響應(yīng)也不為空,并且網(wǎng)絡(luò)響應(yīng)的狀態(tài)碼為 304,則根據(jù)緩存響應(yīng)結(jié)果生成最終的響應(yīng)并返回
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();
// 更新緩存中的響應(yīng)內(nèi)容
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
// 根據(jù)網(wǎng)絡(luò)請求響應(yīng)生成最終的響應(yīng)
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 如果緩存對象不為空,則將響應(yīng)加入到緩存中
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;
}
......
}
2.4 ConnectInterceptor
ConnectInterceptor 攔截器的主要功能是:
- 得到從
RetryAndFollowUpInterceptor中創(chuàng)建的StreamAllocation對象 - 通過
StreamAllocation對象創(chuàng)建HttpCodec對象 - 通過
StreamAllocation對象創(chuàng)建RealConnection對象 - 最終通過
RealInterceptorChain的proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection)方法得到響應(yīng)對象并返回。
源碼如下:
/** Opens a connection to the target server and proceeds to the next interceptor. */
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@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 httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
在
ConnectInterceptor攔截器中用到了StreamAllocation類,此類創(chuàng)建了HttpCodec和RealConnection對象,這兩個類在網(wǎng)絡(luò)請求中都是非常重要的類,會在后面文章中詳細(xì)分析
2.5 CallServerInterceptor
CallServerInterceptor 攔截器中最主要的功能就是:
- 遵循 HTTP 協(xié)議規(guī)范,通過
HttpCodec對象寫入請求頭、請求主體、讀取響應(yīng)頭和響應(yīng)主體 - 生成最初的響應(yīng)對象并返回
源碼如下所示:
@Override public Response intercept(Chain chain) throws IOException {
// 得到 httpCodec、streamAllocation、connection 和 request 對象
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
// 向 HttpCodec 對象中寫入請求頭部信息
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
// 判斷該請求的請求方法是否允許被發(fā)送請求體,請求體是否為空
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.
// 若在請求頭部中存在 ”Expect: 100-continue“,先不發(fā)送請求主體,只有收到 ”100-continue“ 響應(yīng)報(bào)文才會將請求主體發(fā)送出去。
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();
}
}
// 完成請求的發(fā)送
httpCodec.finishRequest();
// 讀取響應(yīng)頭部信息
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
// 創(chuàng)建請求響應(yīng)對象
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
// 判斷是否返回一個空的響應(yīng)
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 {
// 讀取響應(yīng)中的響應(yīng)體信息
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
// 判斷是否關(guān)閉長連接
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
// 如果響應(yīng)的狀態(tài)碼為 204 和 205 并且響應(yīng)體不為空,則拋出異常
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
在此攔截器中非常重要的一個對象是 HttpCodec,它是一個接口,具體的實(shí)現(xiàn)類有 Http1Codec 和 Http2Codec 兩個類,分別對應(yīng)著 HTTP1.1 和 HTTP2。在 HttpCodec 內(nèi)部是通過
sink和source來實(shí)現(xiàn)的。
2.6 注意
關(guān)于 OKHttp 的攔截器需要注意的一點(diǎn)是,從 RealCall 的 getResponseWithInterceptorChain() 方法中可以看到開發(fā)自定義的攔截器有兩種類型的
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);
}
一種是在最開始,通過 client.interceptors() 方法添加的普通攔截器;另一種是通過 client.networkInterceptors() 方法添加的網(wǎng)絡(luò)請求攔截器,兩者的區(qū)別如下:
- 普通攔截器:對發(fā)出去的請求做最初的處理,對最終得到的響應(yīng)做處理
- 網(wǎng)絡(luò)攔截器:對發(fā)出去的請求做最后的處理,對收到的響應(yīng)做最初的處理
兩種攔截器都可以通過在初始化 OKHttpClient 對象的時候設(shè)置,如下所示:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LogInterceptor())
.addNetworkInterceptor(new LogInterceptor())
.build();
雖然都是添加了 LogInterceptor 攔截器給 OKHttpClient 對象,但是從 RealCall 的 getResponseWithInterceptorChain() 方法中可以看出兩個攔截器調(diào)用的時機(jī)不同。
本文中涉及到的自定義類的源碼都在 Github 上的 OkHttpPractice 工程中。
參考資料:
OkHttp源碼解析 -- 俞其榮
OkHttp源碼解析 -- 高沛