首先先看一張流程圖,該圖是從拆輪子系列:拆 OkHttp 中盜來的,以下:
在上一篇博客深刻理解OkHttp源碼(一)——提交請求中介紹到了getResponseWithInterceptorChain()方法,本篇主要從這兒繼續往下講解。前端
private Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); //添加應用攔截器 interceptors.addAll(client.interceptors()); //添加劇試和重定向攔截器 interceptors.add(retryAndFollowUpInterceptor); //添加轉換攔截器 interceptors.add(new BridgeInterceptor(client.cookieJar())); //添加緩存攔截器 interceptors.add(new CacheInterceptor(client.internalCache())); //添加鏈接攔截器 interceptors.add(new ConnectInterceptor(client)); //添加網絡攔截器 if (!retryAndFollowUpInterceptor.isForWebSocket()) { interceptors.addAll(client.networkInterceptors()); } //添加網絡攔截器 interceptors.add(new CallServerInterceptor( retryAndFollowUpInterceptor.isForWebSocket())); //生成攔截器鏈 Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest); }
從上面的代碼能夠看出,首先調用OkHttpClient的interceptor()方法獲取全部應用攔截器,而後再加上RetryAndFollwoUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、若是不是WebSocket,還須要加上OkHttpClient的網絡攔截器,最後再加上CallServerInterceptor,而後構造一個RealInterceptorChain對象,該類是攔截器鏈的具體實現,攜帶整個攔截器鏈,包含全部應用攔截器、OkHttp核心、全部網絡攔截器和最終的網絡調用者。
OkHttp的這種攔截器鏈採用的是責任鏈模式,這樣的好處是將請求的發送和處理分開,而且能夠動態添加中間的處理方實現對請求的處理、短路等操做。 java
下面是RealInterceptorChain的定義,該類實現了Chain接口,在getResponseWithInterceptorChain調用時好幾個參數都傳的null,具體是StreamAllocation、HttpStream和Connection;其他的參數中index表明當前攔截器列表中的攔截器的索引。緩存
/** * 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 HttpStream httpStream; private final Connection connection; private final int index; private final Request request; private int calls; public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation, HttpStream httpStream, Connection connection, int index, Request request) { this.interceptors = interceptors; this.connection = connection; this.streamAllocation = streamAllocation; this.httpStream = httpStream; this.index = index; this.request = request; } @Override public Connection connection() { return connection; } public StreamAllocation streamAllocation() { return streamAllocation; } public HttpStream httpStream() { return httpStream; } @Override public Request request() { return request; } @Override public Response proceed(Request request) throws IOException { return proceed(request, streamAllocation, httpStream, connection); } public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream, Connection 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.httpStream != null && !sameConnection(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.httpStream != 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, httpStream, 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 (httpStream != 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; } private boolean sameConnection(HttpUrl url) { return url.host().equals(connection.route().address().url().host()) && url.port() == connection.route().address().url().port(); } }
主要看proceed方法,該方法是具體根據請求獲取響應的實現。由於一開始httpStream爲null,因此前面的判斷都無效,直接進入第92行,首先建立next攔截器鏈,主須要把索引置爲index+1便可;而後獲取第一個攔截器,調用其intercept方法。
如今假設沒有添加應用攔截器和網絡攔截器,那麼這第一個攔截器將會是RetryAndFollowUpInterceptor。 服務器
RetryAndFollowUpInterceptor攔截器會從錯誤中恢復以及重定向。若是Call被取消了,那麼將會拋出IoException。下面是其intercept方法實現:cookie
@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); streamAllocation = new StreamAllocation( client.connectionPool(), createAddress(request.url())); int followUpCount = 0; Response priorResponse = null; while (true) { //若是取消了,那麼釋放流以及拋出異常 if (canceled) { streamAllocation.release(); throw new IOException("Canceled"); } Response response = null; boolean releaseConnection = true; try { //調用攔截器鏈餘下的獲得響應 response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { // The attempt to connect via a route failed. The request will not have been sent. if (!recover(e.getLastConnectException(), true, request)) throw e.getLastConnectException(); releaseConnection = false; continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. if (!recover(e, false, request)) throw e; releaseConnection = false; continue; } finally { // We're throwing an unchecked exception. Release any resources. if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } // Attach the prior response if it exists. Such responses never have a body. if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } //獲得重定向請求 Request followUp = followUpRequest(response); //若是不存在重定向請求,直接返回響應 if (followUp == null) { if (!forWebSocket) { streamAllocation.release(); } return response; } closeQuietly(response.body()); if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } //判斷重定向請求和前一個請求是不是同一個主機,若是是的話,能夠共用一個鏈接,不然須要從新建立鏈接 if (!sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation( client.connectionPool(), createAddress(followUp.url())); } else if (streamAllocation.stream() != null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } request = followUp; priorResponse = response; } }
從上面的代碼能夠看出,建立了streamAllocation對象,streamAllocation負責爲鏈接分配流,接下來調用傳進來的chain參數繼續獲取響應,能夠看到若是獲取失敗了,在各個異常中都會調用recover方法嘗試恢復請求,從響應中取出followUp請求,若是有就檢查followUpCount,若是符合要求而且有followUp請求,那麼須要繼續進入while循環,若是沒有,則直接返回響應了。首先不考慮有後續請求的狀況,那麼接下來調用的將會是BridgeInterceptor。網絡
BridgeInterceptor從用戶的請求構建網絡請求,而後提交給網絡,最後從網絡響應中提取出用戶響應。從最上面的圖能夠看出,BridgeInterceptor實現了適配的功能。下面是其intercept方法:app
@Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); //若是存在請求主體部分,那麼須要添加Content-Type、Content-Length首部 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)); } //OkHttp默認使用HTTP持久鏈接 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) { 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(); }
從上面的代碼能夠看出,首先獲取原請求,而後在請求中添加頭,好比Host、Connection、Accept-Encoding參數等,而後根據看是否須要填充Cookie,在對原始請求作出處理後,使用chain的procced方法獲得響應,接下來對響應作處理獲得用戶響應,最後返回響應。接下來再看下一個攔截器CacheInterceptor的處理。異步
CacheInterceptor嘗試從緩存中獲取響應,若是能夠獲取到,則直接返回;不然將進行網絡操做獲取響應。CacheInterceptor使用OkHttpClient的internalCache方法的返回值做爲參數。下面先看internalCache方法:ide
InternalCache internalCache() {
return cache != null ? cache.internalCache : internalCache; }
而Cache和InternalCache都是OkHttpClient.Builder中能夠設置的,而其設置會互相抵消,代碼以下:ui
/** Sets the response cache to be used to read and write cached responses. */ void setInternalCache(InternalCache internalCache) { this.internalCache = internalCache; this.cache = null; } public Builder cache(Cache cache) { this.cache = cache; this.internalCache = null; return this; }
默認的,若是沒有對Builder進行緩存設置,那麼cache和internalCache都爲null,那麼傳入到CacheInterceptor中的也是null,下面是CacheInterceptor的intercept方法:
@Override 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. } //不適用網絡響應,可是緩存中沒有緩存響應,返回504錯誤 // 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(EMPTY_BODY) .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 (validate(cacheResponse, networkResponse)) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (HttpHeaders.hasBody(response)) { CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache); response = cacheWritingResponse(cacheRequest, response); } return response; }
從上面的代碼能夠看出,首先嚐試從緩存中根據請求取出相應,而後建立CacheStrategy對象,該對象有兩個字段networkRequest和cahceResponse,其中networkRequest不爲null則表示須要進行網絡請求,cacheResponse表示返回的或須要更新的緩存響應,爲null則表示請求沒有使用緩存。下面是對這兩個字段的不一樣取值返回不一樣的響應:
1. networkRequest\==null&& cacheResponse==null:表示該請求不須要使用網絡可是緩存響應不存在,則返回504錯誤的響應;
2. networkRequest\==null&&cacheRequest!=null:表示該請求不容許使用網絡,可是由於有緩存響應的存在,因此直接返回緩存響應
3. networkRequest!=null:表示該請求強制使用網絡,則調用攔截器鏈中其他的攔截器繼續處理獲得networkResponse,獲得網絡響應後,又分爲兩種狀況處理:
1)cacheResponse!=null: 緩存響應以前存在,若是以前的緩存還有效的話,那麼須要更新緩存,返回組合後的響應
2)cacheResponse==null: 以前沒有緩存響應,則將組合後的響應直接寫入緩存便可。
下面繼續看若是networkRequest爲null的狀況,那麼須要繼續調用攔截器鏈,那麼下一個攔截器是ConnectInterceptor。
打開一個到目標服務器的鏈接。intercept方法的實現以下:
public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); //建立具體的Socket鏈接,傳入proceed中的後兩個參數再也不爲null // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = !request.method().equals("GET"); HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpStream, connection); }
在RetryAndFollowUpInterceptor中,建立了StreamAllocation並將其傳給了後面的攔截器鏈,因此這兒獲得的StreamAllocation就是那時傳入的,接下來是獲取HttpStream對象以及RealConnection對象,而後繼續交給下面的攔截器處理,至此,下一個攔截器中proceed中的後三個參數均不爲null了。其中HttpStream接口能夠認爲是該鏈接的輸入輸出流,能夠從中讀響應,也能夠寫請求數據。
在看最後一個攔截器以前,咱們再看一次RealInterceptorChain的proceed方法,由於此時的HttpStream和Connection均不爲null。下面是proceed方法的實現:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream, Connection 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.httpStream != null && !sameConnection(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.httpStream != 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, httpStream, 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 (httpStream != 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; }
從上面的代碼能夠能夠看出,調用sameConnection方法比較這是請求的URL與初始的是否相同,若是不一樣,則直接異常,由於相同的主機和端口對應的鏈接能夠重用,而ConnectInterceptor已經建立好了Connection,因此這時若是URL主機和端口不匹配的話,不會再建立新的Connection而是直接拋出異常。這就說明網絡攔截器中不能夠將請求修改爲與原始請求不一樣的主機和端口,不然就會拋出異常。其次,每一個網絡攔截器只能調用一次proceed方法,若是調用兩次或以上次數,就會拋出異常。
在處理完網絡攔截器後,會調用最後一個攔截器CallServerInterceptor。
CallServerInterceptor是攔截器鏈中最後一個攔截器,負責將網絡請求提交給服務器。它的intercept方法實現以下:
@Override public Response intercept(Chain chain) throws IOException {
HttpStream httpStream = ((RealInterceptorChain) chain).httpStream(); StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation(); Request request = chain.request(); long sentRequestMillis = System.currentTimeMillis(); //將請求頭部信息寫出 httpStream.writeRequestHeaders(request); //判斷是否須要將請求主體部分寫出 if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength()); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); } httpStream.finishRequest(); //讀取響應首部 Response response = httpStream.readResponseHeaders() .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); if (!forWebSocket || response.code() != 101) { response = response.newBuilder() .body(httpStream.openResponseBody(response)) .build(); } //若是服務端不支持持久鏈接 if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } int code = response.code(); if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); } return response; }
從上面的代碼中能夠看出,首先獲取HttpStream對象,而後調用writeRequestHeaders方法寫入請求的頭部,而後判斷是否須要寫入請求的body部分,最後調用finishRequest()方法將全部數據刷新給底層的Socket,接下來嘗試調用readResponseHeaders()方法讀取響應的頭部,而後再調用openResponseBody()方法獲得響應的body部分,最後返回響應。
能夠看到CallServerInterceptor完成了最終的發送請求和接受響應。至此,整個攔截器鏈就分析完了,而獲得原始響應後,前面的攔截器又分別作了不一樣的處理,ConnectInterceptor沒有對響應進入處理,CacheInterceptor根據請求的緩存控制判斷是否須要將響應放入緩存或更新緩存,BridgeInterceptor將響應去除部分頭部信息獲得用戶的響應,RetryAndFollowUpInterceptor根據響應中是否須要重定向判斷是否須要進行新一輪的請求。
在這邊咱們須要明白一點,OkHttp的底層是經過Java的Socket發送HTTP請求與接受響應的(這也好理解,HTTP就是基於TCP協議的),可是OkHttp實現了鏈接池的概念,即對於同一主機的多個請求,其實能夠公用一個Socket鏈接,而不是每次發送完HTTP請求就關閉底層的Socket,這樣就實現了鏈接池的概念。而OkHttp對Socket的讀寫操做使用的OkIo庫進行了一層封裝。
在上面分析完OkHttp默認的整個攔截器鏈的工做流程後,再來看若是添加了應用攔截器或網絡攔截器後,是怎樣的一個效果?
在上面的代碼分析中,應用攔截器是位於RetryAndFollowUpInterceptor以前,即攔截器鏈的最前端;網絡攔截器位於CallServerInterceptor以前,即正在進行網絡以前。因此能夠更深刻地理解使用OkHttp進行網絡同步和異步操做中應用攔截器和網絡攔截器的區別,這兒再詳細解釋一下。
攔截器在攔截器鏈中位置越靠前,那麼對請求的處理是越靠前,可是對響應的處理確實靠後的,明白這一點,那麼進行攔截器鏈的分析就會簡單不少