淺析Okhttp3架構源碼
- 第一部分 HTTP協定了解
-
- 一、HTTP協定介紹
- 二、HTTP請求方法
- 三、HTTP狀态碼
- 四、HTTP請求封包(請求協定)
- 五、HTTP響應封包(響應協定)
- 第二部分 Okhttp請求流程
-
- 一、Okhttp請求流程圖
- 二、Okhttp使用源碼解析
-
- 1、okhttp架構使用前提
- 2、簡單使用方式
-
- 1)同步的HTTP GET方式
- 2)異步的HTTP GET方式
- 第三部分 源碼剖析
-
- 一、OkHttpClient用戶端
- 二、Request請求體
- 三、NewCall()方法
- 四、ReallCall類
-
- 1、同步請求方式execute()
-
- 1.1)Dispather類
- 2、異步請求方式enqueue(Callback callback)
- 3、getResponseWithInterceptorChain()方法
- 五、攔截器
-
- 1、retryAndFollowUpInterceptor
- 2、BridgeInterceptor
- 3、CacheInterceptor
- 4、ConnectInterceptor
- 5、CallServerInterceptor
- 第四部分 總結
-
- 參考資料
文章較長,連續閱讀可能會産生疲勞效果,如果時間有限,可以按需讀取。
第一部分 HTTP協定了解
在接觸okhttp架構時,如果對HTTP協定不太了解,那麼可以在本文章的第一部分大緻了解下HTTP協定的基本體系和響應請求的流程,希望通過對協定原理的研究,能夠在後續使用和加深學習okhttp架構時能夠幫助了解和使用。
一、HTTP協定介紹
HTTP協定是Hyper Text Transfer Protocol(超文本傳輸協定)的縮寫,是用于從網際網路(WWW:World Wide Web )伺服器傳輸超文本到本地浏覽器的傳送協定。
通過http協定可以達到伺服器端和用戶端進行通信的目的,由用戶端對伺服器指定端口進行TCP連接配接(TCP是相對與UDP較可靠的傳輸方式),而伺服器在端口對用戶端的請求進行監聽,如果收到請求消息,則會傳回一個狀态碼,以及傳回的内容,請求檔案,錯誤消息等。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL1E1VipWOsNGbKdlYxUjMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLyUDO0EDOyETMxEzNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
二、HTTP請求方法
在HTTP中定義了一共8種請求方式來對資源進行通路,其中包括通路方式如下:
請求辨別 | 辨別解釋 |
---|---|
GET | 向指定的資源發出“顯示”請求。 |
HEAD | 進行指定資源請求,隻不過伺服器不傳回資源的文本部分。 |
POST | 向指定資源送出資料,請求伺服器進行處理。 |
PUT | 向指定資源位置上傳其最新内容 |
DELETE | 請求伺服器删除Request-URI所辨別的資源。 |
TRACE | 回顯伺服器收到的請求,主要用于測試或診斷。 |
OPTIONS | 傳回該資源所支援的所有HTTP請求方法。 |
CONNECT | 用于SSL加密伺服器的連結(經由非加密的HTTP代理伺服器)。 |
- GET送出的資料會放在URL之後,也就是請求行裡面,以?分割URL和傳輸資料,參數之間以&相連,如EditBook?name=test1&id=123456.(請求頭裡面那個content-type做的這種參數形式,後面講) POST方法是把送出的資料放在HTTP包的請求體中.
- GET送出的資料大小有限制(因為浏覽器對URL的長度有限制),而POST方法送出的資料沒有限制.
三、HTTP狀态碼
在進行HTTP的請求時都會得到相對的響應碼(也叫狀态碼),根據這裡的狀态碼可以對這次的請求結果進行第一步的簡要分析。
所有HTTP響應的第一行都是狀态行,依次是目前HTTP版本号,3位數字組成的狀态代碼,以及描述狀态的短語,彼此由空格分隔。
狀态代碼的第一個數字代表目前響應的類型:
1xx消息——請求已被伺服器接收,繼續處理
2xx成功——請求已成功被伺服器接收、了解、并接受
3xx重定向——需要後續操作才能完成這一請求
4xx請求錯誤——請求含有詞法錯誤或者無法被執行
5xx伺服器錯誤——伺服器在處理某個正确請求時發生錯誤
四、HTTP請求封包(請求協定)
請求行由三部分組成:請求方法,請求URI(不包括域名),HTTP協定版本
請求頭部由關鍵字/值對組成,每行一對
Host : 請求的主機名,允許多個域名同處一個IP位址,即虛拟主機
Connection:連接配接方式,keepalive(告訴WEB伺服器或者代理伺服器,在完成本次請求的響應後,保持連接配接,等待本次連接配接的後續請求)。
Content-Type:發送端發送的實體資料的資料類型。
Content-Length:發送封包内容的長度。
五、HTTP響應封包(響應協定)
狀态行也由三部分組成:伺服器HTTP協定版本,響應狀态碼,狀态碼的文本描述
同樣,響應頭部由關鍵字/值對組成,每行一對
Date:表示消息發送的時間,緩存在評估響應的新鮮度時要用到,時間的描述格式由RFC822定義。
Content-Type:響應端發送的實體資料的資料類型。
Content-Length:響應封包内容的長度。
第二部分 Okhttp請求流程
在前置的http了解過後,我們終于進入了okhttp架構的了解和學習,作為目前較主流的網絡請求架構,Okhttp的具體請求和響應流程是怎麼樣的,如何在剖析了解這些請求和響應方式就是第二部分的主要内容了。
一、Okhttp請求流程圖
下圖為okhttp整體請求Response時的請求流程圖:
二、Okhttp使用源碼解析
接下來會基于上述的一個請求流程,并依據Okhttp的簡單的使用樣例進行對Okhttp源碼進行解析和了解,盡量達到以下目的:
1、了解Okhttp的基本使用原理。
2、認識OKhttpClient用戶端和Request請求體。
3、明白Dispather排程流程。
4、同步和異步請求的流程方式。
1、okhttp架構使用前提
和大部分架構一樣,在進行okhttp使用前,需要将okhttp的依賴包進行
導入,如下所示:
目前最新版為4.72版本,因為其中的源碼采用Kotlin進行編譯,為了友好的閱讀體驗,且二者之間的核心處理流程并沒有太大改變後面采用以3.10.0的Java版本作為主要源碼剖析版本。其中對于最新的okhttp的位址可以在該Okhttp架構官方連結中進行檢視
因為本身屬于網絡請求,使用在使用時需要在清單檔案中添加能夠通路網絡的權限:
Tips:其中在Android高版本中,如果對不安全的網絡位址進行通路可能會産生無法進行非安全連結通路的問題,可以通過嘗試如下兩種方式解決:
1、将url的連結修改為安全連接配接,https(http+ssl加密)等
2、在AndroidMainfest中添加以下内容:
進行安全配置,其中引用的xml檔案配置如下:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
2、簡單使用方式
在接下來對Okhttp架構源碼解析之前,會以下面兩個例子作為樣例講訴,二者以同步和異步方式作為主要差別。
1)同步的HTTP GET方式
如下需要注意的是,目前主流的連結為https連結,即為在http基礎上進行了ssl證書認證,如果直接進行http請求頭開始通路的話,會出現401通路錯誤。
private void SyncHttpGet(){
new Thread(new Runnable() {//因為網絡請求都需要在子線程進行,這裡建立一個線程
@Override
public void run() {
try {
String urlBaidu = "https://www.baidu.com/";
OkHttpClient okHttpClient = new OkHttpClient(); // 建立OkHttpClient對象
Request request = new Request.Builder().url(urlBaidu).build();
// 建立一個請求體,在請求體中可以設定url,method,body和head,預設method為Get
Response response = okHttpClient.newCall(request).execute(); // 傳回實體
if (response.isSuccessful()) { // 判斷是否成功(code為2XX)
/**擷取傳回的資料,可通過response.body().string()擷取,預設傳回的是utf-8格式;
* string()适用于擷取小資料資訊,如果傳回的資料超過1M,建議使用stream()擷取傳回的資料,
* 因為string() 方法會将整個文檔加載到記憶體中。*/
System.out.println(response.body().string()); // 列印資料
}else {
System.out.println("Wrong"); // 連結失敗
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
2)異步的HTTP GET方式
在異步通路中,因為自身會建立一個新的線程,是以這裡我們不需要再在一個新的線程裡進行網絡請求(耗時操作)的處理
private void AsyncHttpGet() {
String urlBaidu = "https://www.baidu.com/";
OkHttpClient okHttpClient = new OkHttpClient(); // 建立OkHttpClient對象
Request request = new Request.Builder().url(urlBaidu).build(); // 建立一個請求
okHttpClient.newCall(request).enqueue(new Callback() { // 回調
public void onResponse(Call call, Response response) throws IOException {
// 請求成功調用,該回調在子線程
System.out.println(response.body().string());
}
public void onFailure(Call call, IOException e) {
// 請求失敗調用
System.out.println(e.getMessage());
}
});
}
第三部分 源碼剖析
一、OkHttpClient用戶端
依據請求流程可知,在使用時,都會在最開始建立一個OkhttpClient對象,這相當于網絡請求的用戶端,用來和伺服器進行連接配接。下面我們來看下OkhttpClient的源碼部分。
下面為初始代碼塊,在初始代碼塊中會對一些基本變量的聲明和預設協定的配置,協定配置如下:
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
Protocol.HTTP_2, Protocol.HTTP_1_1);//預設使用Http1.1和Http2協定
static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
//預設情況下,OkHttp将嘗試一個moderni_tls連接配接
......(此處省略)
}
在進行變量聲明時,會執行Builder方法,在Builder方法中,利用建造者模式,快速建構一個OkhttpClient用戶端所需求的變量内容。
public OkHttpClient() {
this(new Builder());//執行下面的方法,進行初始話建構
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;//排程器,執行請求時的政策
this.proxy = builder.proxy;//代理端口
this.protocols = builder.protocols;//設定OkHttpClient使用的協定,預設為HTTP/2和HTTP/1.1
this.connectionSpecs = builder.connectionSpecs;//TLS版本與連接配接協定
this.interceptors = Util.immutableList(builder.interceptors);//設定自定義攔截器
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);//設定網絡攔截器
this.eventListenerFactory = builder.eventListenerFactory;//設定事件監聽器,在請求時的一個流程事件的監聽,包括callStrart,dnsStart,dnsEnd等等
this.proxySelector = builder.proxySelector;//設定代理選擇政策
this.cookieJar = builder.cookieJar;//設定Cookie處理器,用于從HTTP響應中接收Cookie,并且可以将Cookie提供給即将發起的請求
this.cache = builder.cache;//設定緩存的路徑和緩存空間大小,用于讀取和寫入已緩存的響應資訊Response
this.internalCache = builder.internalCache;//設定緩存政策的攔截器
this.socketFactory = builder.socketFactory;//套接字工廠設定,對應http協定
boolean isTLS = false;
for (ConnectionSpec spec : connectionSpecs) {
isTLS = isTLS || spec.isTls();
}//設定是否采用TLS(安全傳輸層協定,用于在兩個通信應用程式之間提供保密性和資料完整性。)
if (builder.sslSocketFactory != null || !isTLS) {
this.sslSocketFactory = builder.sslSocketFactory;//套接字工廠設定,對應https協定
this.certificateChainCleaner = builder.certificateChainCleaner;
} else {
X509TrustManager trustManager = systemDefaultTrustManager();
this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
}
this.hostnameVerifier = builder.hostnameVerifier;//主機名驗證
this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
certificateChainCleaner);//設定固定證書
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool;
this.dns = builder.dns;
this.followSslRedirects = builder.followSslRedirects;//設定是否允許HTTP與HTTPS請求之間互相重定向,預設為true允許
this.followRedirects = builder.followRedirects;//設定是否允許請求重定向,預設為true允許;
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;//重連錯誤
this.connectTimeout = builder.connectTimeout;//設定連接配接建立的逾時時間
this.readTimeout = builder.readTimeout;//設定讀資料的逾時時間
this.writeTimeout = builder.writeTimeout;//設定寫資料的逾時時間
this.pingInterval = builder.pingInterval;//ping指令的間隔執行時間
if (interceptors.contains(null)) {
throw new IllegalStateException("Null interceptor: " + interceptors);
}//攔截器是否為空
if (networkInterceptors.contains(null)) {
throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
}//網絡攔截器是否為空
}
二、Request請求體
Request請求體作為用戶端的信件的載體,其内可以設定請求的方式(GET,POST,HEAD,PUT等八種請求方式),将這個請求體加載到Okhttp的newCall方法内進行call請求,最後得到Reponse響應體,總而言之,在Okhttp中request更像是從用戶端發送出去的郵件,郵差也就是網際網路将郵件内容(body),請求方式(method)和請求格式(head)發送給對應的伺服器(url),至于響應接收就是Reponse的事了。
接下來,我們來看看Request請求體的Builder源碼内容:
public static class Builder {
HttpUrl url;//url連結
String method;//請求方式
Headers.Builder headers;//請求頭部,也就是所謂的帶參請求
RequestBody body;//請求體,主要内容
Object tag;//用來辨別請求
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}//預設的請求方式為GET
Builder(Request request) {
this.url = request.url;
this.method = request.method;
this.body = request.body;
this.tag = request.tag;
this.headers = request.headers.newBuilder();
}//進行這些基本變量的設定
三、NewCall()方法
在Okhttp中通過NewCall()的方法放回ReallCall對象,再在ReallCall對象中進行對同步異步請求的處理。如下為Request的newCall方法,很明顯就能發現是繼承了Call類,傳回的RealCall的一個方法。
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
四、ReallCall類
在上述方法的newRealCall(如下所示)中可以發現,其實傳回的已經是繼承了Call類的ReallCall類,這裡進行了一個執行個體化操作和給call添加了一個事件監聽器。
其中ReallCall類在網絡請求上的操作,更像是整體請求的最後一步的封裝,用來進行異步同步請求,取消請求,攔截器連結清單建立操作。
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;// 最初的Request
this.forWebSocket = forWebSocket;//是否支援websocket通信
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);//執行個體化了一個Reallcall類
call.eventListener = client.eventListenerFactory().create(call);//添加了事件監聽器
return call;
}
1、同步請求方式execute()
在同步請求時首先會通過captureCallStackTrace()對調用棧的跟蹤捕獲,具體作用是給重定向設定一個callStackTrace也就是回溯資訊,具體作用這裡就不贅述了,同時事件監聽器也同時執行callStart方法,這裡的CalStart可以自己複寫達到輸出監聽啟動CAll流程的效果。
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();//捕獲調用堆棧跟蹤
eventListener.callStart(this);//CallStart回調
try {
client.dispatcher().executed(this);//此處請求将進入Dispatcher對象内做相應的排程處理
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);
}
}
1.1)Dispather類
在之後client會使用排程器dispather()執行異步的請求政策,這裡的dispather()方法是傳回一個Dispather執行個體,這個執行個體在外面建構OkhttpClient的時候其實就已經有了預設構造,dispather()的方法源碼如下:
public Dispatcher dispatcher() {
return dispatcher;
}
那麼Dispather的作用又是什麼呢?其實在整體流程中,Dispther的作用正如它的名字一樣,是用來進行一個請求測試的配置設定排程,這裡可能是異步的,也可能是同步的,而異步和同步的排程原理和方式就在Dispather這個類中可以詳細看到。
首先在Dispather建構開始,也就是初始化時,會預設設定一下參數,這裡參數包括請求數和接口的設定,同時定義了兩個雙端隊列(Deque),一個是異步隊列,一個是同步隊列,在異步隊列中有準備請求的隊列和正在運作的隊列,這是為了防止同時運作請求的異步線程過多導緻記憶體異常等錯誤,而同步請求的隊列隻有一個進行處理。
Deque即雙端隊列。是一種具有隊列和棧的性質的資料結構。雙端隊列中的元素可以從兩端彈出,相比list增加[]運算符重載。
Dispather源碼如下:
public final class Dispatcher {
private int maxRequests = 64; //預設同時執行的最大請求數, 可以通過setMaxRequests(int)修改.
private int maxRequestsPerHost = 5;//每個主機預設請求的最大數目, 可以通過setMaxRequestsPerHost(int)修改.
private @Nullable Runnable idleCallback;//排程沒有請求任務時的回調.
/** Executes calls. Created lazily. */
//執行異步請求的線程池,預設是 核心線程為0,最大線程數為Integer.MAX_VALUE,空閑等待為60s.
private @Nullable ExecutorService executorService;
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//異步請求的執行順序的隊列.
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//運作中的異步請求隊列.
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();//運作中的同步請求隊列.
********此處省略
}
如果沒有通過構造方法Dispatcher(ExecutorService executorService) 設定線程池的話,預設就是 核心線程為0,最大線程數為Integer.MAX_VALUE,空閑等待為60s,用SynchronousQueue儲存等待任務的線程池。
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;
}
而在該類中的同步請求方法executed()如下所示,是在運作隊列中添加call準備處理,當然,會利用synchronized保證添加過程中的線程安全。那麼到此為止的話就是在同步中添加到隊列的一個過程。
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
而對加載到這個隊列的call,如果在RellCall内捕獲到IO的異常(詳情看上面的ReallCall類說明),那麼最後會通過如下的finised方法進行任務的結束請求(call)。
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);//傳入runningSyncCalls進行執行
}
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();//統計所有的call數量
idleCallback = this.idleCallback;
}
2、異步請求方式enqueue(Callback callback)
和同步請求最主要的不同的是異步請求在最後調用的是enqueue的方法。
@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這個對象,這個對象其實就是一個Runnable,在建構了一個Runnable,AsyncCall之後就直接調用了dispatcher().enqueue這個方法,并将前面建立好的AsyncCall傳到這個方法當中。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@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 {//最後也是進行finished()
client.dispatcher().finished(this);
}
}
}
在enqueue方法中,會對目前的請求數和運作隊列中異步請求數量的一個判斷,依此來決定該請求是存放在運作隊列還是準備隊列。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);//運作隊列添加
executorService().execute(call);//通過executorService送出該請求
} else {
readyAsyncCalls.add(call);//準備隊列添加
}
}
因為異步請求的特殊性,會在最後的finish()中(詳見上述AsnycCall),對這些請求隊列進行一個判斷和處理,如果運作隊列有空,那麼就可以将等待隊列中的一些請求移到運作隊列内,是異步請求雙隊列轉化方式的主要邏輯。
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.
// 超過同一主機同時運作的最大請求任務數
}
}
3、getResponseWithInterceptorChain()方法
那麼無論是同步還是異步方法,都在代碼中不約而同的調用了getResponseWithInterceptorChain()方法來傳回Response對象,這個方法是在請求分發後都會進行的,首先是将一些攔截器依次加入,包括使用者自定義的,架構已經搭建好的,最後通過責任鍊的傳回模式得到最終的響應體,也就是我們這的Response。
責任鍊處理模式如下:
責任鍊模式是一個請求有多個對象來處理,這些對象是一條鍊,但具體由哪個對象來處理,根據條件判斷來确定,如果不能處理會傳遞給該鍊中的下一個對象,直到有對象處理它為止。
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()));
//資料轉換,将使用者設定的Request轉換成網絡請求所需要的請求,傳到下一個攔截器中,并将下一個攔截器傳回的結果轉換成RealResponseBody傳回給上一個攔截器。
interceptors.add(new CacheInterceptor(client.internalCache()));
//緩存,讀取緩存直接傳回和更新緩存,并将其下一個攔截器傳回的結果傳回給上一個攔截器。
interceptors.add(new ConnectInterceptor(client));
//建立一個連結,并将其下一個攔截器傳回的結果傳回給上一個攔截器
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
//網絡攔截器,可用于查詢用于連接配接到網絡伺服器的IP位址和TLS配置。
}
interceptors.add(new CallServerInterceptor(forWebSocket));
//向伺服器請求資料,完成請求,并傳回給上一個攔截器。
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//最後将這些攔截器都傳入到RealInterceptorChain,通過chain.proceed()來完成整個請求過程。而這些攔截器中采用的是責任鍊模式,一個攔截器會去調用下一個攔截器的intercept()或.proceed()來完成最後的請求。
return chain.proceed(originalRequest);
}
具體的責任鍊的剖析在下述代碼,會在最後執行的proceed中将傳入的攔截器形成一個個鍊條,調用下一個攔截器,擷取Response,通過對得到的Response進行處理,并将其傳回給上一個攔截器。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection 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.httpCodec != null && !this.connection.supportsUrl(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.httpCodec != 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, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
// 根據index擷取攔截器
Response response = interceptor.intercept(next);
//再把上面擷取到的攔截器鍊,當做參數傳進去,這樣就形成了所有攔截器的鍊條
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != 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");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}//對響應體為空的判斷
return response;
}
五、攔截器
攔截器是OkHttp中提供的一種機制,它可以實作網絡監聽、請求以及響應重寫、請求失敗重試,填充頭部資訊等功能。将request進行完善,最終得到滿足需求的一個Response。
整個流程就是OkHttp通過定義許多攔截器一步一步地對Request進行攔截處理(從頭至尾),直到請求傳回網絡資料,後面又倒過來,一步一步地對Response進行攔截處理,最後攔截的結果就是回調給調用者的最終Response。(從尾至頭)
1、retryAndFollowUpInterceptor
在重定向中,主要作用是負責請求的重定向操作,用于處理網絡請求中,請求失敗後的重試機制。首先,會根據用戶端請求Request以及OkHttpClient,建立出StreamAllocation,它主要用于管理用戶端與伺服器之間的連接配接,同時管理連接配接池,以及請求成功後的連接配接釋放等操作,在執行StreamAllocation建立時,可以看到根據用戶端請求的位址url,還調用了createAddress方法。
進入該方法可以看出,這裡傳回了一個建立成功的Address,實際上Address就是将用戶端請求的網絡位址,以及伺服器的相關資訊,進行了統一的包裝,也就是将用戶端請求的資料,轉換為OkHttp架構中所定義的伺服器規範,這樣一來,OkHttp架構就可以根據這個規範來與伺服器之間進行請求分發了。
// 攔截進行重定向
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
// 建立建立Http請求的一些網絡組建
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
int followUpCount = 0; // 重定向次數,其中MAX_FOLLOW_UPS預設為20次
Response priorResponse = null;
// 将前一步得到的followUp 指派給request,重新進入循環
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
// proceed 内部會實作下一個攔截器鍊的方法 這個在上一篇已經分析過了
// 将前一步得到的followUp不為空進入循環 繼續執行下一步 followUp就是request
// 繼續執行下一個Interceptor,即BridgeInterceptor
// 進行重定向進行新的請求
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(), streamAllocation, 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, streamAllocation, 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.
// 建構響應體,這個響應體的body為空
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp = followUpRequest(response, streamAllocation.route());
// followUp 就是followUpRequest方法經過檢測傳回的Request
if (followUp == null) {
if (!forWebSocket) {//是否為WebSocket協定,是的話就釋放這個網絡元件對象
streamAllocation.release();
}
return response;// 如果Request為空 則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) {
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;// 把重定向的請求指派給request,以便再次進入循環執行
priorResponse = response;
}
}
其中重定向功能預設是開啟的,可以選擇關閉,然後去實作自己的重定向功能
new OkHttpClient.Builder()
.followRedirects(false) //禁制OkHttp的重定向操作,我們自己處理重定向
.followSslRedirects(false)//https的重定向也自己處理
2、BridgeInterceptor
在橋攔截器中可以明顯的發現,在這個攔截器中對request和response進行了一個合理有效的規範,這個規範包括将請求添加上一些必要的請求格式,比如内容類型,将這些必需的請求進行内部的封裝,這樣就可以在對Request進行簡單的設定就能達到請求規範的原則,同理,将Response進行規範可用化也是橋攔截器重要的功能。
其中在Gzip解壓部分,當 transparentGzip 為 true ,表示請求設定的 Accept-Encoding 是 支援gzip 壓縮的,意思就是告知伺服器用戶端是支援 gzip 壓縮的,然後再判斷伺服器的響應頭 Content-Encoding 是否也是 GZIP 壓縮的,意思就是響應體内容是否是經過 GZIP 壓縮的,如果都成立的條件下,那麼它會将 Resposonse.body().source() 的輸入流 BufferedSource 轉化為 GzipSource 類型,這樣的目的就是讓調用者在使用 Response.body().string() 擷取響應内容時就是以解壓的方式進行讀取流資料。
Gzip是用于UNⅨ系統的檔案壓縮。我們在Linux中經常會用到字尾為.gz的檔案,它們就是GZIP格式的。現今已經成為Internet 上使用非常普遍的一種資料壓縮格式,或者說一種檔案格式。
@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());
}
//定義網絡檔案的類型和網頁的編碼,如果未指定 ContentType,預設為[TEXT]/[HTML]
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
//表示的是請求體内容的長度。
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
//Transfer-Encoding值為 chunked 表示請求體的内容大小是未知的
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
//Host 請求的 url 的主機
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
//Connection 預設就是 "Keep-Alive",就是一個 TCP 連接配接之後不會關閉,保持連接配接狀态。
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
//預設是 GZIP 壓縮的
//Accept-Encoding : 就是告訴伺服器用戶端能夠接受的資料編碼類型,OKHTTP 預設就是 GZIP 類型。
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
//标記請求支援 GZIP 壓縮
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
//Accept-Encoding 預設是 "gzip" 告訴伺服器用戶端支援 gzip 編碼的響應
//cookie 頭的添加
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
//Cookie 當請求設定了 Cookie 那麼就是添加 Cookie 這個請求頭。
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//User-Agent "okhttp/3.4.1" 這個值根據 OKHTTP 的版本不一樣而不一樣,它表示用戶端 的資訊。
//發送網絡請求
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)) {
//當伺服器傳回的資料是 GZIP 壓縮的,那麼用戶端就有責任去進行解壓操作
GzipSource responseBody = new GzipSource(networkResponse.body().source());
//轉為GzipSource類行,對傳回的response進行解壓
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
//移除請求頭Content-Encoding和Content-Length
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
3、CacheInterceptor
首先介紹下http緩存,在http緩存中,目前分為兩種,一種強制緩存,一種對比緩存,強制緩存生效時直接使用以前的請求結果,無需發起網絡請求。對比緩存生效時,無論怎樣都會發起網絡請求,如果請求結果未改變,服務端會傳回304,但不會傳回資料,資料從緩存中取,如果改變了會傳回資料。
Tips:對方法是POST,PATCH,PUT,DELETE,MOVE的請求,将緩存清除掉,這些是不應該被緩存的。然後明确了一點,隻有GET方法才會被緩存。而真正的緩存寫入到檔案是通過一個叫Entry的輔助類來的。具體源碼可以自行檢視CacheRequest的put方法。
@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();
//CacheStrategy,就是由它來決定采用何種緩存規則的。okhttp為每一個請求都記錄了一個Strategy。
//所謂的決定政策,其實就是決定是否要重新發起網絡請求
//包括:沒有對應的緩存結果;https請求卻沒有握手資訊;不允許緩存的請求(包括一些特殊狀态碼以及Header中明确禁止緩存)。
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.
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();
}
//這種情況說明從緩存政策上來說強制緩存生效,應該直接取上次緩存結果,但由于未知原因緩存的結果沒有了或者上次傳回就沒有,這裡直接傳回了失敗的Response
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//這種情況下,強制緩存生效,這裡對Response的cacheResponse的body做了置空處理。
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 (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());
}
}
//按照對比緩存規則,取完之後應該判斷是否傳回304,如果是應該使用緩存結果
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//剩下的情況就是緩存完全失效了,這個時候應該利用剛才網絡請求的結果
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
4、ConnectInterceptor
ConnectInterceptor攔截器的主要作用是用來打開與伺服器的連接配接connection,正式開啟了網絡請求,而這裡的連接配接也主要是通過連接配接池擷取的。然後将連接配接傳入到下一個攔截器中。
@Override public Response intercept(Chain chain) throws IOException {
//從攔截器鍊裡得到StreamAllocation對象
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
通過streamAllocation ,newStream,這個裡面會建立連接配接等一系列的操作
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//擷取realConnetion
RealConnection connection = streamAllocation.connection();
//執行下一個攔截器,也就是最後一個攔截器
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
在上述源碼中會發現多了一個StreamAllocation類,這個類其實在第一次調用攔截器的時候就建立了,在裡面會傳入三個參數,連接配接池(connectionPool()),位址類(request.url()),調用堆棧跟蹤相關(callStackTrace)
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
在StreamAllocation構造函數中,主要是把這個三個參數儲存為内部變量,供後面使用,還有一個就是同時建立了一個線路選擇器,讓後面可以進行線路選擇。
之後在newStream中會通過findHealthyConnection()方法獲得一個健康可用的連接配接,而這裡的連接配接如果進行深入了解會發現是存在于RealConnection線程池的一個連接配接(具體方法在findHealthyConnection-> RealConnection findConnection源碼中可以檢視)
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();//擷取設定的連接配接逾時時間
int readTimeout = chain.readTimeoutMillis();//讀寫逾時的時間
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();//ping指令間隔時間
boolean connectionRetryEnabled = client.retryOnConnectionFailure();//否進行重連
try {
// 擷取健康可用的連接配接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//通過resultConnection初始化,對請求以及結果 編解碼的類(分http 1.1 和http 2.0)
// 這裡主要是初始化,在後面一個攔截器才用到這相關的東西。
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
5、CallServerInterceptor
其實在進行CallServerInterceptor攔截器之前還有一個網絡攔截器networkInterceptors,這個是可以通過我們自定義進行實作以下功能的,包括
1、能操作中間響應,例如重定向和重試。
2、發生網絡短路的緩存響應時,不被調用。
3、觀察将通過網絡傳輸的資料。
4、可以擷取到攜帶請求的connection
那麼作為最後一個攔截器,CallServerInterceptor的主要作用就是向伺服器發送請求,最終傳回response。其中在該攔截器中的HttpCodec對象中的source、Sink對象是來對向伺服器發送和接收狀态行或實體内容,進行些狀态碼判斷、進而完成對Response對象的建構,再傳回到上一級攔截器做處理。
其執行流程如下:
1、擷取request、httpCodec等對象。
2、通過httpCodec發送狀态行和頭部資料到服務端。
3、判斷是否是有請求實體的請求,如果是,判斷目前是否有設定Expect: 100-continue請求頭。
4、往流中寫入請求實體内容。
5、讀取伺服器傳回的頭部資訊、狀态碼等,建構responseBuilder對象并傳回。
6、通過responseBuilder對象來建構response對象。
7、判斷目前狀态碼,如果是100,則重新請求一次,得到response對象。
8、給Response對象設定body。
9、判斷是否需要斷開連接配接。
@Override public Response intercept(Chain chain) throws IOException {
// 擷取對象
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
// 發送狀态行和頭部資料到服務端
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
// 判斷是否有請求實體的請求(不是GET請求)
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
// 如果頭部資訊添加了"Expect: 100-continue",這個請求頭字段的作用是在發送RequestBody前向伺服器确認是否接受RequestBody,如果伺服器不接受也就沒有發送的必要了。
// 有這個字段,相當于一次簡單的握手操作,會等待拿到伺服器傳回的ResponseHeaders之後再繼續,如果伺服器接收RequestBody,會傳回null。
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
// 伺服器傳回
responseBuilder = httpCodec.readResponseHeaders(true);
}
// 伺服器同意接收,開始向流中寫入RequestBody
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
// 向流寫入資料
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
streamAllocation.noNewStreams();
}
}
//httpCpdec完成請求
httpCodec.finishRequest();
// 讀取頭部資訊、狀态碼等
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
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 (code == 100) {
//如果狀态碼為100,那就再請求一次
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
// 如果狀态碼為101,設定一個空的Body
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
// 讀取Body資訊
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
// 如果設定了連接配接關閉,則斷開連接配接
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
//HTTP 204(no content) 代表響應封包中包含若幹首部和一個狀态行,但是沒有實體的主體内容。
//HTTP 205(reset content) 表示響應執行成功,重置頁面(Form表單),友善使用者下次輸入
//這裡做了同樣的處理,就是抛出協定異常。
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
第四部分 總結
那麼到此為止其實已經完成了對repose響應體的一個擷取,本篇文章的主要内容是用于對架構源碼的原理進行淺要的分析,通過架構的一個使用流程我們進行跟蹤式的通路解析,當然因為隻是淺要分析,是以在較深層的源碼結構中,文章内部隻是一筆帶過,有興趣的可以自行了解。
參考資料
OkHttp之Dispatcher
https://blog.csdn.net/lxk_1993/article/details/101449342
OkHttp之getResponseWithInterceptorChain
https://blog.csdn.net/nihaomabmt/article/details/88187205
OkHttp攔截器鍊源碼解讀
https://www.jianshu.com/p/1181f48d6dcf
OKhttp源碼解析詳解系列
https://www.jianshu.com/p/d98be38a6d3f
OkHttp3
https://www.jianshu.com/p/0acb2d787125
_JW的攔截器分析系列
https://blog.csdn.net/qq_21612413/article/details/88356726
OkHttp架構的RetryAndFollowUpInterceptor請求重定向源碼解析
https://blog.csdn.net/qq_15274383/article/details/73729801
HTTP協定超級詳解
https://www.cnblogs.com/an-wen/p/11180076.html