OkHttp源码解析(上)
導語
學過Android開發的同學都知道,我們使用網絡請求獲取服務器的數據時,通常使用的是封裝好的Retrofit框架,這個框架很好的幫助我們對網絡的發起,以及返回的數據進行操作,我們使用起來十分方便,對于Retrofit來說,我們僅僅看到了它的表面,如何正確使用等,其內部還是要借助OkHtttp來請求網絡的,Retrofit只是對OkHttp進行了再次的封裝,而且Retrofit不具備網絡請求功能,只是在OkHtttp的外表又套了一層,對返回的數據支持RxJava轉換和Gson解析。真正起到網絡請求的還是OkHttp,所以要了解里面的實質,我們還是著手從OkHttp出發,來探索它對網絡的認知和對數據的傳遞。
OkHttp使用方式
要想了解其原理,首先得學會使用它,OkHttp的使用也非常簡單。
從請求方式來講,分為 get 和 post
get請求的同步和異步
// 構建一個OkHttpClient對象 OkHttpClient client = new OkHttpClient.Builder().build();// 創建一個Request請求對象 Request request = new Request.Builder().get().url("https://www.baidu.com/").build();// 把request對象 通過 newCall 轉換成call Call call = client.newCall(request);try {// 通過call來發起網絡請求// 同步請求Response response = call.execute();//返回響應體ResponseBody body = response.body();System.out.println(body.string()); } catch (IOException e) {e.printStackTrace(); }// 通過call來發起網絡請求 // 異步請求 call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {// 返回響應體ResponseBody body = response.body();System.out.println(body.string());} });上面的步驟也非常清楚:
post請求的同步和異步
OkHttpClient client = new OkHttpClient.Builder().build();// 表單格式構建 RequestBody RequestBody requestBody = new FormBody.Builder().add("username", "admin").add("password", "123456").build();Request request = new Request.Builder().post(requestBody).url("https://www.baidu.com/").build();Call call = client.newCall(request); try {Response response = call.execute();ResponseBody responseBody = response.body();System.out.println(responseBody.string()); } catch (IOException e) {e.printStackTrace(); }call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {ResponseBody responseBody = response.body();System.out.println(responseBody.string());} });post請求與get請求的唯一區別就是:post請求通過RequestBody來構建一個請求體,Body里面帶上我們要請求的參數;而get請求的參數是拼接在url后面。
了解了OkHttp的使用,我們梳理下整個OkHttp的調用過程。
不管我們通過execute還是enqueue,都會經過Dispatcher(分發器)和 Interceptors(攔截器)來獲得Response。
分發器到底做了什么事情,接下來我們深入地了解分發器內部的原理。
分發器—Dispatcher
我們從上面的代碼中知道,真正請求網絡的是一個call對象,call是一個接口,通過RealCall實現,我們通過newCall(request)構建的這個call,通過execute或enqueue就能獲得response,為何,進入execute看下:
@Override public Response execute() throws IOException {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}captureCallStackTrace();eventListener.callStart(this);try {client.dispatcher().executed(this);Response result = getResponseWithInterceptorChain();if (result == null) throw new IOException("Canceled");return result;} catch (IOException e) {eventListener.callFailed(this, e);throw e;} finally {client.dispatcher().finished(this);} }可以看到,調用了分發器的executed,然后通過getResponseWithInterceptorChain()獲得響應。
synchronized void executed(RealCall call) {runningSyncCalls.add(call); }將call加入running隊列。
對于同步請求,分發器并沒有做什么事情。我們分析下異步請求的分發器干了什么事情。
@Override public void enqueue(Callback responseCallback) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}captureCallStackTrace();eventListener.callStart(this);client.dispatcher().enqueue(new AsyncCall(responseCallback)); }調用了分發器的enqueue,注意這里:分發器將Callback包裝成AsyncCall來傳給了enqueue。AsyncCall繼承自NamedRunnable,而NamedRunnable又實現了Runnable,所以AsyncCall本質上就是一個Runnable。AsyncCall交給了分發器:
synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);} } private int maxRequests = 64; private int maxRequestsPerHost = 5;這里通過兩個條件判斷:
正在執行的running隊列里面的任務數小于最大請求數(64)以及同一Host的請求數小于最大同一Host請求數(5)時,將call加入running隊列,然后交給線程池處理。否則,將AsyncCall加入ready隊列。
分別解釋下這兩個條件:
如果把任務放在ready隊列后,這個隊列里的任務怎么執行,什么時候執行?
前面說到,正在執行的任務會交給線程池處理,當線程池處理完之后,會finish掉這個任務。由于AsyncCall本質上就是一個Runnable,所以會調用run方法,而run方法里面又調用了execute方法,execute方法是一個抽象方法,所以在分發器里實現如下:
public abstract class NamedRunnable implements Runnable {protected final String name;public NamedRunnable(String format, Object... args) {this.name = Util.format(format, args);}@Override public final void run() {String oldName = Thread.currentThread().getName();Thread.currentThread().setName(name);try {execute();} finally {Thread.currentThread().setName(oldName);}}protected abstract void execute(); } @Override protected void execute() {boolean signalledCallback = false;try {// 執行請求(攔截器)Response response = getResponseWithInterceptorChain();if (retryAndFollowUpInterceptor.isCanceled()) {signalledCallback = true;responseCallback.onFailure(RealCall.this, new IOException("Canceled"));} else {signalledCallback = true;responseCallback.onResponse(RealCall.this, response);}} catch (IOException e) {if (signalledCallback) {// Do not signal the callback twice!Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);} else {eventListener.callFailed(RealCall.this, e);responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);} }我們注意一點:無論請求成功還是失敗,都會執行finally里的代碼,(看這個try…catch代碼塊),在finally里會調用分發器的finished方法:
void finished(AsyncCall call) {finished(runningAsyncCalls, call, true); } private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {int runningCallsCount;Runnable idleCallback;synchronized (this) {if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");if (promoteCalls) promoteCalls();runningCallsCount = runningCallsCount();idleCallback = this.idleCallback;}if (runningCallsCount == 0 && idleCallback != null) {idleCallback.run();} }到這里,我們分析下這個方法:
記錄這兩個參數:calls就是runningAsyncCalls,promoteCalls是true
首先執行calls.remove(call),說明這個call完成了,從隊列里面移除,然后調用promoteCalls()方法。
private void promoteCalls() {if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {AsyncCall call = i.next();if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call);executorService().execute(call);}if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.} }來看promoteCalls,這個方法首先做兩個判斷,如果running隊列任務數大于maxRequests,直接返回;如果ready隊列為空,直接返回。
接下來,開始從ready執行隊列里遍歷任務,通過next()取到下一個任務,再對這個任務進行判斷,如果同一Host請求數已經有5個了,那就不會從ready隊列取出任務到running隊列,否則,從ready隊列取出任務放入running隊列,交給線程池,同時移除掉ready隊列的任務。
用流程圖看下分發器的流程:
講到這里,我們說一下OkHttp里用到的線程池。
線程池
線程池的工作原理
當一個任務通過execute(Runnable)方法添加到線程池時:
OkHttp如何創建線程池
前面我們說到,running隊列的任務直接交給線程池處理,那我們看下線程池是如何處理這么多任務的。
OkHttp通過executorService()構建一個線程池
public synchronized ExecutorService executorService() {if (executorService == null) {executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));}return executorService; }帶著兩個問題分析:
首先看第一個問題:
我們知道,構建線程池的幾大參數是:核心線程數、最大線程數、線程存活時間、存活時間單位、阻塞隊列、線程工廠。
對應到這個線程池中:核心線程數為0,最大線程數為0x7fffffff(2147483647),存活時間是60s,阻塞隊列使用了SynchronousQueue。
如果了解線程池的工作原理的話,這個線程池沒有核心線程數,來一個任務加入隊列,那么看看這個隊列:SynchronousQueue
這是一個沒有容量的雙端隊列,說明一個問題,這個隊列里存放不了任務,而最大線程數是如此的龐大,那么,來一個任務就會立馬新建線程來執行,但是并不是每一個任務都會新建線程,線程有60s的存活時間,如果這個線程執行完任務后,下一個任務來時,就會復用線程,所以這樣設計,就是為了提高執行效率,這是一個高并發,最大吞吐量的線程池。
第二個問題:
OkHttp這么設計,就是為了能夠最大限度地執行任務請求,任務無需等待,立馬執行。
攔截器—Interceptors
上面分析到,分發器會將任務交給線程池處理,然后調用getResponseWithInterceptorChain()獲取響應結果,這個方法字面意思就是:通過攔截器責任鏈獲取響應,看一下這個方法:
Response result = getResponseWithInterceptorChain(); Response getResponseWithInterceptorChain() throws IOException {// Build a full stack of interceptors.List<Interceptor> interceptors = new ArrayList<>();interceptors.addAll(client.interceptors()); // 自定義攔截器加入到集合interceptors.add(retryAndFollowUpInterceptor); // 重試重定向攔截器interceptors.add(new BridgeInterceptor(client.cookieJar())); // 橋接攔截器interceptors.add(new CacheInterceptor(client.internalCache())); // 緩存攔截器interceptors.add(new ConnectInterceptor(client)); // 連接攔截器if (!forWebSocket) { // 如果不是webSocket,將自定義的網絡攔截器添加進去interceptors.addAll(client.networkInterceptors()); // 網絡攔截器}interceptors.add(new CallServerInterceptor(forWebSocket)); // 服務器通訊攔截器Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,originalRequest, this, eventListener, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());return chain.proceed(originalRequest); }這個方法里,創建一個存放Interceptor的List集合,首先通過addAll方式將攔截器添加進list集合中,這里的攔截器是我們自定義的攔截器。以及下面的client.networkInterceptors()。
/*** 自定義攔截器*/ public class MyInterceptor implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();RealInterceptorChain realChain = (RealInterceptorChain) chain;return realChain.proceed(request);} } OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new MyInterceptor()).addInterceptor(new MyInterceptor()).addInterceptor(new MyInterceptor()).addNetworkInterceptor(new MyInterceptor()).build();如果沒有自定義攔截器,那么這個集合就是null。
接下來添加retryAndFollowUpInterceptor(重試重定向攔截器)、 BridgeInterceptor(橋接攔截器)、CacheInterceptor(緩存攔截器)、ConnectInterceptor(連接攔截器)、CallServerInterceptor(服務器通訊攔截器),一共有五大攔截器,這些攔截器是怎么一個一個執行起來,幫助我們完成整個請求過程,這里就涉及到一個設計模式——責任鏈模式
責任鏈模式
責任鏈模式是一種對象行為型模式,為請求創建了一個接收者對象的鏈,在處理請求的時候執行過濾(各司其職)。
責任鏈上的處理者負責處理請求,客戶只需要將請求發送到責任鏈即可,無須關心請求的處理細節和請求的傳遞,所以責任鏈將請求的發送者和請求的處理者解耦了。
文字描述理解起來確定有點困難,我們通過代碼來看一下什么是責任鏈。
首先定義一個攔截器接口,五大攔截器分別實現這個接口,定義一個Chain鏈條實體。
第一個攔截器:RetryAndFollowUpInterceptor:
public class RetryAndFollowUpInterceptor implements Interceptor {@Overridepublic String intercept(Chain chain) {System.out.println("開始執行重試重定向攔截器");String result = chain.proceed(chain.request + "===>經過重試重定向攔截器");System.out.println("結束執行重試重定向攔截器");return result + "===>經過重試重定向攔截器";} }最后一個攔截器:CallServerInterceptor:
public class CallServerInterceptor implements Interceptor {@Overridepublic String intercept(Chain chain) {System.out.println("開始執行服務器通訊攔截器");System.out.println("===發起請求===");System.out.println("結束執行服務器通訊攔截器");return chain.request + "===>經過請求服務器攔截器\nOkHttp響應===>經過請求服務器攔截器";} }Chain:
public class Chain {private List<Interceptor> interceptors;private int index;public String request;public Chain(List<Interceptor> interceptors, int index, String request) {this.interceptors = interceptors;this.index = index;this.request = request;}public Chain(List<Interceptor> interceptors, int index) {this.interceptors = interceptors;this.index = index;}public String proceed(String request) {if (index >= interceptors.size()) {throw new AssertionError();}Chain chain = new Chain(interceptors, index + 1, request);Interceptor interceptor = interceptors.get(index);return interceptor.intercept(chain);} } public static void main(String[] args) {List<Interceptor> interceptors = new ArrayList<>();interceptors.add(new RetryAndFollowUpInterceptor());interceptors.add(new BridgeInterceptor());interceptors.add(new CacheInterceptor());interceptors.add(new ConnectInterceptor());interceptors.add(new CallServerInterceptor());Chain chain = new Chain(interceptors, 0);System.out.println(chain.proceed("OkHttp請求"));}首先將五大攔截器加入List集合中,創建一個Chain對象,將List集合加入鏈條,并指向第一個攔截器,開始執行proceed,調用Chain的proceed,new一個新的Chain,這時index + 1, 說明新的鏈條指向第二個攔截器,拿到index對應的攔截器,執行intercept方法,在intercept方法中,通過新鏈條的index執行第二個攔截器的intercept,以此類推,這樣就像工廠流水線一樣,一個工序一個工序流下去,而且每個工序在傳遞給下一個工序之前,還能做自己的事情,互相不影響,這就是所謂的責任鏈??聪逻@個過程的打印結果:
OkHttp請求===>經過重試重定向攔截器 ===>經過橋接攔截器 ===>經過緩存攔截器 ===>經過連接攔截器===>經過請求服務器攔截器 OkHttp響應===>經過請求服務器攔截器===>經過連接攔截器===>經過緩存攔截器===>經過橋接攔截器===>經過重試重定向攔截器拿到響應后,再根據對應的攔截器反向傳輸回來,類似一個U型流向。
了解了責任鏈模式后,我們分析下這五大默認攔截器。
重試重定向攔截器
第一個攔截器:RetryAndFollowUpInterceptor,主要就是完成兩件事情:重試和重定向。
重試
@Override public Response intercept(Chain chain) throws IOException {Request request = chain.request();RealInterceptorChain realChain = (RealInterceptorChain) chain;Call call = realChain.call();EventListener eventListener = realChain.eventListener();streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),call, eventListener, callStackTrace);int followUpCount = 0;Response priorResponse = null;while (true) {if (canceled) {streamAllocation.release();throw new IOException("Canceled");}Response response;boolean releaseConnection = true;try {response = realChain.proceed(request, streamAllocation, null, null);releaseConnection = false;} catch (RouteException e) {// The attempt to connect via a route failed. The request will not have been sent.if (!recover(e.getLastConnectException(), false, request)) {throw e.getLastConnectException();}releaseConnection = false;continue;} catch (IOException e) {// An attempt to communicate with a server failed. The request may have been sent.boolean requestSendStarted = !(e instanceof ConnectionShutdownException);if (!recover(e, requestSendStarted, request)) throw e;releaseConnection = false;continue;} finally {// We're throwing an unchecked exception. Release any resources.if (releaseConnection) {streamAllocation.streamFailed(null);streamAllocation.release();}}// Attach the prior response if it exists. Such responses never have a body.if (priorResponse != null) {response = response.newBuilder().priorResponse(priorResponse.newBuilder().body(null).build()).build();}Request followUp;try {followUp = followUpRequest(response, streamAllocation.route());} catch (IOException e) {streamAllocation.release();throw e;}if (followUp == null) {streamAllocation.release();return response;}closeQuietly(response.body());// 限制重定向最大次數為20次if (++followUpCount > MAX_FOLLOW_UPS) {streamAllocation.release();throw new ProtocolException("Too many follow-up requests: " + followUpCount);}if (followUp.body() instanceof UnrepeatableRequestBody) {streamAllocation.release();throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());}// 判斷是不是可以復用同一份連接if (!sameConnection(response, followUp.url())) {streamAllocation.release();streamAllocation = new StreamAllocation(client.connectionPool(),createAddress(followUp.url()), call, eventListener, callStackTrace);this.streamAllocation = streamAllocation;} 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;} }首先在一個while(true)的死循環里,通過各種try…catch來分別處理不同類型的異常。
首先定義了一個布爾型releaseConnection變量,默認為true,在try語句塊里:
response = realChain.proceed(request, streamAllocation, null, null); releaseConnection = false;如果請求沒有異常,責任鏈將事件分發到下一個攔截器,releaseConnection置為false。
如果請求階段發生了 RouteException 或者 IOException會進行判斷是否重新發起請求。
RouteException
catch (RouteException e) {// 路由異常,連接未成功,請求還沒發出去。if (!recover(e.getLastConnectException(), false, request)) {throw e.getLastConnectException();}releaseConnection = false;continue; }IOException
catch (IOException e) {// 請求發出去了,但是和服務器通信失敗了。(socket流正在讀寫數據的時候斷開連接)// HTTP2才會拋出ConnectionShutdownException。所以對于HTTP1 requestSendStarted一定是trueboolean requestSendStarted = !(e instanceof ConnectionShutdownException);if (!recover(e, requestSendStarted, request)) throw e;releaseConnection = false;continue; }兩個方法都是通過recover方法判斷是否能夠進行重試,如果返回true,則表示允許重試。進入recover方法看下:
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {streamAllocation.streamFailed(e);// 1if (!client.retryOnConnectionFailure()) return false;// 2if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;// 3if (!isRecoverable(e, requestSendStarted)) return false;// 4if (!streamAllocation.hasMoreRoutes()) return false;// For failure recovery, use the same route selector with a new connection.return true; }分別看下這幾個if語句:
在配置OkhttpClient是設置了不允許重試(默認允許),則一旦發生請求失敗就不再重試。這個就是在我們最開始配置OkhttpClient時,如果手動配置了fasle,則不允許重試。
這個判斷在RouteException下是永遠不成立的,所以我們看IOException的情況,在IOException里面:
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);前面我們分析到,HTTP2才會拋出ConnectionShutdownException。所以對于HTTP1,requestSendStarted一定是true,所以主要看第二個條件,userRequest.body() instanceof UnrepeatableRequestBody,UnrepeatableRequestBody是一個接口,這個接口的作用就是打個標簽,如果我們的請求體標記成這種類型,那就表示這個請求攔截器不會幫你重試。
public class MyRequestBody extends RequestBody implements UnrepeatableRequestBody {@Nullable@Overridepublic MediaType contentType() {return null;}@Overridepublic void writeTo(BufferedSink sink) throws IOException {} }就像上面這樣,自定義MyRequestBody,并且實現了UnrepeatableRequestBody這個接口,如果請求的是我們自定義的請求體,那么OkHttp對這次請求失敗后,判斷到你的請求體實現了這個接口,那他就不會幫你重試。
判斷是不是屬于重試的異常,這個里面調用了isRecoverable方法。如果這個方法返回了false,那么也不會重試,什么情況下返回false呢,我們看一下:
private boolean isRecoverable(IOException e, boolean requestSendStarted) {// 如果是協議異常,返回falseif (e instanceof ProtocolException) {return false;}if (e instanceof InterruptedIOException) {return e instanceof SocketTimeoutException && !requestSendStarted;}if (e instanceof SSLHandshakeException) {if (e.getCause() instanceof CertificateException) {return false;}}if (e instanceof SSLPeerUnverifiedException) {// e.g. a certificate pinning error.return false;}return true; }如果是協議的異常,則返回false,什么情況下是協議的異常,我們看下源碼:在CallServerInterceptor中,有一段這樣的代碼:
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {throw new ProtocolException("HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); }如果code是204或者205(204表示服務器沒有返回內容,205表示服務器讓你重置內容,也就是刷新網頁。),這種情況下,是沒有響應體的,但是第二個條件是響應體長度大于0,這就沖突了,這種沖突是服務器的問題,就會拋出ProtocolException。
如果異常屬于IO異常,同時又屬于SocketTimeoutException,那OkHttp就會幫你重試。例如:網絡波動造成了Socket連接的超時,可以使用不同路線重試。
SSL證書異常/SSL驗證失敗異常,則不會重試。前者是證書驗證失敗,后者可能就是壓根就沒證書,或者證書數據不正確。
如果SSL握手未授權異常,也不能重試。
經過了異常的判定之后,如果仍然允許進行重試,就會再檢查當前有沒有可用路由路線來進行連接。簡單來說,比如 DNS 對域名解 析后可能會返回多個 IP,在一個IP失敗后,嘗試另一個IP進行重試。
重定向
如果請求結束后沒有發生異常并不代表當前獲得的響應就是最終需要給用戶的,還需要進一步來判斷是否需要重定向,重定向的判斷在followUpRequest方法中。
private Request followUpRequest(Response userResponse, Route route) throws IOException {if (userResponse == null) throw new IllegalStateException();int responseCode = userResponse.code();final String method = userResponse.request().method();switch (responseCode) {// 407 客戶端使用了HTTP代理服務器,在請求頭中添加 “Proxy-Authorization”,讓代理服務器授權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 需要身份驗證 有些服務器接口需要驗證使用者身份 在請求頭中添加 “Authorization”case HTTP_UNAUTHORIZED:return client.authenticator().authenticate(route, userResponse);// 308 永久重定向// 307 臨時重定向case HTTP_PERM_REDIRECT:case HTTP_TEMP_REDIRECT:// 如果請求方式不是GET或者HEAD,框架不會自動重定向請求if (!method.equals("GET") && !method.equals("HEAD")) {return null;}// 300 301 302 303case HTTP_MULT_CHOICE:case HTTP_MOVED_PERM:case HTTP_MOVED_TEMP:case HTTP_SEE_OTHER:// 如果用戶不允許重定向,那就返回nullif (!client.followRedirects()) return null;// 從響應頭取出locationString location = userResponse.header("Location");if (location == null) return null;// 根據location 配置新的請求 urlHttpUrl url = userResponse.request().url().resolve(location);// 如果為null,說明協議有問題,取不出來HttpUrl,那就返回null,不進行重定向if (url == null) return null;// 如果重定向在http到https之間切換,需要檢查用戶是不是允許(默認允許)boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());if (!sameScheme && !client.followSslRedirects()) return null;// Most redirects don't include a request body.Request.Builder requestBuilder = userResponse.request().newBuilder();/*** 重定向請求中 只要不是 PROPFIND 請求,無論是POST還是其他的方法都要改為GET請求方式,* 即只有 PROPFIND 請求才能有請求體*///請求不是get與headif (HttpMethod.permitsRequestBody(method)) {final boolean maintainBody = HttpMethod.redirectsWithBody(method);// 除了 PROPFIND 請求之外都改成GET請求if (HttpMethod.redirectsToGet(method)) {requestBuilder.method("GET", null);} else {RequestBody requestBody = maintainBody ? userResponse.request().body() : null;requestBuilder.method(method, requestBody);}// 不是 PROPFIND 的請求,把請求頭中關于請求體的數據刪掉if (!maintainBody) {requestBuilder.removeHeader("Transfer-Encoding");requestBuilder.removeHeader("Content-Length");requestBuilder.removeHeader("Content-Type");}}// 在跨主機重定向時,刪除身份驗證請求頭if (!sameConnection(userResponse, url)) {requestBuilder.removeHeader("Authorization");}return requestBuilder.url(url).build();// 408 客戶端請求超時case HTTP_CLIENT_TIMEOUT:// 408 算是連接失敗了,所以判斷用戶是不是允許重試if (!client.retryOnConnectionFailure()) {// The application layer has directed us not to retry the request.return null;}// UnrepeatableRequestBody實際并沒發現有其他地方用到if (userResponse.request().body() instanceof UnrepeatableRequestBody) {return null;} // 如果是本身這次的響應就是重新請求的產物同時上一次之所以重請求還是因為408,那我們這次不再重請求 了if (userResponse.priorResponse() != null&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {// We attempted to retry and got another timeout. Give up.return null;}return userResponse.request();// 503 服務不可用 和408差不多,但是只在服務器告訴你 Retry-After:0(意思就是立即重試) 才重請求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 delayreturn userResponse.request();}return null;default:return null;} }重定向的主要過程分析都在代理中注釋了,主要注意的一點是:followup在攔截器中定義的最大次數是20次。
總結
本攔截器是整個責任鏈上的第一環,這意味著它會是首次接觸到Request與最后接收到Response的角色,在這個攔截器中主要的功能就是判斷是否需要重試與重定向。
重試的前提是出現了 RouteException 或者 IOException,一旦在后續的攔截器執行過程中出現了這兩個異常,就會通過recover方法進行判斷是否進行連接重試。
重定向發生在重試的判定之后,如果不滿足重試的條件,還需要進一步調用followUpRequest根據Response的響應碼(當然,如果直接請求失敗, Response 都不存在就會拋出異常),來進行不同的重定向操作。注意:followup 最大發生20次。
橋接攔截器
BridgeInterceptor ,連接應用程序和服務器的橋梁,我們發出的請求將會經過它的處理才能發給服務器,比如設置請求內容長度,編碼,gzip壓縮,cookie等,獲取響應后保存Cookie等操作。這個攔截器相對比較簡單。
// 為我們補全請求頭,并默認使用gzip壓縮,同時將響應體同時設置為gzip讀取。 @Override public Response intercept(Chain chain) throws IOException {Request userRequest = chain.request();// 創建新的requestBuilderRequest.Builder requestBuilder = userRequest.newBuilder();RequestBody body = userRequest.body();if (body != null) {MediaType contentType = body.contentType();if (contentType != null) {requestBuilder.header("Content-Type", contentType.toString());}long contentLength = body.contentLength();if (contentLength != -1) {requestBuilder.header("Content-Length", Long.toString(contentLength));requestBuilder.removeHeader("Transfer-Encoding");} else {requestBuilder.header("Transfer-Encoding", "chunked");requestBuilder.removeHeader("Content-Length");}}if (userRequest.header("Host") == null) {requestBuilder.header("Host", hostHeader(userRequest.url(), false));}if (userRequest.header("Connection") == null) {// 默認建立長連接,如果我們不想與服務器建立長連接,value改為Close。requestBuilder.header("Connection", "Keep-Alive");}// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing// the transfer stream.boolean transparentGzip = false;if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {transparentGzip = true;requestBuilder.header("Accept-Encoding", "gzip");}List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());if (!cookies.isEmpty()) {requestBuilder.header("Cookie", cookieHeader(cookies));}if (userRequest.header("User-Agent") == null) {requestBuilder.header("User-Agent", Version.userAgent());}Response networkResponse = chain.proceed(requestBuilder.build());HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest);if (transparentGzip&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))&& HttpHeaders.hasBody(networkResponse)) {GzipSource responseBody = new GzipSource(networkResponse.body().source());Headers strippedHeaders = networkResponse.headers().newBuilder().removeAll("Content-Encoding").removeAll("Content-Length").build();responseBuilder.headers(strippedHeaders);String contentType = networkResponse.header("Content-Type");responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));}return responseBuilder.build(); }補全請求頭:
| Content-Type | 請求體類型,如: application/x-www-form-urlencoded |
| Content-Length / Transfer-Encoding | 請求體解析方式 |
| Host | 請求的主機站點 |
| Connection: Keep-Alive | 保持長連接 |
| Accept-Encoding: gzip | 接受響應支持gzip壓縮 |
| Cookie | cookie身份辨別 |
| User-Agent | 請求的用戶信息,如:操作系統、瀏覽器等 |
在補全了請求頭后交給下一個攔截器處理,得到響應后,主要干兩件事情:
總結
橋接攔截器的執行邏輯主要就是以下幾點:
接下來的緩存攔截器以及后續的分析會在《OkHttp源碼解析(下)》中體現。
總結
以上是生活随笔為你收集整理的OkHttp源码解析(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 禅道工具的使用
- 下一篇: Factstone Benchmark