在上一篇 OkHttp 請求流程分析中,分別介紹了同步和異步請求的具體流程,也瞭解到具體的的網路請求時經過 OkHttp 中的 RealCall 的攔截器鏈實現的,還沒了解的朋友建議去看下上篇文章。java
OkHttp 的攔截器總共分爲如下幾種: 緩存
綠色的部分是咱們用戶能夠本身去定義添加的,不設置就沒有,藍色的部分是 OkHttp 內部實現的,不設置也有,而且不可更改。服務器
下面依次進行講解分析:cookie
應用攔截器的使用:網絡
設置攔截器app
這樣就添加了一個很普通的應用攔截器。less
官方對應用攔截器的描述異步
中文:socket
RetryAndFollowUpInterceptor 攔截器是應用攔截器以後的第一個攔截器,主要是負責失敗重連和重定向的。ide
可是並非全部的網絡請求能夠進行重連的,在RetryAndFollowUpInterceptor 攔截器的內部會經過必定的邏輯進行判斷。具體來看下源碼是怎麼實現的:
根據上一篇文章中講述的,主要邏輯存在 intercept 方法中:
@Override public Response intercept(Chain chain) throws IOException {
// 獲取原始請求
Request request = chain.request();
// 建立 StreamAllocation對象,這裏只是建立,具體處理交給後面的攔截器處理
// 主要用戶獲取鏈接服務端的 Connect鏈接 和 用於服務端用於數據傳輸的輸入輸出流
// 主要給 ConnectIntercept 使用的
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 {
// 傳入建立的StreamAllocation對象,交給下個攔截器處理,至於下個攔截器處理不處理,不用管
// 在 catch 和 finally 中作了一些異常處理和資源釋放
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// 若是進入 RouteException 路由異常,則嘗試是否能夠從新進行請求,若能夠則從頭開始新的請求
// 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) {
// 如果進入 IOException IO異常,若能夠從新嘗試請求,則從頭開始新的請求
// 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();
}
}
//若是以前發生太重定向,而且 priorResponse 不爲空,則建立新的 響應對象,並將其 body 置位空
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
// 等到一個接收 Response 的HTTP新請求。
// 這將添加身份驗證、遵循重定向、處理客戶端請求超時。若是後續操做沒必要要或不適用,則返回null。
Request followUp = followUpRequest(response);
// 若 followUp 重試請求爲空,返回當前的響應
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
//關閉響應結果
closeQuietly(response.body());
// 若是重試次數超過最大值MAX_FOLLOW_UPS,釋放資源,拋出異常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
// 若是新請求的 body 屬於不可重複請求的 body,釋放資源
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 判斷是不是相同的鏈接,若不相同,釋放 streamAllocation,建立新的 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;
}
}
複製代碼
總結主要做用下:
BridgeInterceptor 主要做用就是添加頭部、設置壓縮等。
仍是主要看下 Intercept 中的代碼:
@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");
}
}
// 設置主機地址
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
// 設置鏈接方式,這裏看到默認是開啓持久鏈接的 使用 Keep-Alive
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
// 默認不使用gzip 傳輸, 若是咱們添加 gzip 壓縮頭字段,會對傳輸的數據進行編碼解碼處理
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 設置 cookies
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
// 設置 ua
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
// 調用下個攔截器,等待下個攔截器返回結果
Response networkResponse = chain.proceed(requestBuilder.build());
// 執行到這裏,下面都是對返回值進行處理
// 將網絡請求返回的 Response 轉換成用戶可使用的 Response
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// 若是請求的時候支持 gzip,而且服務器返回的 response 響應頭也支持 gzip,,而且響應體有值,就要負責解壓工做
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();
}
複製代碼
總結主要做用:
緩存攔截器主要是對網絡請求的緩存進行處理。使用很簡單,主要經過 Cache 類實現的,在建立 OkHttpClient 的時候設置 cache 就行。
// 建立 Cache 類 "app/cache"是緩存的路徑 20*1024*1024 是緩存大小
Cache cache = new Cache(new File("app/cache"), 20 * 1024 * 1024);
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.addInterceptor(new ApplicationIntercept())
//設置緩存
.cache(cache)
.build();
複製代碼
這樣就可以藉助於 OkHttp 幫助咱們實現緩存。
具體看下 CacheInterceptor 的 Intercept 方法:
public final class CacheInterceptor implements Interceptor {
// 實現緩存功能的 InternalCache 類,下面講解
final InternalCache cache;
public CacheInterceptor(InternalCache cache) {
this.cache = cache;
}
@Override public Response intercept(Chain chain) throws IOException {
// 若是 cache 爲空,則用戶沒設置緩存,直接返回 null
// 不爲空,設置了緩存,經過 cache 對象的 get 方法去獲取緩存,稱之爲候選緩存
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 建立緩存策略,而且分別獲得 請求 networkRequest,和響應 cacheResponse
// 裏面維護了一個 Request 和 Response ,關於這個策略,下面分析
// public final Request networkRequest; 若是不須要網絡訪問,爲 null,不爲 null,須要網絡請求
// public final Response cacheResponse; 不使用緩存,爲 null,不爲 null,表示可使用本地緩存
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.
// 若是網絡不可用,而且沒有緩存,經過構建者模式構建一個 504 的 Response。並返回
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 {
// 調用下一個攔截器去獲取 Response
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.
// 此時經過網絡請求拿到了 Response ,而且本地有緩存的 response,
if (cacheResponse != null) {
// 若是響應碼爲 未修改,直接使用緩存的 Response做爲最終的返回 Response
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();
// 若是返回的 Response 有請求體
if (HttpHeaders.hasBody(response)) {
// 經過調用 maybeCache() 方法,進而調用 cache 的 put 寫入用戶緩存
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
// 寫入緩存
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
}
複製代碼
緩存策略CacheStrategy:
前面在 Intercept 方法中調用了
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
// 看下靜態內部類的 Factory 構造方法,主要是讀取傳入的 緩存 Response 的 headers 信息
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++) {
// 響應頭 key
String fieldName = headers.name(i);
// 響應頭 value
String value = headers.value(i);
// 有 Date 頭,服務器返回時間信息
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
// 有 Expires 頭,獲取有效期信息
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
// 有 Last-Modified 頭,獲取最後修改時間,
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
// 有 ETag 頭,獲取資源的版本
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
// 有 Age 頭,獲取到如今的經歷了多少時間
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
// 而後調用 get 方法
/** * Returns a strategy to satisfy {@code request} using the a cached response {@code response}. */
public CacheStrategy get() {
// 返回一個假設可使用網絡請求的緩存策略,也就是 networkRequest 不爲 null
CacheStrategy candidate = getCandidate();
// 若是networkRequest 不爲 null,表示須要使用網絡,而且 Request 的 CacheControl 字段設置爲只使用緩存
// 就須要設置 都爲 null,而後再 CacheInterceptor 中構建錯誤的返回。
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
// 返回不請求網絡,不使用緩存
return new CacheStrategy(null, null);
}
return candidate;
}
複製代碼
前面講到的將響應 Response 添加到本地,主要是經過 CacheInterceptor 內部的 InternalCache 實現的,
public interface InternalCache {
Response get(Request request) throws IOException;
CacheRequest put(Response response) throws IOException;
/** * Remove any cache entries for the supplied {@code request}. This is invoked when the client * invalidates the cache, such as when making POST requests. */
void remove(Request request) throws IOException;
/** * Handles a conditional request hit by updating the stored cache response with the headers from * {@code network}. The cached response body is not updated. If the stored response has changed * since {@code cached} was returned, this does nothing. */
void update(Response cached, Response network);
/** Track an conditional GET that was satisfied by this cache. */
void trackConditionalCacheHit();
/** Track an HTTP response being satisfied with {@code cacheStrategy}. */
void trackResponse(CacheStrategy cacheStrategy);
}
複製代碼
而 InternalCache 是個接口,內部都是調用的 Cache 這個類中的方法,先看下 Cache 類:
public final class Cache implements Closeable, Flushable {
private static final int VERSION = 201105;
private static final int ENTRY_METADATA = 0;
private static final int ENTRY_BODY = 1;
private static final int ENTRY_COUNT = 2;
// 這個 InternalCache 是個接口,裏面定義了經常使用的增刪改查等操做
// 全部的 實現都是經過 外部的 Cache 類的對應方法實現的
final InternalCache internalCache = new InternalCache() {
@Override public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
@Override public CacheRequest put(Response response) throws IOException {
return Cache.this.put(response);
}
@Override public void remove(Request request) throws IOException {
Cache.this.remove(request);
}
@Override public void update(Response cached, Response network) {
Cache.this.update(cached, network);
}
@Override public void trackConditionalCacheHit() {
Cache.this.trackConditionalCacheHit();
}
@Override public void trackResponse(CacheStrategy cacheStrategy) {
Cache.this.trackResponse(cacheStrategy);
}
};
// 硬盤緩存實現
final DiskLruCache cache;
/* read and write statistics, all guarded by 'this' */
int writeSuccessCount;
int writeAbortCount;
private int networkCount;
// 命中次數
private int hitCount;
// 請求次數
private int requestCount;
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
複製代碼
前面在講 LruCache 的時候,既然是緩存就必然伴隨着 插入緩存、更新緩存、刪除緩存、查詢緩存。
先來看下 插入緩存的 put 方法:
CacheRequest put(Response response) {
// 獲取請求方法
String requestMethod = response.request().method();
// 判斷若是是 POST、PATCH、PUT、DELETE、MOVE幾種請求方法,則不支持緩存,移除緩存,返回 null
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
// 若是不是 get 請求,返回 null。好比 post 不必緩存
if (!requestMethod.equals("GET")) {
// Don't cache non-GET responses. We're technically allowed to cache
// HEAD requests and some POST requests, but the complexity of doing
// so is high and the benefit is low.
return null;
}
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
// 生成 Entry 實體,Entry 封裝了全部的返回值信息
Entry entry = new Entry(response);
// 緩存的實際操做,使用DiskLruCache實現硬盤緩存
DiskLruCache.Editor editor = null;
try {
// 建立寫入的 editor。
// key 值就是 key() 方法生成的,md5 加密,而後換算成 16 進制。
// ByteString.encodeUtf8(url.toString()).md5().hex();
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
// 將緩存寫入磁盤,在 writeTo 方法中,將頭部等信息進行緩存,若是是 https 請求,將會作特殊的處理
entry.writeTo(editor);
// 傳入 editor 建立一個 CacheRequestImpl
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
複製代碼
刪除緩存的 remove 方法:
void remove(Request request) throws IOException {
// 這裏調用 DiskLruCache 中的 remove 方法移除緩存
cache.remove(key(request.url()));
}
複製代碼
更新緩存的 update 方法:
void update(Response cached, Response network) {
Entry entry = new Entry(network);
DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;
DiskLruCache.Editor editor = null;
try {
editor = snapshot.edit(); // Returns null if snapshot is not current.
if (editor != null) {
entry.writeTo(editor);
editor.commit();
}
} catch (IOException e) {
abortQuietly(editor);
}
}
複製代碼
這裏是先生成一個實體,而後找到保存的快照,而後用執行寫入
查找緩存的 get 方法:
Response get(Request request) {
//先得到通過 md5 加密後的 url
String key = key(request.url());
// DiskLruCache 的快照
DiskLruCache.Snapshot snapshot;
// 保存緩存的實體
Entry entry;
try {
// 從 DiskLruCache 中的 get 方法,查找到 快照。
snapshot = cache.get(key);
//爲空,則沒緩存
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
// 建立Entry
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
// 從 Entry 照片中得到 Response
Response response = entry.response(snapshot);
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
複製代碼
做用主要是打開與服務器之間的連接,正式開始網絡請求。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
// 拿到 RealInterceptorChain 中建立的 StreamAllocation 對象。
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 經過 newStream 建立一個 HttpCodec 對象,主要用來編碼 Request 和解碼 Response 的
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
// 得到 RealConnection 的對象 connection。 用來進行實際的 io 傳輸
RealConnection connection = streamAllocation.connection();
// 調用下個攔截器
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
//來看下 newStream 方法
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
// 設置超時
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
// 是否重試
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
// 構建 RealConnection 對象
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
// 構建一個 HttpCodec 對象
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
// 看下 findHealthyConnection
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
複製代碼
在 findHealthyConnection 方法 內部調用了 findConnection 方法,在findConnection 方法內部主要是去找有沒有能夠服用的連接
這個攔截器和應用攔截器都是用戶本身設置的攔截器。使用方法可應用攔截器差很少,這裏就不在講了,看下官方對其描述:
Able to operate on intermediate responses like redirects and retries.
Not invoked for cached responses that short-circuit the network.
Observe the data just as it will be transmitted over the network.
Access to the Connection
that carries the request.
可以對重定向和重試的時候進行操做(很好理解,重定向什麼的在第二層,應用攔截器不會調用,可是網絡攔截器能夠調用)。
若是讀取緩存中的數據,就不會執行網絡攔截器
能夠檢測到全部須要網絡傳輸的數據
能夠訪問完整的Request 請求
因爲是在重定向攔截器以後的,因此在發生重定向的時候,網絡攔截器可能會執行屢次,能夠較爲完整的檢測網絡請求的情況。
而應用攔截器在請求重試攔截器以前,因此檢測不到重定向的請求,只能檢測到最初的請求和最終的返回。
CallServerInterceptor攔截器 是攔截器鏈中的最後一個攔截器,主要負責向服務器發送真正的網絡請求和接收服務器返回的響應。
@Override public Response intercept(Chain chain) throws IOException {
// 獲取在 ConnectInterceptor 攔截器中建立的 httpCodec 對象
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
// 獲取 streamAllocation
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
// 向 Socket 中寫入請求的頭部信息
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);
}
// Write the request body, unless an "Expect: 100-continue" expectation failed.
if (responseBuilder == null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
// 向 Socket 中寫入請求的 body 信息
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
}
// 完成網絡請求的寫入工做
httpCodec.finishRequest();
// 下面是讀取操做
if (responseBuilder == null) {
// 讀取響應的頭部信息
responseBuilder = httpCodec.readResponseHeaders(false);
}
// 構建返回的 Response
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();
}
// 若是響應的狀態碼爲 204 和 205 而且響應體不爲空,則拋出異常
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 是一個接口,主要用來編碼 Request 和解碼 Response 的,主要有兩個實現類:
/**A socket connection that can be used to send HTTP/1.1 messages. */
public final class Http1Codec implements HttpCodec {}
/** Encode requests and responses using HTTP/2 frames. */
public final class Http2Codec implements HttpCodec {}
複製代碼
都是如今主流的 Http 協議規範。
CallServerInterceptor攔截器主要就是完成最終的網絡請求工做,遵循 HTTP 協議規範,經過 HttpCodec
對象寫入請求頭、請求主體,而且讀取響應頭和響應主體,寫入 Response 中,依次返回給上級攔截器,最終傳遞到調用的地方,就完成了依次網絡請求。
到這裏 OkHttp 源碼就分析完了,可能有些地方理解的還不是很透徹,繼續加油吧。
歡迎關注個人公衆號: