okhttp搭建伺服器
『壹』 OkHttp源碼解析 (三)——代理和路由
初看OkHttp源碼,由於對Address、Route、Proxy、ProxySelector、RouteSelector等理解不夠,讀源碼非常吃力,看了幾遍依然對於尋找復用連接、創建連接、連接伺服器、連接代理伺服器、創建隧道連接等邏輯似懂非懂,本篇決定梳理一遍相關的概念及基本原理。
● HTTP/1.1(HTTPS)
● HTTP/2
● SPDY
一個http請求的流程(直連):
1、輸入url及參數;
2、如果是url是域名則解析ip地址,可能對應多個ip,如果沒有指定埠,則用默認埠,http請求用80;
3、創建socket,根據ip和埠連接伺服器(socket內部會完成3次TCP握手);
4、socket成功連接後,發送http報文數據。
一個https請求的流程(直連):
1、輸入url及參數;
2、如果是url是域名則解析ip地址,可能對應多個ip,如果沒有指定埠,則用默認埠,https請求用443;
3、創建socket,根據ip和埠連接伺服器(socket內部會完成3次TCP握手);
4、socket成功連接後進行TLS握手,可通過java標准款提供的SSLSocket完成;
5、握手成功後,發送https報文數據。
1、分類
● HTTP代理:普通代理、隧道代理
● SOCKS代理:SOCKS4、SOCKS5
2、HTTP代理分類及說明
普通代理
HTTP/1.1 協議的第一部分。其代理過程為:
● client 請求 proxy
● proxy 解析請求獲取 origin server 地址
● proxy 向 origin server 轉發請求
● proxy 接收 origin server 的響應
● proxy 向 client 轉發響應
其中proxy獲取目的伺服器地址的標准方法是解析 request line 里的 request-URL。因為proxy需要解析報文,因此普通代理無法適用於https,因為報文都是加密的。
隧道代理
通過 Web 代理伺服器用隧道方式傳輸基於 TCP 的協議。
請求包括兩個階段,一是連接(隧道)建立階段,二是數據通信(請求響應)階段,數據通信是基於 TCP packet ,代理伺服器不會對請求及響應的報文作任何的處理,都是原封不動的轉發,因此可以代理 HTTPS請求和響應。
代理過程為:
● client 向 proxy 發送 CONNET 請求(包含了 origin server 的地址)
● proxy 與 origin server 建立 TCP 連接
● proxy 向 client 發送響應
● client 向 proxy 發送請求,proxy 原封不動向 origin server 轉發請求,請求數據不做任何封裝,為原生 TCP packet.
3、SOCKS代理分類及說明
● SOCKS4:只支持TCP協議(即傳輸控制協議)
● SOCKS5: 既支持TCP協議又支持UDP協議(即用戶數據包協議),還支持各種身份驗證機制、伺服器端域名解析等。
SOCK4能做到的SOCKS5都可得到,但反過來卻不行,比如我們常用的聊天工具QQ在使用代理時就要求用SOCKS5代理,因為它需要使用UDP協議來傳輸數據。
有了上面的基礎知識,下面分析結合源碼分析OkHttp路由相關的邏輯。OkHttp用Address來描述與目標伺服器建立連接的配置信息,但請求輸入的可能是域名,一個域名可能對於多個ip,真正建立連接是其中一個ip,另外,如果設置了代理,客戶端是與代理伺服器建立直接連接,而不是目標伺服器,代理又可能是域名,可能對應多個ip。因此,這里用Route來描述最終選擇的路由,即客戶端與哪個ip建立連接,是代理還是直連。下面對比下Address及Route的屬性,及路由選擇器RouteSelector。
描述與目標伺服器建立連接所需要的配置信息,包括目標主機名、埠、dns,SocketFactory,如果是https請求,包括TLS相關的SSLSocketFactory 、HostnameVerifier 、CertificatePinner,代理伺服器信息Proxy 、ProxySelector 。
Route提供了真正連接伺服器所需要的動態信息,明確需要連接的伺服器IP地址及代理伺服器,一個Address可能會有很多個路由Route供選擇(一個DNS對應對個IP)。
Address和Route都是數據對象,沒有提供操作方法,OkHttp另外定義了RouteSelector來完成選擇的路由的操作。
1、讀取代理配置信息:resetNextProxy()
讀取代理配置:
● 如果有指定代理(不讀取系統配置,在OkHttpClient實例中指定),則只用1個該指定代理;
● 如果沒有指定,則讀取系統配置的,可能有多個。
2、獲取需要嘗試的socket地址(目標伺服器或者代理伺服器):resetNextInetSocketAddress()
結合Address的host和代理,解析要嘗試的套接字地址(ip+埠)列表:
● 直連或者SOCK代理, 則用目標伺服器的主機名和埠,如果是HTTP代理,則用代理伺服器的主機名和埠;
● 如果是SOCK代理,根據目標伺服器主機名和埠號創建未解析的套接字地址,列表只有1個地址;
● 如果是直連或HTTP代理,先DNS解析,得到InetAddress列表(沒有埠),再創建InetSocketAddress列表(帶上埠),InetSocketAddress與InetAddress的區別是前者帶埠信息。
3、獲取路由列表:next()
選擇路由的流程解析:
● 遍歷每個代理對象,可能多個,直連的代理對象為Proxy.DIRECT(實際是沒有中間代理的);
● 對每個代理獲取套接字地址列表;
● 遍歷地址列表,創建Route,判斷Route如果在路由黑名單中,則添加到失敗路由列表,不在黑名單中則添加到待返回的Route列表;
● 如果最後待返回的Route列表為空,即可能所有路由都在黑名單中,實在沒有新路由了,則將失敗的路由集合返回;
● 傳入Route列表創建Selection對象,對象比較簡單,就是一個目標路由集合,及讀取方法。
為了避免不必要的嘗試,OkHttp會把連接失敗的路由加入到黑名單中,由RouteDatabase管理,該類比較簡單,就是一個失敗路由集合。
1、創建Address
Address的創建在RetryAndFollowUpInteceptor里,每次請求會聲明一個新的Address及StreamAllocation對象,而StreamAllocation使用Address創建RouteSelector對象,在連接時RouteSelector確定請求的路由。
每個Requst都會構造一個Address對象,構造好了Address對象只是有了與伺服器連接的配置信息,但沒有確定最終伺服器的ip,也沒有確定連接的路由。
2、創建RouteSelector
在StreamAllocation聲明的同時會聲明路由選擇器RouteSelector,為一次請求尋找路由。
3、選擇可用的路由Route
下面在測試過程跟蹤實例對象來理解,分別測試直連和HTTP代理HTTP2請求路由的選擇過程:
● 直連請求流程
● HTTP代理HTTPS流程
請求url: https://www.jianshu.com/p/63ba15d8877a
1、構造address對象
2、讀取代理配置:resetNextProxy
3、解析目標伺服器套接字地址:resetNextInetSocketAddress
4、選擇Route創建RealConnection
5、確定協議
測試方法:
● 在PC端打開Charles,設置埠,如何設置代理,網上有教程,比較簡單;
● 手機打開WIFI,選擇連接的WIFI修改網路,在高級選項中設置中指定了代理伺服器,ip為PC的ip,埠是Charles剛設置的埠;
● OkHttpClient不指定代理,發起請求。
1、構造address對象
2、讀取代理配置:resetNextProxy
3、解析目標伺服器套接字地址:resetNextInetSocketAddress
4、選擇Route創建RealConnection
5、創建隧道
由於是代理https請求,需要用到隧道代理。
從圖可以看出,建立隧道其實是發送CONNECT請求,header包括欄位Proxy-Connection,目標主機名,請求內容類似:
6、確定協議,SSL握手
1、代理可分為HTTP代理和SOCK代理;
2、HTTP代理又分為普通代理和隧道代理;普通代理適合明文傳輸,即http請求;隧道代理僅轉發TCP包,適合加密傳輸,即https/http2;
3、SOCK代理又分為SOCK4和SOCK5,區別是後者支持UDP傳輸,適合代理聊天工具如QQ;
4、沒有設置代理(OkHttpClient沒有指定同時系統也沒有設置),客戶端直接與目標伺服器建立TCP連接;
5、設置了代理,代理http請求時,客戶端與代理伺服器建立TCP連接,如果代理伺服器是域名,則解釋代理伺服器域名,而目標伺服器的域名由代理伺服器解析;
6、設置了代理,代理https/http2請求時,客戶端與代理伺服器建立TCP連接,發送CONNECT請求與代理伺服器建立隧道,並進行SSL握手,代理伺服器不解析數據,僅轉發TCP數據包。
如何正確使用 HTTP proxy
OkHttp3中的代理與路由
HTTP 代理原理及實現(一)
『貳』 OkHttp3-連接(Connections)
雖然通常你只需要提供一個 URL 給OkHttp,OkHttp就可以幫你完成其他事情。但是實際上OkHttp連接伺服器需要三個條件:蔽圓 URL , Address , Route 。
提供一爛並絕個 URL (比如 https://github.com/square/okhttp )讓Http去連接伺服器是最基本的工作。還有一種漸漸普遍的文件定位方式稱為 URN(同意名稱定位符) ,它是利用一種分散式的命名方案去指定所需要訪問的資源文件。
URLs 是抽象的:
URL 是具體的:
Address 在OKHttp中是一個對象,它為OkHttp提供靜態配置!
地址指定了一個伺服器(比如 github.com )以及連接此伺服器所需要的靜態配置:埠號, HTTPS 設置,以及指定的網路協議(比如 HTTP/2 或者 SPDY )。
相同地址的 URL 也可以共用相同的底層 TCP Socket 連接。共用相同的連接對於性能有很大的提升:更低的延遲,更大的吞吐量(復用連接,由於每個 TCP 啟動的都需要較多的准備工作),更少的電能損耗。OkHttp使用一個連接池,來自動復用 HTTP/飢姿1.x connections 、 HTTP/2 、 SPDY 連接。
URL為地址提供了一些欄位(比如域名、主機名、埠號),其他的欄位都來自於 OkHttpClient 。
Routes 在OKHttp中是一個對象,它為OkHttp提供動態配置!
路由提供了實際連接到伺服器所需要的動態配置。比如所指定用來嘗試連接伺服器的 IP 地址(從 DNS 服務商獲得)、連接過程中實際所使用到的代理伺服器(如果使用了 ProxySelector ),以及使用的是哪個版本的
TLS 協議(當使用 Https 協議連接時候需要)。
對於一個地址來說 ,可能存在有很多種路由的方式。比如,當一個伺服器被託管在多個數據中心,這時路由從 DNS 供應商獲取的響應中就可以獲取到多個 IP 地址。
當你使用OkHttp去請求一個 URL 時,OkHttp為你做了如下事情:
如果在連接的過程中出現問題,那麼OkHttp將會選擇其他的路由進行重新連接。這意味著當一個伺服器的某一個 IP 地址無法訪問時,OkHttp可以嘗試別的 IP 地址進行訪問。或者當一個連接池過期或者你嘗試連接的所用的 TLS 版本不受伺服器支持時,這種重連機制也是非常有用的。
一旦客戶端發來接收到來自伺服器的響應,那麼這個 Connection 將會被放置到連接池中以備於將來新的連接進行復用。 Connection 在長期不使用的情況下,將會從這個連接池中被移除。
··
『叄』 webservice是基於soap協議的,在android端可以用okhttp建立連接嗎
調用幾主要:
1.創建HttpTransportSE傳輸象:HttpTransportSE ht = new HttpTransportSE(SERVICE_URL); SERVICE_URLwebservice提供服務url
2.使用SOAP1.1協議創建Envelop象:SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11); 設置SOAP協議版本號根據服務端WebService版本號設置
3.實例化SoapObject象:SoapObject soapObject = new SoapObject(SERVICE_NAMESPACE, methodName); 第參數表示WebService命名空間WSDL文檔找WebService命名空間第二參數表示要調用WebService名
4.設置調用參數值沒參數省略:例soapObject.addProperty("theCityCode", cityName);
5.記設置bodyout屬性 envelope.bodyOut = soapObject;
6.調用webservice:ht.call(SERVICE_NAMESPACE+methodName, envelope);
7.獲取伺服器響應返SOAP消息:
SoapObject result = (SoapObject) envelope.bodyIn;
SoapObject detail = (SoapObject) result.getProperty(methodName+"Result");
『肆』 OkHttp完全解析(一)
以 3.14.9版本為例,這應該是最後一個java版本了,後面的版本都是kotlin開發的。
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
一個完整的url可蠢族以包含哪些內容?
看一下這個類的構造方法
前面幾個數據拆解了url中的不同部分,包括:協議、用戶名、密碼、域名、埠、路徑、參數、錨位。
builder.toString() 將各個部分組合為一個整體,如下:
http://username:[email protected]:80/path1/path2?Key1=value1&key2=value2#anchor
域名查詢服務,通過域名獲取鋒檔培主機IP地址。
InetAddress 表示一個IP地址,這個類沒有開放的構造方法,必須使用getXX靜態方法創建。
套接字工廠,SSL套接字工廠, java SDK 內容。
搶先驗證和反應式驗證
搶先驗證:用http代理伺服器實現https請求時,需要進行搶先驗證。
反應式驗證:服務端反饋401或407時,需要提交賬號信息用於驗證。
401: 用戶沒有[訪問許可權, 需要進行身份認證。
407:客戶應首先通過代理伺服器驗證。
OkHttpClient 包含兩個實例用於驗證,分別為authenticator用於驗證原始伺服器賬號、proxyAuthenticator 用於驗證代理伺服器賬號。
這是個枚舉類, 包含以下數據
http/1.0 默認情況下不使用持久套接字,明文請求,過時
http/1.1 包含持久連接,明文請求
spdy/3.1 OkHttp不再支持該協議,使用http2.0
h2 就是http2.0,支持請求頭壓縮、多路復用、服務推送。
h2_prior_knowledge 明文http2.0
quic 快速udp網路連接,okhttp不支持,但可以通過攔截器實現支持。
連接規定。
指定ssl的加密演算法 - 或不指定加密演算法。
加密演算法
這個類裡面只有一個演算法的名稱,沒有具體的演算法實現邏輯。
類會緩存演算法名稱對應的CipherSuite對象
TLS的版本號
代理伺服器設置和代理伺服器選擇器,java SDK內容。
參考: https://blog.csdn.net/qq_33022345/article/details/53453585
鎖定SSL證書。
給指定的域名提供固定的證書,那該域名只有使用這個固定證書時才能正常訪問。如果服務端證書改變,將無法訪問。
一個伺服器地址的表示銀唯。
本文以上提到的所有類,都被用於Address類中。
『伍』 Android通過OKhttp從伺服器端獲取數據
①簡單的非同步Get請求
第一步,創建OKHttpClient對象
第二步,創建Request請求
第三步,創建一個Call對象
第四步,將請求添加到調度中
不多說,直接上代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//okHttp的基本使用 --- get方法
String url = "https://api.douban.com/v2/movie/top250?start=0&count=10";
//1,創建OKHttpClient對象
OkHttpClient mOkHttpClient = new OkHttpClient();
//2,創建一個Request
Request request = new Request.Builder().url(url).build();
//3,創建一個call對象
Call call = mOkHttpClient.newCall(request);
//4,將請求添加到調度中
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
if (response.isSuccessful()) {
final String message = response.body().string();
handler.post(new Runnable() {
@Override
public void run() {
tv_message.setText(message);
progressBar.setVisibility(View.GONE);
}
});
}
}
});
『陸』 Okhttp3源碼分析
在OkHttp3中,其靈活性很大程度上體現在可以 intercept 其任意一個環節,而這個優勢便是okhttp3整個歲春請求響應架構體系的精髓所在,先放出一張主框架請求流程圖,接著再分析源碼。
這大概是一個最簡單的一個例子了,在 new OkHttpClient() 內部使用構造器模式初始化了一些配置信息:支持協議、任務分發器(其內部包含一個線程池,執行非同步請求)、連接池(其內部包含一個線程池,維護connection)、連接/讀/寫超時時長等信息。
第一行創建了一個 Dispatcher 任務調度器,它定義了三個雙向任務隊列,兩個非同步隊列:准備執行的請求隊列 readyAsyncCalls 、正在運行的請求隊列 runningAsyncCalls ;一個正在運行的同步請求隊列 runningSyncCalls ;
另外還有一個線程池 executorService ,這個線程池跟Android中的CachedThreadPool非常類似,這種類型的線程池,適用於大量的耗時較短的非同步任務。 下一篇文章 將對OkHttp框架中的線程池做一個總結。
接下來接著看Request的構造,這個例子Request比較簡單,指定了請求方式 GET 和請求 url
緊接著通過 OkHttpClient 和 Request 構造一個 Call 對象,它的實現是 RealCall
可以看到在 RealCall 的構造方法中創建了一個 RetryAndFollowUpInterceptor ,用於處理請求錯誤和重定向等,這是 Okhttp 框架的精髓 interceptor chain 中的一環,默認情況下也是第一個攔截器,除非調用 OkHttpClient.Builder#addInterceptor(Interceptor) 來添加全局的攔截器。關於攔截器鏈的順序參見 RealCall#() 方法。
可以看到,一個 Call 只能執行一次,否宏祥則會拋異常,這里創建了一個 AsyncCall 並將Callback傳入,接著再交給任務分發器 Dispatcher 來進一步處理。
從 Dispatcher#enqueue() 方法的策略可以看出,對於請求的入隊做了一些限制,若正在執行的請求數量小於最大值(默認64),並且此請求所屬主機的正在執行任務小於最大值(默認5),就加入正在運行的隊列並通過線程池來執行該任務,否則加入准備執行隊列中。
現在回頭看看 AsyncCall ,它繼承自 NamedRunnable ,而 NamedRunnable 實現了 Runnable 介面,它的作用有2個:
①採用模板方法的設計模式,讓子類將具體的操作放在 execute() 方法中;
②給線程指定一個名字,比如傳入模塊名稱,方便監控線程的活動狀態;
先看注釋②的行finally塊中執行的 client.dispatcher().finished(this)
其中 promoteCalls() 為推動下一個任務執行,其實它做的也很簡單,就是在條件滿足的情況下,將 readyAsyncCalls 中的任務移動到 runningAsyncCalls 中,並交給線程池來蔽雀搏執行,以下是它的實現。
接下來就回到注釋①處的響應內容的獲取 ()
可以看這塊重點就是 interceptors 這個集合,首先將前面的 client.interceptors() 全部加入其中,還有在創建 RealCall 時的 retryAndFollowUpInterceptor 加入其中,接著還創建並添加了 BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor ,最後通過 RealInterceptorChain#proceed(Request) 來執行整個 interceptor chain ,可見把這個攔截器鏈搞清楚,整體流程也就明朗了。
從這段實現可以看出,是按照添加到 interceptors 集合的順序,逐個往下調用攔截器的intercept()方法,所以在前面的攔截器會先被調用。這個例子中自然就是 RetryAndFollowUpInterceptor 了。
這個攔截器就如同它的名字 retry and followUp ,主要負責錯誤處理和重定向等問題,比如路由錯誤、IO異常等。
接下來就到了 BridgeInterceptor#intercept() ,在這個攔截器中,添加了必要請求頭信息,gzip處理等。
這個攔截器處理請求信息、cookie、gzip等,接著往下是 CacheInterceptor
這個攔截器主要工作是做做緩存處理,如果有有緩存並且緩存可用,那就使用緩存,否則進行調用下一個攔截器 ConnectionInterceptor 進行網路請求,並將響應內容緩存。
這個攔截器主要是打開一個到目標伺服器的 connection 並調用下一個攔截器 CallServerInterceptor ,這是攔截器鏈最後一個攔截器,它向伺服器發起真正的網路請求。
從上面的請求流程圖可以看出,OkHttp的攔截器鏈可謂是其整個框架的精髓,用戶可傳入的 interceptor 分為兩類:
①一類是全局的 interceptor,該類 interceptor 在整個攔截器鏈中最早被調用,通過 OkHttpClient.Builder#addInterceptor(Interceptor) 傳入;
②另外一類是非網頁請求的 interceptor ,這類攔截器只會在非網頁請求中被調用,並且是在組裝完請求之後,真正發起網路請求前被調用,所有的 interceptor 被保存在 List<Interceptor> interceptors 集合中,按照添加順序來逐個調用,具體可參考 RealCall#() 方法。通過 OkHttpClient.Builder#addNetworkInterceptor(Interceptor) 傳入;
相關閱讀
『柒』 OkHttp之連接
盡管你只提供了URL,OkHttp在規劃連接伺服器的連李乎卜接時使用了三種類型:URL,Address和Route。
URLs是HTTP和網路的最基本哪穗的。除了作為一個通用的,網路資源的分散命名機制,它們也規定了如何訪問網路資源。
URLs是抽象的:
它們也是具體的:每一個URL確定一個特定路徑(像/square/okhttp)和查詢(像頃慎?q=sharks&lang=en)。每個伺服器有很多URL。
Addresses規定了伺服器(像github.com)和所有連接伺服器需要的靜態配置:埠號,HTTPS設置和優先網路協議(像HTTP/2或SPDY)。
共享相同address的URLs也可能共享相同的下層TCP socket連接。共享一個連接有巨大的性能好處:低延遲,高吞吐量(因為TCP啟動慢)和節省電源。OkHttp使用ConnectionPool來自動復用HTTP/1.X連接和多路傳輸HTTP/2和SPDY連接。
在OkHttp中,address的一些欄位來自URL(機制,主機名,埠),剩下的來自OkHttpClient。
Routes提供了真正連接到伺服器所需要的動態信息。這是明確的要嘗試的IP地址(通過DNS查詢發現),明確的要使用的代理伺服器(如果使用了ProxySelector)以及什麼版本的TLS來協商(針對HTTPS連接)。
對於一個地址有可能有很多路由,一個存在多個數據中心的網路伺服器可能在它的DNS響應中產生多個IP地址。
當你使用OkHttp請求一個URL時,下面是它所做的:
當連接出現問題時,OkHttp會選擇另外一個route進行嘗試。這使得OkHttp可以在伺服器部分地址無法訪問時恢復。它同時對於當連接池過期或嘗試的TLS版本不支持時有用。
一旦接收到響應,連接就會返回到池中,這樣它可以在之後的請求復用。連接空閑一段時間會從池中移除。
原文鏈接:
https://github.com/square/okhttp/wiki/Connections
OkHttp官方文檔系列文章:
『捌』 安卓okhttp和通知創建方法好像啊
OkHttp是一種HTTP客戶端,它可以用於創建和發送HTTP請求,以及接收和處理HTTP響應。它是用Java編寫的,可以在Android和Java應用程序茄睜中輕松使用。OkHttp可以用於構建高性能、可靠的HTTP客戶端,可以改善網路連接的速度和可靠性。OkHttp可以用來支持像HTTP/2和WebSocket這樣的新協議,以及支持身份驗證、GZIP壓縮和緩存的一些高級功能。
通知是指一種從應用程序發出的消息,可以提示用戶關於特定事件或狀態的變化。它可以被顯示在不同位置,以提醒用戶注意,例如在狀態欄、桌大配面小部件和應用程序內部。它們可以使用多種不同的格式,包括文本、圖像、音頻和視頻。
通知的創建方法取決於你使用的開發框架,但基本流程滾納指大致相同。首先,需要創建一個通知對象,並使用該對象中的方法配置它,例如設置標題、文本和圖標。然後,需要使用推送服務將通知發送到指定的目標設備。最後,需要設置一個回調,以便在通知被接收和處理之後執行某些操作。
『玖』 網路請求框架-OkHttp原理解析
okhttp是square公司貢獻的一個處理網路請求的開源框架,是目前Android開發使用最廣泛的一個網路框架,從Android4.4開始,httpURLconnection的底層實現採用的就是okhttp。內部實現就是利用java基礎,對socket進行封裝,實現http通信。最重要的兩個關鍵點就是分發器和5個攔截器。
分發器 就是內部維護隊列和線程池,完成請求分配,總結就是用於對非同步任務加入隊列管理,然後判斷條件,控制數量,加入線程池執行非同步請求任務。
五個默認攔截器 就是利用責任鏈模式對網路請求進行層層處理,完成整個請求過程,簡單總結如下。
1.橋接攔截器對用戶發出的請求添加缺少的請求配置欄位,比如keep-alive等
2.緩存攔截器就是查詢有沒有符合判斷條件的已緩存的網路請求,執行復用,直接返回response
3.連接攔截器就是創建請求,加入連接器 或者訪問連接池,根據條件判斷,是否能懟已創建的tcp請求進行復用
4.請求伺服器攔截器就是對scoket進行操作,請求網路訪問伺服器,返回response,
5.重試和重定向攔截器就是對返回的response進行code判斷,決定是否要重試或者重定向操作。
1.支持http2.0版本,並且允許對同一主機的所有請求共享一個套接字
2.即使不是http2.0版本,通過連接池,減少請求延遲
3.默認使用Gzip 壓縮數據
4.響應緩存,避免重復請求網路
最簡單的http請求案例
1.利用建造者模式構建okHttpClient實例對象,構建過程中可以動態配置參數,請求時間,響應時間,緩存信息等。
2.創建Request對象,設置請求方式,鏈接地址,參數等信息。
3.把request對象,傳給client,通過newCall函數,得到RealCall對象。
4.RealCall 分為同步和非同步執行
5.同步執行時,分發器只是做個記錄,把請求任務加到隊列中,然後直接通過攔截器訪問伺服器,返回response。
6.非同步執行
6.1先對非同步任務進一步封裝,把任務放到AsyncCall對象中
2.分發器 把 封裝後的非同步任務 添加到等待運行的隊列中
7. 通過攔截器,獲取response
okhttp 默認提供5個攔截器 重試重定向攔截器,橋接攔截器,緩存攔截器,連接攔截器,訪問伺服器攔截器。還可以自定義攔截器。
自定義攔截器分為應用攔截器(通過addInterceptor 添加)和網路攔截器(通過addNetworkInterceptor攔截)
攔截器採用責任鏈的設計默認,讓請求者和處理者解耦,最終請求從前往後,響應從後往前。
首先先判斷用戶是否取消了請求,如果沒有取消,就把請求交個橋接攔截器。
在獲得響應結果response的時候根據響應碼,判斷是否需要重試或者重定向, 重試不限制次數,重定向最多20次 ,如果需要重試或者重定向,那麼會再一次重新執行所有攔截器。
有如下幾種情況不會重試:IO異常,線路異常,配置client實例時配置不允許重試,協議異常,證書異常等等。
先獲取用戶發送的請求,判斷條件用戶是否已經配置過請求頭欄位,若用戶沒有配置,則將http協議必備的請求頭欄位補齊,比如Content-Type,Content-Length等,然後交給下一個攔截器。
在獲得響應結果response之後,調用保存cookie的介面(也可以在配置client的時候,設置cookjar進行cookie回調數據),並且解析gzip數據
獲取結果之後,對cookie進行保存,對返回的數據進行gzip解壓
就是根據緩存策略從緩存中查找是否有合適的緩存response,如果有合適的緩存,直接返回給請求任務,不在繼續執行後面的攔截器。
獲得響應結果response後,根據條件判斷,決定是否要緩存。
維護一個連接池,負責對連接的服務。在把請求交給下一個攔截器之前。會先在連接池中找到一個合適的連接(滿足適配條件相同,並且沒有正在被使用)或者新建一個連接,並且接入連接池,獲得對應的socket流,把請求交給下一個攔截器。獲得response結果後不會進行額外的處理。
連接池, 也稱之為對象池,主要用來存放request請求連接,內部維護了一個LinkedQueue隊列用來存放請求。在添加新的請求對象時,都會執行一個周期性任務,用以對連接池進行清理操作。
1.隊列長度超過5,清理最近未被使用連接,LRE演算法
2.存儲的連接,5分鍾未被復用,清理
拿到上一個攔截器返回的請求,真正的與伺服器進行通信,向伺服器發送數據,解析讀取響應的數據,返回給上一個攔截器。
1.創建request =>OkHttpClient=>RealCall()
2.同步執行 ,分發器添加同步任務,執行攔截器,訪問伺服器,返回reponse,觸發非同步分發流程。
3.非同步執行 ,封裝任務= >AsyncCall ,實現runnable介面。添加任務到非同步任務等待隊列,執行分發任務,判斷非同步任務是否能加入正在執行的非同步任務隊列,滿足兩個條件
同時執行的非同步任務數量不得大於64個
對同一個主機的訪問任務,最多不得大於5個
4.加入正在執行的非同步任務隊列,通過線程池執行任務,經過5個默認攔截器訪問伺服器,返回response,執行非同步任務分發。
分發器工作 分為同步任務和非同步任務兩種
同步任務 就是把任務加入同步任務隊列,加個標記,執行結束之後,觸發非同步任務的分發操作。
非同步任務 先封裝任務到asyncCall對象,實現了runnable介面。把任務加入等待執行隊列,執行分發操作。
先遍歷等待任務隊列,判斷是否符合加入正在運行的非同步任務隊列,要同時滿足兩個條件。
同時執行的非同步任務數量不得大於64個
對同一個主機的訪問任務,最多不得大於5個
當滿足條件後,從等待隊列中刪除任務,把任務加入正在執行的隊列中,通過自定義的線程池,執行任務,任務執行結束後,再次執行分發操作。
攔截器採用了責任鏈設計默認,讓請求者和執行者解耦,請求者只需要將請求發給責任鏈即可,無需關心請求過程和細節。okHttp 默認有5個攔截器,重試重定向攔截器,橋接攔截器,緩存攔截器,連接攔截器,請求服務攔截器。工作細節參考上面攔截器原理分析部分
1.位置的關系,應用攔截器 放在責任鏈最頂端,網路攔截器放在責任鏈倒數第二的位置。所以應用攔截器 最先攔截,最後響應,網路攔截器 倒數第二攔截,第二響應。如果列印請求日誌的情況,應用攔截器列印的是用戶請求信息,經過重試重定向,橋接,緩存,鏈接 等攔截器的層層包裝,網路攔截器列印的是實際請求的信息。
2.應用攔截器一定會被執行,網路攔截器不一定被執行。
利用連接池,緩存所有的有效連接對象。
清理機制:垃圾連接
1.超過5分鍾沒有用過的鏈接
2.超過5個閑置鏈接後,從最久閑置的鏈接開始執行清理(LRU)
『拾』 重識OkHttp——更深入了解如何使用
OkHttp作為square公司出品的一個網路請求框架,應該算是目前Android端最火爆的網路框架了。我公司目前的項目中採用的都是Rxjava結合Retrofit進行網路請求的處理,對於底層真正實現網路請求的OkHttp關注的不是很多。最近探究了一下OkHttp的源碼,對OkHttp的使用有了一些新的認識,在此做一下總結。
OkHttp作為當前Android端最火熱的網路請求框架,必然有很多的優點。
對於客戶端來講,我們關注的就是把正確的請求發送到服務端並拿到結果來進行處理。在OkHttp中,我認為可以分為3個部分:
OkHttp中通過建造者模式來構建OkHttpClient、Request和Response。對於客戶端來講,我們不需要過多關注Response是如何構建的,因為這個是OkHttp對響應結果進行了封裝處理。我們只關注請求Request和客戶端OkHttpClient如何構建即可。
Request採用建造者模式來配置url,請求方法method、header、tag和cacheControl。
OkHttp採用POST方法向伺服器發送一個請求體,在OkHttp中這個請求體是RequestBody。這個請求體可以是:
RequestBody有幾個靜態方法用於創建不同類型的請求體:
最終都是相當於重寫了RequestBody的兩個抽象方法來寫入流,如果傳遞流類型的參數,只要重寫這兩個抽象方法即可。
例如,我們提交一個String:
提交File:
提交流:
對於提交表單和分塊請求,OkHttp提供了兩個RequestBody的子類, FormBody 和 MultipartBody
FormBody也是採用建造者模式, 這個很簡單,添加key-value形式的鍵值對即可。
添加鍵值對有兩個方法:
例如:
MultipartBody也是採用建造者模式,MultipartBody.Builder可以構建兼容Html文件上傳表單的復雜請求體。每一部分的多塊請求體都是它自身的請求體,並且可以定義它自己的請求頭。如果存在的話,這些請求頭用來描述這部分的請求體。例如Content-Disposition、Content-Length 和 Content-Type如果可用就會被自動添加到頭。
MIME類型有:
有幾個主要的方法:
例如提交一個圖片文件:
OkHttpClient採用建造者模式,通過Builder可以配置連接超時時間、讀寫時間,是否緩存、是否重連,還可以設置各種攔截器interceptor等。
建議在一個App中,OkHttpClient保持一個實例。一個OkHttpClient支持一定數量的並發,請求同一個主機最大並發是5,所有的並發最大是64。這個與OkHttp中的調度器Dispatcher有關,可以設置並發數。本文不對Dispatcher進行討論。
一個例子:
OkHttpClient支持單獨配置,例如原來設置不同的請求時間,可以通過OkHttpClient的newBuilder()方法來重新構造一個OkHttpClient。例如:
上面已經講了如何創建Request和OkHttpClient,剩下的就是發送請求並得到伺服器的響應了。OkHttp發送請求可分為同步和非同步。OkHttpClient首先通過Request構建一個Call,通過這個Call去執行同步或者非同步請求。
同步方式,調用Call的execute()方法,返回Response,會阻塞當前線程:
非同步方式,調用Call的enqueue(CallBack callBack)方法,會在另一個線程中返回結果。
為了緩存響應,需要一個可讀寫並且設置大小Size的緩存目錄。緩存目錄需要私有,其它不信任的應用不能訪問這個文件。
如果同時有多個緩存訪問同一個緩存目錄會報錯。所以最好只在App中初始化一次OkHttpClient,給這個實例配置緩存,在整個App生命周期內都用這一個緩存。否則幾個緩存會相互影響,導致緩存出錯,引起程序崩潰。
響應緩存採用Http頭來配置,你可以添加這樣的請求頭 Cache-Control: max-stale=3600 。 max-age 指的是客戶端可以接收生存期不大於指定時間(以 秒 為單位)的響應。
為了防止響應使用緩存,可以用 CacheControl.FORCE_NETWORK 。為了防止使用網路,採用 CacheControl.FORCE_CACHE 。
調用Call.cancel()方法可以立即取消一個網路請求。如果當前線程正在寫request或者讀response會報IO異常。如果不再需要網路請求,採用這種方法是比較方便的。例如在App中返回了上一頁。無論是同步還是非同步的請求都可以被取消。
可以通過Response的code來判斷請求是否成功,如果伺服器返回的有數據,可以通過Response的body得到一個ResponseBody讀取。
如果採用ResponseBody的string()方法會一次性把數據讀取到內存中,如果數據超過1MB可能會報內存溢出,所以對於超過1MB的數據,建議採用流的方式去讀取,如ResponseBody的byteStream()方法。
需要說明的是:
OkHttp中的很多類都用到了建造者模式,可以根據需要靈活配置。採用建造者模式的有:
如果單獨使用OkHttp進行網路請求,通常需要開發者自己再封裝一下,如果不想重復造輪子,Github上面的有一些優秀開源庫可以拿來使用(本文只列出star較多的幾個):
OkHttp官方Wiki文檔