Hello小伙伴們,現(xiàn)在公司已經(jīng)恢復了正常辦公散罕,但是疫情依舊還在繼續(xù)分歇。最近工作實在是有點小忙,導致更新有點緩慢欧漱,實在抱歉职抡,本文是OkHttp原理解析的第二篇, 主要針對OkHttp中幾個 默認攔截器 的具體實現(xiàn)邏輯進行分析误甚。
因為OkHttp的很大一部分邏輯都在攔截器中繁调,因此本文會比較長,同時采用連載更新的方式進行描述靶草,每完成一個攔截器的邏輯分析都會進行更新。
如有對OkHttp的框架流程不太了解的可優(yōu)先閱讀網(wǎng)我上篇博客 OkHttp原理解析1(框架流程篇)
我又要開始表演了~~~
但為了方便后續(xù)描述岳遥,我還是簡單對上文做了個總結(jié)奕翔。大概分為以下幾步。
- 創(chuàng)建OkHttpClient(可通過OkHttpClient.Builder創(chuàng)建浩蓉,內(nèi)部設(shè)置一些基礎(chǔ)參數(shù))
- 創(chuàng)建 Request 對象設(shè)置 url派继,請求類型等參數(shù)
- 通過OkHttpClient.newCall()創(chuàng)建RealCall對象,同時創(chuàng)建了Transmitter對象捻艳。
- 通過RealCall.enqueue()或者RealCall.execute()觸發(fā) 異步或同步 請求驾窟。
- 調(diào)用OkHttpClient.dispatcher().enqueue(new AsyncCall(responseCallback))做請求準備,循環(huán)runningAsyncCalls和readyAsyncCalls 隊列找出host相同的AsyncCall進行重用认轨,并將readyAsyncCalls中AsyncCall轉(zhuǎn)移到runningAsyncCalls中绅络,如果runningAsyncCalls超過64則終止轉(zhuǎn)移,如相同主機計數(shù)器>5則終止轉(zhuǎn)移本AsyncCall。
-
- 循環(huán)runningAsyncCalls調(diào)用AsyncCall.executeOn(executorService())
- 6.1. AsyncCall為Runnable恩急,執(zhí)行run()方法杉畜,調(diào)用AsyncCall.execute()連帶調(diào)用AsyncCall.getResponseWithInterceptorChain()設(shè)置攔截器List,首先設(shè)置用戶自定義的攔截器衷恭。最后通過RealInterceptorChain.proceed()啟動攔截器此叠。
而攔截器的啟動與運行依賴 責任鏈 模式,大概分為以下3步随珠。
- 首先創(chuàng)建RealInterceptorChain對象灭袁,通過procee()判斷各種異常,并獲取當前Interceptor對象窗看。
- 然后 通過Interceptor.intercept(RealInterceptorChain)啟動當前攔截器邏輯茸歧,并且觸發(fā)下一個攔截器啟動。
- 如果當前攔截器出現(xiàn)異常等錯誤烤芦,則終止責任鏈举娩。
我們從添加攔截的的位置開始本文介紹,其實上文已經(jīng)做了描述构罗,源碼如下所示铜涉。
//RealCall.getResponseWithInterceptorChain();
Response getResponseWithInterceptorChain() throws IOException {
// 建立一個完整的攔截器堆棧
List<Interceptor> interceptors = new ArrayList<>();
//將創(chuàng)建okhttpclient時的攔截器添加到interceptors
interceptors.addAll(client.interceptors());
//重試攔截器,負責處理失敗后的重試與重定向
interceptors.add(new RetryAndFollowUpInterceptor(client));
//請求轉(zhuǎn)化攔截器(用戶請求轉(zhuǎn)為服務(wù)器請求遂唧,服務(wù)器響應轉(zhuǎn)為用戶響應)
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//緩存攔截器芙代。負責
//1.根據(jù)條件,緩存配置盖彭,有效期等返回緩存響應纹烹,也可增加到緩存。
//2.設(shè)置請求頭(If-None-Match召边、If-Modified-Since等) 服務(wù)器可能返回304(未修改)
//3.可配置自定義的緩存攔截器铺呵。
interceptors.add(new CacheInterceptor(client.internalCache()));
//網(wǎng)絡(luò)連接攔截器,主要負責和服務(wù)器建立連接隧熙。
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//創(chuàng)建okhttpclient時設(shè)置的networkInterceptors
interceptors.addAll(client.networkInterceptors());
}
//數(shù)據(jù)流攔截器片挂,主要負責像服務(wù)器發(fā)送和讀取數(shù)據(jù),請求報文封裝和解析贞盯。
interceptors.add(new CallServerInterceptor(forWebSocket));
//責任鏈模式的創(chuàng)建音念。
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
//啟動責任鏈
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
主要看我注釋的部分,OkHttp主要涉及了以下幾個默認攔截器
- client.interceptors();//用戶自定義攔截器
- RetryAndFollowUpInterceptor(client)//失敗重試攔截器
- BridgeInterceptor(client.cookieJar())//請求轉(zhuǎn)化攔截器
- CacheInterceptor(client.internalCache())//緩存攔截器
- ConnectInterceptor(client)//網(wǎng)絡(luò)連接攔截器
- CallServerInterceptor(forWebSocket)//數(shù)據(jù)流攔截器
OkHttp會把用戶自定義的攔截器默認放到攔截器列表的頭部躏敢,以方面優(yōu)先執(zhí)行闷愤,然后通過創(chuàng)建RealInterceptorChain對象,并調(diào)用RealInterceptorChain.proceed()啟動第一個攔截器件余,然后調(diào)用攔截器的interceptor.intercept(next)執(zhí)行第一個攔截器的邏輯并將下一個攔截器RealInterceptorChain對象傳入依此類推讥脐。接下來我們就一個個進行分析遭居。
1. client.interceptors() 連接失敗重試攔截器
用戶自定義的攔截器具有優(yōu)先執(zhí)行的特權(quán),具體執(zhí)行用戶自定義的邏輯攘烛,不過多介紹魏滚。
2. RetryAndFollowUpInterceptor(client) 連接失敗重試攔截器
首先開始的是RetryAndFollowUpInterceptor失敗重試攔截器,這個攔截器是可以在OkHttpClient.Builder對象中通過retryOnConnectionFailure(boolean retryOnConnectionFailure)設(shè)置是否開啟坟漱,默認構(gòu)建的OkHttpClient對象是開啟的鼠次。
該攔截器主要的職責官方注釋解釋了這么幾點。
- 無法訪問ip地址芋齿,如果URL的主機有多個IP地址腥寇,則無法訪問任何單個IP地址不會使整個請求失敗。這可以提高多宿服務(wù)的可用性觅捆。
- 過時的池連接赦役,通過ConnectionPool重用套接字以減少請求延遲,但這些連接偶爾會超時栅炒。
- 無法訪問的代理服務(wù)器掂摔,可以使用ProxySelector,最終返回到直接連接赢赊。
說實話乙漓,這描述略顯抽象,要不是我用過释移,我真不知道他在扯啥叭披,咱們還是通過源碼看下真像吧。
//我們應該嘗試多少重定向和驗證挑戰(zhàn)玩讳?Chrome遵循21個重定向涩蜘;Firefox、curl和wget遵循20熏纯;Safari遵循16同诫;HTTP/1.0建議5。
private static final int MAX_FOLLOW_UPS = 20;
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
//通過transmitter創(chuàng)建ExchangeFinder樟澜,Address误窖,RouteSelector三個對象
transmitter.prepareToConnect(request);
//判斷如果當前請求結(jié)束了則拋出異常,可通過transmitter終止請求。
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
//啟動下一個攔截器
response = realChain.proceed(request, transmitter, null);
//執(zhí)行順利則通過該字段退出循環(huán)往扔。
success = true;
} catch (RouteException e) {
// 如果是路由異常。請求還沒發(fā)送熊户。
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;
} catch (IOException e) {
// 嘗試與服務(wù)器通信失敗萍膛。請求可能已發(fā)送。
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
//網(wǎng)絡(luò)調(diào)用引發(fā)異常嚷堡。釋放所有資源蝗罗。
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// 如果body不為空
if (priorResponse != null) {
//獲得新的response
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
// 調(diào)用followUpRequest()查看響應是否需要重定向艇棕,不需要就返回當前請求,如果需要返回新的請求
Request followUp = followUpRequest(response, route);
// 不需要重定向或無法重定向
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
//如果重定向次數(shù)超過最大次數(shù)拋出異常
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
}
}
//RetryAndFollowUpInterceptor.recover()
//報告并嘗試從與服務(wù)器通信失敗中恢復串塑。如果{@code e}可恢復沼琉,則返回true;
//如果失敗是永久性的桩匪,則返回false打瘪。只有在緩沖了正文或在發(fā)送請求之前發(fā)生故障時,才可以恢復具有正文的請求傻昙。
private boolean recover(IOException e, Transmitter transmitter,
boolean requestSendStarted, Request userRequest) {
// 用戶設(shè)置的禁止重試
if (!client.retryOnConnectionFailure()) return false;
// 不能再發(fā)送請求體
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
// 異常是致命的
if (!isRecoverable(e, requestSendStarted)) return false;
// 沒有更多的路線可以嘗試焕刮。
if (!transmitter.canRetry()) return false;
//對于故障恢復同规,請對新連接使用同一路由選擇器
return true;
}
/**
* 查看響應是否需要重定向,不需要就返回當前請求,如果需要返回新的請求邑闲。
* 找出接收{(diào)@code userResponse}時要發(fā)出的HTTP請求。這將添加身份驗證頭癣诱、遵循重定向或處理客戶端請求超時匿沛。
* 如果后續(xù)操作不必要或不適用,則返回null须板。
*/
private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
//獲取響應碼
int responseCode = userResponse.code();
//請求方法
final String method = userResponse.request().method();
//響應碼分類處理
switch (responseCode) {
//407 代理需要身份認證
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
}
return client.proxyAuthenticator().authenticate(route, userResponse);
//401 需要身份認證
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// "If the 307 or 308 status code is received in response to a request other than GET
// or HEAD, the user agent MUST NOT automatically redirect the request"
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
// 300多種選擇碰镜。請求的資源可包括多個位置,相應可返回一個資源特征與地址的列表用于用戶終端(例如:瀏覽器)選擇
case HTTP_MULT_CHOICE:
// 301永久移動逼纸。請求的資源已被永久的移動到新URI洋措,返回信息會包括新的URI,瀏覽器會自動定向到新URI杰刽。
case HTTP_MOVED_PERM:
// 302臨時移動菠发。與301類似。但資源只是臨時被移動贺嫂。
case HTTP_MOVED_TEMP:
// 303查看其它地址滓鸠。與301類似。使用GET和POST請求查看
case HTTP_SEE_OTHER:
// Does the client allow redirects?
if (!client.followRedirects()) return null;
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// 不要遵循重定向到不支持的協(xié)議第喳。
if (url == null) return null;
//如果已配置糜俗,請不要遵循SSL和非SSL之間的重定向。
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// 大多數(shù)重定向不包括請求正文曲饱。
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
// 跨主機重定向時悠抹,刪除所有身份驗證頭。這對應用程序?qū)觼碚f可能很煩人扩淀,因為它們無法保留它們楔敌。
if (!sameConnection(userResponse.request().url(), url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
//408 超時
case HTTP_CLIENT_TIMEOUT:
// 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure()) {
// 應用層指示我們不要重試請求
return null;
}
RequestBody requestBody = userResponse.request().body();
if (requestBody != null && requestBody.isOneShot()) {
return null;
}
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
// 503 由于超載或系統(tǒng)維護,服務(wù)器暫時的無法處理客戶端的請求驻谆。延時的長度可包含在服務(wù)器的Retry-After頭信息中
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request();
}
return null;
default:
return null;
}
}
//Transmitter.prepareToConnect()
public void prepareToConnect(Request request) {
if (this.request != null) {
//判斷是不是相同的連接卵凑,如果是則用之前的庆聘。
if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
return; // Already ready.
}
if (exchange != null) throw new IllegalStateException();
//exchangeFinder
if (exchangeFinder != null) {
maybeReleaseConnection(null, true);
exchangeFinder = null;
}
}
this.request = request;
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
call, eventListener);
}
//Transmitter.createAddress()
private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (url.isHttps()) {
//https的設(shè)置
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner();
}
return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
}
//ExchangeFinder.ExchangeFinder()
ExchangeFinder(Transmitter transmitter, RealConnectionPool connectionPool,
Address address, Call call, EventListener eventListener) {
this.transmitter = transmitter;
this.connectionPool = connectionPool;
this.address = address;
this.call = call;
this.eventListener = eventListener;
//創(chuàng)建路由選擇器
this.routeSelector = new RouteSelector(
address, connectionPool.routeDatabase, call, eventListener);
}
//RouteSelector.RouteSelector()
RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
EventListener eventListener) {
this.address = address;
this.routeDatabase = routeDatabase;
this.call = call;
this.eventListener = eventListener;
resetNextProxy(address.url(), address.proxy());
}
該攔截器的邏輯很多,但其實主要做兩件事勺卢,一個是重試伙判,一個是重定向,邏輯流程大概是這樣的黑忱。
- 通過realChain.transmitter獲取了transmitter對象宴抚,并啟用一個while死循環(huán),
-
- 然后通過transmitter.prepareToConnect(request)transmitter創(chuàng)建ExchangeFinder杨何,Address酱塔,RouteSelector三個對象,并判斷了是否是相同的連接,是否需要maybeReleaseConnection(),重置ExchangeFinder危虱。
- 2.1 . ExchangeFinder此攔截器只是創(chuàng)建ExchangeFinder羊娃,但ExchangeFinder中有個find()方法主要通過內(nèi)部的 findHealthyConnection() 從 connectionPool 中找到一個可用的連接,這個連接可能是復用的埃跷,并 connect(),從而得到 輸入/輸出 流 (source/sink) 蕊玷,返回一個 Exchange 給 CallServerIntercepter , 通過這個 Exchange 就可以添加請求頭和請求體,并讀取響應頭和響應體弥雹,來交給上面的 Intercepter垃帅,層層向上傳遞。
- 2.2 . Address為請求參數(shù)的封裝類剪勿,包含url贸诚,端口,DNS厕吉,SSL酱固,Proxy,ProxySelector头朱,SocketFactory运悲,主機名驗證,證書校驗等邏輯项钮。
- 2.3 . RouteSelector主要來選擇路由班眯,主要做三件事。1.收集所有的可用路由烁巫。2.選擇可用路由署隘。3.借助RouteDatabase內(nèi)的Set對象來維護連接失敗的路由信息,防止去連接失敗路由浪費時間亚隙。
- 啟動攔截器列表的下一個攔截器磁餐。
- 判斷全局變量priorResponse是否為null,如果不為空則代表請求成功了恃鞋。
- 執(zhí)行 followUpRequest()查看響應是否需要重定向崖媚,如果不需要重定向則返回當前請求
- 判斷重定向次數(shù),如果超過最大值則退出恤浪。
- 重置request畅哑,并把當前的Response保存到priorResponse,進入下一次的while循環(huán)水由。
總結(jié)來說:
通過while死循環(huán)來獲取response荠呐,每次循環(huán)開始都根據(jù)條件獲取下一個request,如果沒有request砂客,則返回response泥张,退出循環(huán)。而獲取的request 的條件是根據(jù)上一次請求的response 狀態(tài)碼確定的鞠值,在循環(huán)體中同時創(chuàng)建了一些后續(xù)需要的對象
3. BridgeInterceptor(client.cookieJar())//請求轉(zhuǎn)化攔截器
咱先不看邏輯彤恶,就瞅這個名字。
Bridge:橋本刽, Interceptor:攔截器世囊。 連起來翻譯: 橋攔截器蝙寨? 這是 堵橋 的意思么?那得先給我把98K才行啊虹菲,最好能配個手雷毕源。在我簡單看了下源碼的注釋以及邏輯后發(fā)現(xiàn) 這其實是將用戶定義的對象Request轉(zhuǎn)換為網(wǎng)絡(luò)請求以及將網(wǎng)絡(luò)返回轉(zhuǎn)回用戶Response對象的橋梁址愿,那具體是怎么充當橋梁角色的呢,我們看下源代碼。
public BridgeInterceptor(CookieJar cookieJar) {
this.cookieJar = cookieJar;
}
- 首先是BridgeInterceptor的構(gòu)造函數(shù),在創(chuàng)建BridgeInterceptor對象的時候需要傳入CookieJar,CookieJar獲取的是OkHttpClient對象中的cookieJar,如果用戶沒有設(shè)置則默認使用OkHttpClient.Builder中的cookieJar,而OkHttpClient.Builder.cookieJar默認為CookieJar.NO_COOKIES白魂。
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
//通過用戶Request對象對請求頭進行包裝上岗。
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.
// 如果用戶沒有設(shè)置Accept-Encoding字段肴掷,okhttp默認使用Gzip進行壓縮,
//設(shè)置Accept-Encoding為Gzip的目的就是告訴服務(wù)器客戶端能接受的數(shù)據(jù)編碼類型呆瞻。
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));
}
if (userRequest.header("User-Agent") == null) {
//Version.userAgent()返回的是okhttp的版本號台夺。
requestBuilder.header("User-Agent", Version.userAgent());
}
//啟用責任鏈的下一個攔截器痴脾,并接收下一個攔截器的處理結(jié)果冤灾。
Response networkResponse = chain.proceed(requestBuilder.build());
//獲取服務(wù)器返回的 Cookie瞳购,設(shè)置接收頭
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//判斷服務(wù)器是否支持gzip壓縮年堆,如果支持吞杭,則將壓縮提交給Okio庫來處理
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();
}
總結(jié)
BridgeInterceptor的代碼也是一片,但是功能相對單一变丧,主要可以分為兩個部分來理解芽狗。
- 將用戶的請求Request包裝成網(wǎng)絡(luò)請求。
- 將網(wǎng)絡(luò)返回Response 包裝成用戶需要的返回對象痒蓬。
包裝網(wǎng)絡(luò)請求主要對請求頭做了特殊的處理童擎,具體可以看以上注釋。比較特殊的是Accept-Encoding攻晒。
如果用戶沒有自定義添加Accept-Encoding顾复,那框架會默認添加gzip,同時返回的數(shù)據(jù)也通過gzip解碼鲁捏。
自動解壓時需要注意芯砸,會移除Content-Encoding和Content-Length,所以java上層調(diào)用contentLength時為-1给梅。
如果用戶手動添加了Accept-Encoding則transparentGzip 為false假丧,返回的數(shù)據(jù)框架不負責解壓縮。
4. CacheInterceptor(client.internalCache())//緩存攔截器
- 這個攔截器看名字大概能知道是用來處理緩存的动羽,至于什么緩存包帚?我用一段話簡短的描述下。
- http請求后會有緩存生成在客戶端运吓,在有緩存的前提下渴邦,后續(xù)再發(fā)起請求前可以配置兩種策略
- 1.對比緩存
- 2.強制緩存
如果使用對比緩存則通過數(shù)據(jù)tag對比或時間對比,然后通過對比差異智能選擇是否使用緩存拘哨,如果使用強制緩存策略則直接使用緩存數(shù)據(jù)几莽。除此之外不同的是對比緩存必然會發(fā)起網(wǎng)絡(luò)請求,只是數(shù)據(jù)沒有變化會通過304狀態(tài)碼告訴客戶端通過緩存獲取數(shù)據(jù)宅静。而強制緩存無需網(wǎng)絡(luò)請求章蚣,直接緩存讀取。
- 其實上邊這段話大家都能看明白,但就是映射不到具體的場景中纤垂,接下來我寫一個簡單的例子來描述矾策。
//創(chuàng)建緩存對象,同時設(shè)置緩存位置峭沦,以及緩存的大小贾虽。
Cache cache = new Cache(new File("mnt/sdcard/package/cache"), 1024 * 1024);
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
//設(shè)置緩存對象
.cache(cache)
.build();
Request request = new Request
.Builder()
.url("")
//noCache()代表不使用緩存,通過網(wǎng)絡(luò)獲取吼鱼,此處如果是noStore()代表不緩存也不使用緩存蓬豁,如果不寫則默認緩存和使用緩存。
.cacheControl(new CacheControl.Builder().noCache().build())
.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 {
}
});
- 以上為自定義緩存的使用方式菇肃,但是具體的內(nèi)部原理是通過責任鏈模式實現(xiàn)的地粪,具體得到CacheInterceptor類中詳細分析,并且只要是責任鏈優(yōu)先看intercept()方法琐谤。
@Override
public Response intercept(Chain chain) throws IOException {
//通過本次請求的request對象獲取請求的緩存對象Response 作為候選緩存蟆技,此處如果配置了則cache不為空,沒有配置默認為空斗忌。
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根據(jù)時間戳质礼,請求request對象,本地候選緩存對象 生成一個緩存策略织阳,以此判斷使用網(wǎng)絡(luò)緩存眶蕉,本地緩存,還是都用唧躲。
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//不為空則使用網(wǎng)絡(luò)緩存
Request networkRequest = strategy.networkRequest;
//不為空使用本地緩存
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
//跟蹤滿足strategy策略的http響應
cache.trackResponse(strategy);
}
//如果候選緩存為空妻坝,但是本地緩存為空,證明本地緩存失效了惊窖,則關(guān)閉備選緩存cacheCandidate
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//如果禁止使用網(wǎng)絡(luò)緩存且本地緩存也未空則直接返回fial刽宪,code 504
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();
}
//如果不使用網(wǎng)絡(luò)請求緩存,則直接返回本地緩存數(shù)據(jù)界酒。
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//邏輯至此代表使用網(wǎng)絡(luò)請求圣拄。
Response networkResponse = null;
try {
//責任鏈開始繼續(xù)往下調(diào)用了。
networkResponse = chain.proceed(networkRequest);
} finally {
// 如果網(wǎng)絡(luò)返回對象為空毁欣,但本地候選緩存不為空則關(guān)閉候選緩存庇谆。
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 如果本地緩存存在,通過本地緩存和網(wǎng)絡(luò)返回的響應組合構(gòu)建一個reponse對象
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//如果網(wǎng)絡(luò)狀態(tài)碼為304則從本地緩存中獲取
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());
}
}
//邏輯至此說明本地緩存為空凭疮,通過網(wǎng)絡(luò)緩存構(gòu)建Response 對象
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
//將網(wǎng)絡(luò)的response添加到緩存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//判斷下是否為post請求饭耳,如果是則移除緩存,只保留get請求
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
總結(jié)
整體來說我把上述的一大段代碼總結(jié)為以下6個步驟执解,可輔助快速了解該攔截器的原理寞肖。
- 1.首先通過用戶配置的cache對象結(jié)合request請求對象獲取response緩存對象。
- 2.根據(jù)時間戳,request請求對象新蟆,以及本地的response緩存對象聯(lián)合生成本次請求的緩存策略觅赊,以此策略決定使用本地緩存,網(wǎng)絡(luò)緩存還是都用琼稻。
- 3.異常情況判斷吮螺,如果用戶配置緩存已經(jīng)獲取到,但本次緩存策略為不使用本地緩存帕翻,則關(guān)閉釋放本地獲取到的緩存數(shù)據(jù)鸠补,如果本次既不使用網(wǎng)絡(luò),也不使用本地則直接返回504錯誤嘀掸。
- 4.如果不可使用網(wǎng)絡(luò)緩存紫岩,可使用本地緩存的情況下直接return本次緩存。
- 5.如果使用網(wǎng)絡(luò)緩存則通過責任鏈chain.proceed(networkRequest);繼續(xù)往下一個攔截器傳遞横殴。
- 6.如果本地緩存不為空,且網(wǎng)絡(luò)請求返回了304狀態(tài)碼卿拴,則通過本地緩存和網(wǎng)絡(luò)返回共同構(gòu)建一個response返回衫仑,且更新到本地緩存。
- 7.如果本地緩存為空堕花,則將網(wǎng)絡(luò)返回的response返回并將response存在緩存文件中文狱,如果是post請求則再移除response(此處本質(zhì)意思是只緩存get請求,其實可以先判斷在存儲防止添加進去再移除缘挽。)
注:
緩存策略生成涉及幾個規(guī)則
- 1.用戶創(chuàng)建Request手動配置了noCache()代表不使用本地緩存瞄崇。
- 2.用戶創(chuàng)建Request手動配置了noStore()()代表不使用本地緩存同時網(wǎng)絡(luò)請求的數(shù)據(jù)也不緩存到本地。
- 3.通過“If-Modified-Since”字段判斷服務(wù)器的數(shù)據(jù)是否修改過壕曼,如果修改過則返回服務(wù)器的最新數(shù)據(jù)苏研,如果沒修改過可使用本地的緩存數(shù)據(jù)。(判斷規(guī)則通過時間判斷腮郊,如果上次請求的時間>服務(wù)器的最新更新時間則證明此數(shù)據(jù)沒有更新)
- 4.通過“If-None-Match”字段判斷服務(wù)器數(shù)據(jù)是否修改過摹蘑,如果與服務(wù)器資源的ETag不同則代表數(shù)據(jù)已經(jīng)更改,由服務(wù)器返回轧飞,如果相同則返回304從本地獲取衅鹿。(判斷規(guī)則需要ETag配合,ETag是資源的一個唯一標識过咬,每次請求前通過If-None-Match帶著ETag標識去對比大渤。)
5. ConnectInterceptor(client)//網(wǎng)絡(luò)連接攔截器
該攔截器主要用于處理網(wǎng)絡(luò)連接相關(guān)的攔截器,也是以責任鏈的模式進行調(diào)用的掸绞,因此我們首先看ConnectInterceptor中的intercept()方法泵三。
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//獲取到Request請求對象
Request request = realChain.request();
//通過realChain獲取到Transmitter對象,至于這個對象是干啥,先不用管
Transmitter transmitter = realChain.transmitter();
// 我們需要網(wǎng)絡(luò)來滿足這個要求切黔≡壹梗可能用于驗證條件GET。(前邊是直譯的纬霞,簡單來說是判斷當前請求的類型是post還是get凌埂,如果是post則后續(xù)需要網(wǎng)絡(luò)檢測)
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//關(guān)鍵邏輯,通過transmitter.newExchange()創(chuàng)建了Exchange 對象然后調(diào)用realChain.proceed()啟動下一個攔截器傳遞了诗芜。
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
- 對瞳抓,沒看錯,該攔截器只有以上幾行代碼伏恐。但是連帶邏輯會比較深入孩哑,我們一步一步進行分析,先看下transmitter.newExchange(chain, doExtensiveHealthChecks);內(nèi)部邏輯翠桦。
/** 返回一個新的Exchange對象横蜒,并且攜帶 request 和 response. */
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
if (noMoreExchanges) {
throw new IllegalStateException("released");
}
if (exchange != null) {
throw new IllegalStateException("cannot make a new request because the previous response "
+ "is still open: please call response.close()");
}
}
//只有這句是核心,我們需要繼續(xù)跟蹤
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
synchronized (connectionPool) {
this.exchange = result;
this.exchangeRequestDone = false;
this.exchangeResponseDone = false;
return result;
}
}
- 以上邏輯中只有ExchangeCodec 對象的生成邏輯不太明確销凑,因此我們需要繼續(xù)跟蹤
exchangeFinder.find(client, chain, doExtensiveHealthChecks);方法丛晌。
public ExchangeCodec find(OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
- 看到這個find()方法是不是有點熟悉的感覺?開始我還以為是我代碼功力可以跟OKHTTP的作者睥睨了斗幼,后來才想明白原來第一個攔截器RetryAndFollowUpInterceptor中分析過相關(guān)的邏輯澎蛛。
- 此時我們使用exchangeFinder對象即是通過RetryAndFollowUpInterceptor攔截器中的transmitter.prepareToConnect()創(chuàng)建出來的,我粘貼一下上邊描述的一段話蜕窿。
- ExchangeFinder中有個find()方法主要通過內(nèi)部的 findHealthyConnection() 從 connectionPool 中找到一個可用的連接谋逻,這個連接可能是復用的,并調(diào)用RealConnection.connect(),從而得到 輸入/輸出 流 (source/sink) 和對應的Http2Connection對象桐经。
- 最后返回的ExchangeCodec 對象是通過RealConnection .newCodec(client, chain)生成的毁兆,我們繼續(xù)跟進。
ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
if (http2Connection != null) {
return new Http2ExchangeCodec(client, this, chain, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1ExchangeCodec(client, this, source, sink);
}
}
- 至此邏輯已經(jīng)比較清晰了阴挣,RealConnection.newCodec()方法的返回值為ExchangeCodec 荧恍,而ExchangeCodec 為一個接口件舵,其實現(xiàn)類只有兩個唤反,分別為Http2ExchangeCodec和Http1ExchangeCodec。如果http2Connection不為空則直接創(chuàng)建并返回Http2ExchangeCodec對象茉唉,然后返回到transmitter.newExchange()方法中盒卸,最后返回到最初的ConnectInterceptor.intercept()方法中骗爆,從而得到Exchange 對象然后通過 realChain.proceed(request, transmitter, exchange)傳到下一個攔截器中。
其實該攔截器是OKHTTP中最核心的攔截器之一蔽介,以上的描述并沒有最深層次的展開代碼分析摘投,為了更清晰的表述ConnectInterceptor的完整邏輯煮寡,我總結(jié)了成了以下幾個步驟,可最快速度了解內(nèi)部原理與邏輯犀呼。
攔截器intercept()方法有兩步邏輯
- 1.首先通過realChain.transmitter()獲取到Transmitter對象幸撕,該對象是應用層和網(wǎng)絡(luò)層通信的中轉(zhuǎn)站,每一個請求都對應一個transmitter外臂。
- 2.通過transmitter.newExchange(.., ...)獲取Exchange對象并通過realChain.proceed(..., transmitter, exchange)傳遞到下一個攔截器中坐儿,核心在于獲取Exchange的過程。
獲取Exchange分為以下幾步
- 1.跟進transmitter.newExchange()后通過exchangeFinder.find(client, chain, doExtensiveHealthChecks)獲取ExchangeCodec宋光。
- 2.通過new Exchange(...)且將ExchangeCodec等參數(shù)傳入獲取到Exchange對象貌矿。
問題轉(zhuǎn)換為ExchangeCodec對象的獲取過程
- 注意:而ExchangeCodec 為一個接口,其實現(xiàn)類只有兩個罪佳,分別為Http2ExchangeCodec和Http1ExchangeCodec逛漫,而獲取ExchangeCodec需要通過exchangeFinder.find(...)獲取.
- 1.exchangeFinder對象是在RetryAndFollowUpInterceptor攔截器邏輯中初始化的,具體通過exchangeFinder.repareToConnect()進行的初始化赘艳。
- 2.exchangeFinder.find()中連帶調(diào)用了exchangeFinder.findHealthyConnection(...)獲取到了RealConnection對象(此時的邏輯是在找可用的健康連接)酌毡。
- 3.通過RealConnection.newCodec(...)獲取到了ExchangeCodec對象。
問題再次轉(zhuǎn)換為獲取RealConnection的過程
- 1.exchangeFinder.findHealthyConnection(...)死循環(huán)通過exchangeFinder.findConnection()找可用的連接蕾管,同時不可用的做好標記枷踏,方便后續(xù)移除。
- 2.第一次獲取連接過程首先會嘗試從transmitter.connection中獲取娇掏。
- 3.第二次獲取連接通過address呕寝,host勋眯,port婴梧,代理等信息去連接池匹配,如果未匹配到則取下一個代理的路由信息(多個Route客蹋,即多個IP地址)塞蹭,再次嘗試從連接池獲取。(transmitterAcquirePooledConnection()方法是嘗試從線程池中獲取連接的方法)
- 4.第三次通過創(chuàng)建RealConnection實例讶坯,進行TCP + TLS 握手番电,與服務(wù)端建立連接。
- 5.創(chuàng)建RealConnection實例的過程中還需要從連接池繼續(xù)匹配辆琅,如果匹配到則釋放剛剛創(chuàng)建的連接漱办,如果沒匹配到則將RealConnection加入連接池。
至此逐步返回則得到了Exchange實體婉烟,同時需要注意一個問題娩井,怎么判斷RealConnection是不是監(jiān)控的連接?
- 1.通過socket.isClosed()似袁,isInputShutdown()洞辣,isOutputShutdown()判斷socket是否可用咐刨。
- 2.Http2的連接通過http2Connection.isShutdown()
- 3.socket通過setSoTimeout()設(shè)置一秒延時,檢測Stream是否可用扬霜,若可用則該連接不可用定鸟。
- CallServerInterceptor(forWebSocket)//數(shù)據(jù)流攔截器
誒呀媽呀,終于到了最后一個攔截器了著瓶,這篇博客寫的太久了联予,終于要完結(jié)了。
CallServerInterceptor攔截器的主要用于做網(wǎng)絡(luò)連接蟹但,收發(fā)轉(zhuǎn)換數(shù)據(jù)使用的躯泰,前邊的一切準備都是為了最后這一步請求。我們直接看代碼华糖,還是和之前的攔截器相同麦向,直接看intercept()方法。
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
//一看這名字就知道是寫請求Header的
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
//如果本次請求既不是get請求客叉,也不是Head诵竭,并且請求的body不為空(換句話說是post請求,且有請求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.
//如果header中添加了(Expect: 100-continue)兼搏,則需要等待服務(wù)端返回含有 "HTTP/1.1 100 Continue"的響應卵慰,然后再根據(jù)響應內(nèi)容確定是否發(fā)送body,如果服務(wù)器可以接收body會返回null佛呻。
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
//詢問服務(wù)器是否愿意接受body
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
//此處為服務(wù)器返回結(jié)果
responseBuilder = exchange.readResponseHeaders(true);
}
//responseBuilder 為null代表服務(wù)器接收RequestBody
if (responseBuilder == null) {
//除非子類重寫了isDuplex()方法,否則這個方法默認返回值為false
//以下的邏輯是寫body的操作吓著。
if (request.body().isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, true));
//寫入請求主體信息
request.body().writeTo(bufferedRequestBody);
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, false));
//寫入請求主體信息
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
//此處代表服務(wù)器不接收body鲤嫡,首先這個方法把資源釋放了。
exchange.noRequestBody();
//這個判斷是用于判斷RealConnection對象中的http2Connection是否為空
if (!exchange.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.
//如果為空绑莺,證明是http1.0暖眼,且服務(wù)器不接收body,這時應該關(guān)閉連接纺裁,防止http1.0被重用诫肠,因為只有http2.0才能被復用。
exchange.noNewExchangesOnConnection();
}
}
} else {
//沒有body欺缘,直接請求結(jié)束栋豫。
exchange.noRequestBody();
}
if (request.body() == null || !request.body().isDuplex()) {
//請求結(jié)束
exchange.finishRequest();
}
if (!responseHeadersStarted) {
//讀取響應頭開始回調(diào)
exchange.responseHeadersStart();
}
if (responseBuilder == null) {
//讀取響應頭
responseBuilder = exchange.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
//如果服務(wù)器返回了100就再次嘗試獲取真正的響應。
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
//回調(diào)讀取響應頭結(jié)束
exchange.responseHeadersEnd(response);
//如果狀態(tài)碼為101谚殊,獲取響應body
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(exchange.openResponseBody(response))
.build();
}
//如果請求的Header中key為“Connection”的值為"close"丧鸯,則請求完后需要關(guān)閉連接。
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
//如果狀態(tài)碼是204/205络凿,那返回數(shù)據(jù)應該是空骡送,如果返回長度大于0則報異常(ProtocolException)
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
看到這里整個CallServerInterceptor攔截器的源碼就分析結(jié)束了昂羡,其實這個攔截器是整個OKHTTP中網(wǎng)絡(luò)io的核心,因此為了方便理解摔踱,我總結(jié)下這個攔截器內(nèi)都干了什么事虐先。
- 1.首先通過責任鏈對象獲取RealInterceptorChain對象,并獲取到Exchange對象派敷,通過Exchange.writeRequestHeaders()將header寫入蛹批。
- 2.如果Request中包含body,還需要判斷請求頭中是否包含"Expect: 100-continue"鍵值對篮愉,如果有則需要等待服務(wù)器返回“HTTP/1.1 100 Continue”的結(jié)果腐芍,以此結(jié)果決定是否發(fā)送body。(如果返回null則代表返回了100狀態(tài)碼试躏,需要發(fā)送body猪勇,如返回4**狀態(tài)碼,則代表不發(fā)送body)
- 3.通過okio.buffer()配合exchange.createRequestBody(request, true)颠蕴,構(gòu)造出BufferedSink對象泣刹,然后通過 request.body().writeTo(BufferedSink)寫請求body。
- 4.請求發(fā)送結(jié)束后犀被,通過exchange的responseHeadersStart()發(fā)送讀取響應頭回調(diào)椅您,并通過readResponseHeaders(false)開始讀取響應頭(注意,如果服務(wù)器返回100狀態(tài)碼寡键,需要再次讀取一次響應掀泳。),讀取完后需要調(diào)用exchange.responseHeadersEnd(response)回調(diào)告知讀取響應頭結(jié)束西轩。
- 5.開始通過 response.newBuilder().body(exchange.openResponseBody(response)).build();讀取響應body员舵。(具體的讀取都在Http2ExchangeCodec類中進行)
- 6.最后如果header中的鍵值對Connection是close,則需要在請求結(jié)束關(guān)閉連接遭商。
這么久固灵,終于寫完了捅伤,相信看到這的你也對OKHTTP的流程有了更深入的了解劫流,如果有錯誤希望各位指正。如果覺得有點收獲丛忆,可以幫忙點點贊祠汇。