1. 程式人生 > >OKHttp 官方文件【一】

OKHttp 官方文件【一】

最近工作比較忙,文章更新出現了延時。雖說寫技術部落格最初主要是寫給自己,但隨著文章越寫越多,現在更多的是寫給關注我技術文章的小夥伴們。最近一段時間沒有更新文章,雖有工作生活孩子佔用了大部分時間的原因,但也有自身的懶惰,這裡向小夥伴們也向自己說一聲抱歉... OkHttp 是這幾年比較流行的 Http 客戶端實現方案,其支援HTTP/2、支援同一Host 連線池複用、支援Http快取、支援自動重定向 等等,有太多的優點。 一直想找時間瞭解一下 OkHttp 的實現原理 和 具體原始碼實現,不過還是推薦在使用 和 瞭解其原理之前,先通讀一遍 OkHttp 的官方文件,由於官方文件為英文,我在通讀的時候,順便翻譯了一下,`如翻譯有誤,請幫忙指正`。 OkHttp官方API地址: [https://square.github.io/okhttp/](https://square.github.io/okhttp/) ## 一、概述 Http是現在流行的應用程式請求方法。Http幫助我們交換資料和多媒體內容。有效地執行HTTP可以使您的內容載入更快,並節省頻寬。 OkHttp 是一個執行效率比較高的Http客戶端: + 支援HTTP/2 ,當多個請求對應同一host地址時,可共用同一個socket; + 連線池可減少請求延遲(如果HTTP/2不可用); + 支援GZIP壓縮,減少網路傳輸的資料大小; + 支援Response資料快取,避免重複網路請求; 當網路出現問題時,OkHttp會不斷重試: OkHttp將從常見的連線問題中靜默恢復您的網路請求;如果您的服務具有多個IP地址,則在第一次連線失敗時,OkHttp將嘗試使用備用地址,這對於IPv4 + IPv6、減少伺服器的資料駐留是必需的;OkHttp支援TLS功能(TLS 1.3, ALPN, certificate pinning),OkHttp可將其配置回退以獲得廣泛的連線性。 使用OkHttp很容易,OkHttp的請求/響應API使用builder方式構建,OkHttp支援同步阻塞呼叫、非同步回撥呼叫。 ### 1.1、Get a URL Get方法請求一個url地址,並將response結果打印出來:[完整的Http Get請求舉例](https://raw.github.com/square/okhttp/master/samples/guide/src/main/java/okhttp3/guide/GetExample.java) ```kotlin OkHttpClient client = new OkHttpClient(); String run(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); try (Response response = client.newCall(request).execute()) { return response.body().string(); } } ``` ## 1.2、Post to a Server 向伺服器發起Post請求;[完整的Http Post請求舉例](https://raw.github.com/square/okhttp/master/samples/guide/src/main/java/okhttp3/guide/PostExample.java) ```kotlin public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(json, JSON); Request request = new Request.Builder() .url(url) .post(body) .build(); try (Response response = client.newCall(request).execute()) { return response.body().string(); } } ``` ## 1.3、Requirements OkHttp執行環境為 `Android 5.0+ (API level 21+) 、Java 8+`; OkHttp 3.12.x分支執行環境為`Android 2.3+(API level 9+)、Java 7+`; OkHttp依賴高效能I/O庫[Okio](https://github.com/square/okio),依賴Kotlin library使用Kotlin開發語言;這兩個依賴庫很小,並有很強的向後相容; 我們強烈建議你保持使用OkHttp最新版本。與自動更新的Web瀏覽器一樣,保持HTTPS客戶端的最新狀態是防範潛在安全問題的重要防禦措施。我們跟蹤有危險的TLS生態系統並調整OkHttp以改善連線性和安全性。 OkHttp當前使用平臺的內建TLS實現。 在Java平臺上,OkHttp還支援[Conscrypt](https://github.com/google/conscrypt/),它將BoringSSL與Java整合在一起。如果Conscrypt是最安全的SSL提供程式,OkHttp將使用Conscrypt。 OkHttp 3.12.x分支執行環境為`Android 2.3+(API level 9+)、Java 7+`。OkHttp 3.12.x不支援TLS 1.2,因此不推薦使用。因為升級困難,我們將在2021年12月31日之前,向3.12.x分支新增向後相容的補丁程式。 ```java Security.insertProviderAt(Conscrypt.newProvider(), 1); ``` ### 1.4、Releases release history可參考 [https://square.github.io/okhttp/changelog/](https://square.github.io/okhttp/changelog/) 最新阪本已上傳 Maven Central。 ```java implementation("com.squareup.okhttp3:okhttp:4.8.0") ``` OkHttp支援R8壓縮、混淆規則: [https://square.github.io/okhttp/r8_proguard/](https://square.github.io/okhttp/r8_proguard/) 在一個使用了OkHttp依賴包的Android工程中,如果你使用了預設的R8壓縮演算法,你不用為引入Okhttp而多做任何事情。特定的規則已經整合到OkHttp提供的JAR包中,這些規則支援R8自動解析。 但是,如果你使用的不是R8,則必須應用[以下混淆規則](https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro);你可能還需要新增Okio相關混淆規則,因為OkHttp使用了Okio依賴庫。 ``` # JSR 305 annotations are for embedding nullability information. -dontwarn javax.annotation.** # A resource is loaded with a relative path so the package of this class must be preserved. -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. -dontwarn org.codehaus.mojo.animal_sniffer.* # OkHttp platform used only on JVM and when Conscrypt dependency is available. -dontwarn okhttp3.internal.platform.ConscryptPlatform ``` ### 1.5、MockWebServer OkHttp includes a library for testing HTTP, HTTPS, and HTTP/2 clients. OkHttp包含一個測試HTTP、HTTPS、HTTP/2的客戶端工程。 最新測試工程已上傳 [Maven Central](https://search.maven.org/artifact/com.squareup.okhttp3/mockwebserver/4.8.0/jar) ``` testImplementation("com.squareup.okhttp3:mockwebserver:4.8.0") ``` ### 1.6、 軟體開源許可協議 詳細瞭解 Apache License 2.0許可協議,可參考我的文章:[https://blog.csdn.net/xiaxl/article/details/106137088](https://blog.csdn.net/xiaxl/article/details/106137088) ``` Copyright 2019 Square, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ## 二、Calls HTTP客戶端用於接收Http request請求,並響應Http response資料。理論原理並不複雜,但在是實現上確實比較難。 ### 2.1、Requests 每一個 HTTP 請求包含一個`請求地址URL`、`請求方法(如GET或POST)`、`請求Headers`,很多請求可能包含`請求 Body` (特定內容型別的資料流); ### 2.2、Responses 響應資料包含 `響應狀態碼`(如200表示成功或404表示未找到)、`響應Headers`、`可選的響應Body`; ### 2.3、Rewriting Requests 當你使用OkHttp發起HTTP請求時,你的思想處於這樣一個高度 “*使用這個URL地址 和這些請求Headers獲取資料*”。為了保證效率和正確性,OkHttp在傳輸請求之前會先對其進行重寫。 OkHttp可以新增原始請求中缺少的Header,包括Content-Length、Transfer-Encoding、User-Agent、Host、Connection、Content-Type;如Accept-Encoding不存在,OkHttp會新增Header用於壓縮傳輸的Accept-Encoding;如果你獲取過cookies,OkHttp將自動將cookies新增到header中。 一些請求支援response資料快取。當快取的response資料過期時,OkHttp在一定條件下可以發起一個GET請求,以獲取新的response資料。這類請求的headers,例如`If-Modified-Since`、`If-None-Match`會被新增。 ### 2.4、Rewriting Responses 如果資料傳輸中使用壓縮演算法,OkHttp將刪除相應的響應Header 如Content-Encoding、Content-Length,因為它們不適用於解壓縮的response body。 如果conditional GET 成功,則會按照規範將 network與cache 的響應資料進行合併。 ### 2.5、Follow-up Requests 如果你請求的 URL 已被重定向,在發生網路請求時,webserver將會返回一個 302 響應碼,此302響應碼用來標識重定向後新的 URL 請求地址。OkHttp將會自動重定向,並獲取最終的response資料。 如果請求的響應資訊提示要求進行 authorization 授權挑戰,OkHttp將會安全的完成授權挑戰(如果此時挑戰資訊已配置);如果認證器支援證書,OkHttp將使用內建證書進行重試; ### 2.6、Retrying Requests 有時連線失敗: 無論是本地連線池原因,還是網路原因造成webserver不可達,當網路條件可用時,OkHttp將會重試 ### 2.7、Calls 通過 rewrites、redirects、follow-ups、retries,你的請求可能會產生許多中間請求和響應資料。OkHttp使用API `Call`來建立Request請求,為了保證請求的安全性,許多中間請求和響應是必不可少的。通常,中間請求不多!但是,值得高興的是,如果你的URLs被重定向,或你的伺服器出現故障,該請求將會被重試。 `Calls` 會以以下兩種方式執行: + `同步執行:`執行執行緒會被阻塞,直到response資料返回; + `非同步執行:`你可以將請求放在任何執行緒上,響應的回撥資料將在另一個執行緒中獲取。 `Calls` 可以在任何執行緒中被取消掉,這將倒是請求失敗,如果請求尚未完成。當取消請求時,寫request body或讀response body位置處將丟擲IOException異常。 ### 2.8、Dispatch 對於同步呼叫,您需要自己建立執行執行緒,並負責控制您發出的請求數量, 同時連線過多會浪費資源, 太少會造成延遲; 對於非同步呼叫,`Dispatcher`預設請求策略為: 每個webserver伺服器預設最大請求數量預設為5,整體的最大請求數量為64,並且這兩個值使用者可自行定義。 ## 三、Caching OkHttp網路快取預設是關閉的,使用者可以選擇開啟。OkHttp實現網路快取功能依賴的是`RFC`標準,存在模糊定義的情況下,以當前比較流行的瀏覽器軟體`Firefox/Chrome`為準。 ### 3.1、Basic Usage ```kotlin private val client: OkHttpClient = OkHttpClient.Builder() .cache(Cache( directory = File(application.cacheDir, "http_cache"), // $0.05 worth of phone storage in 2020 maxSize = 50L * 1024L * 1024L // 10 MiB )) .build() ``` ### 3.2、EventListener events 快取的回撥事件API為`EventListener`,典型場景如下: #### 3.2.1、Cache Hit 在理想情況下,快取資料可以完全滿足對應的request請求,而無需發起任何網路請求。應用Http網路快取資料後,將跳過常規網路請求事件,如DNS解析、連線到網路以及下載response資料。 根據HTTP RFC的建議,基於“Last-Modified”,文件的最長過期時間預設為文件計劃服務時間的10%。預設過期日期不應用於查詢的URI。 + CallStart + CacheHit + CallEnd #### 3.2.2、Cache Miss 快取未命中時,可以看到正常的網路請求,但回撥事件顯示快取存在。根據響應headers,如果資料未從網路中獲取、不可快取、快取過期,則快取未命中很常見。 + CallStart + CacheMiss + ProxySelectStart + … Standard Events … + CallEnd #### 3.2.3、Conditional Cache Hit 當需要檢查快取結果仍然有效時,跟隨在`cachehit` 或 `miss`後,會收到一個`cacheConditionalHit`事件。然後是快取命中或未命中。 至關重要的是,在快取命中的情況下,伺服器不會發送響應正文。重要的是,在快取命中的情況下,不會 server 不會發送response body 資料。 The response will have non-null cacheResponse and networkResponse. The cacheResponse will be used as the top level response only if the response code is HTTP/1.1 304 Not Modified. 在 HTTP/1.1 伺服器返回的Response 為 304 Not Modified情況下,請求的response將返回非空的 `cacheResponse` 和 `networkResponse`。cacheResponse 的優先順序最高。 + CallStart + CacheConditionalHit + ConnectionAcquired + … Standard Events… + ResponseBodyEnd (0 bytes) + CacheHit + ConnectionReleased + CallEnd #### 3.2.4、Cache directory 快取目錄必須有且僅有一個單例類持有。 可以在不再需要快取時刪除它,但這可能會刪除App重啟之前保持不變的快取。(Deleting the cache when it is no longer needed can be done. However this may delete the purpose of the cache which is designed to persist between app restarts.) ``` cache.delete() ``` #### 3.2.5、Pruning the Cache 可以使用 `evictAll` 刪除整個快取 ``` cache.evictAll() ``` 可以使用 url 迭代方式,刪除某個單獨的Item。典型的應用場景是,使用者通過 `下拉重新整理(pull to refresh)`強制啟動一個重新整理動作。 ``` val urlIterator = cache.urls() while (urlIterator.hasNext()) { if (urlIterator.next().startsWith("https://www.google.com/")) { urlIterator.remove() } } ``` #### 3.2.6、Troubleshooting *1、有效的,可快取的 responses 資料,未被快取(Valid cacheable responses are not being cached)* 確保完全讀取 responses 響應資料,除非完全讀取響應資料 或 請求被取消。 #### 3.2.7、Overriding normal cache behaviour See Cache documentation. [https://square.github.io/okhttp/4.x/okhttp/okhttp3/-cache/](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-cache/) ## 四、Connections 儘管你只提供了 URL ,但 OkHttp 規劃與對應伺服器(webserver)的網路連線(connection)時,使用以下三種類型:URL、Address、and Route. ### 4.1、URLs URL( 如 https://github.com/square/okhttp )是 HTTP 和 Internet 的基礎。除了針對網路上所有內容的通用,還規定了如何訪問 web 資源。 URLs are abstract: + 指定請求(call)可以是明文 ( http ) 或加密 ( https ),但不指定應該使用哪種加密演算法。也沒有指定如何驗證對等方的證書(HostnameVerifier)或哪些證書可以信任(SSLSocketFactory); + 沒有指定是否應使用特定的代理伺服器或如何向該代理伺服器進行身份驗證; 每個 URL 標識一個特定的路徑( 如 /square/okhttp )和 查詢(如 ?q=sharks&lang= en),每個 webserver 管理許多URL。 ### 4.2、Addresses Addresses 指定一個 webserver (如 github.com) 和 連線到該伺服器所需的所有靜態配置:埠號、HTTPS設定、首選網路協議(如 HTTP/2、SPDY)。 多個 URL 共享同一個 address,也可能共享相同的 TCP socket 連線;共享連線具有顯著的效能優勢:更低的延遲、更高的吞吐量(由於 TCP 連線建立緩慢)、節省電量。 OkHttp使用一個 ConnectionPool,它可以自動重用 HTTP / 1.x 連線並多路複用 HTTP/2、SPDY連線。 在OkHttp中,地址的某些欄位來自URL(scheme、hostname、port),其餘部分來自OkHttpClient ### 4.3、Routes Routes 提供實際連線到 webserver 所需的動態資訊。特定IP地址(由 DNS 查詢獲取)、要使用的確切代理伺服器(如果正在使用 ProxySelector )、要協商的TLS版本(用於HTTPS連線)。 一個 address 可能有很多 routes。 例如,一個 webserver 可以託管在多個數據中心中,DNS查詢時可以產生多個IP地址。 ### 4.4、Connections 當你使用 OkHttp 向某個 URL 發起一個 Request 網路請求時,OkHttp做了以下幾件事: + OkHttp 使用URL,並配置 OkHttpClient 來建立一個 address。 此 address 指定了我們如何連線到 webserver ; + OkHttp 嘗試從連線池中檢索該 address 的連線; + 如果在池中找不到對應的連線,則選擇一個路由進行嘗試。 這通常意味著,建立一個DNS請求,以獲取伺服器的IP地址; 然後根據需要選擇TLS版本和代理伺服器; + 如果是新建一個路由,則可以通過建立 socket 連線、TLS隧道(基於HTTP代理的HTTPS),直接通過 TLS 建立隧道進行連線。如果有必要會進行TLS握手; + 傳送Http請求 和 讀取 Response 資料; 如果連接出現問題,OkHttp 將選擇其他路由進行重試, 這樣當一部分伺服器無法訪問時,OkHttp可以恢復使用; 當共用連線失效 或 TLS版本不受支援時,此功能也很有用。 一旦請求的 response 資料返回到客戶端,這個connection 將被釋放返回到連線池中,以保證該 這個connection 可以被其他請求複用。閒置一段時間後,連線將從池中退出。 ## 五、Events Events 可以讓你獲取應用程式執行中HTTP的狀態,使用 Events 來監聽狀態變化: + 應用程式發出的HTTP請求的的數量和頻率。 如果您發起了太多的Http請求,或者您的請求的內容太大,那麼您應該知道這些回撥資料! + 網路請求的效能。 如果網路的效能不足,則需要改善網路或減少使用。 ### 5.1、EventListener Subclass EventListener and override methods for the events you are interested in. In a successful HTTP call with no redirects or retries the sequence of events is described by this flow. 重寫介面 EventListener 中,你感興趣的的方法。在一次成功的Http請求獲取中,沒有重定向或重試的前提下,事件的呼叫順序如下圖所示: ![執行順序](https://upload-images.jianshu.io/upload_images/5969042-f8eded2ee7123708?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 以下為 EventListener 使用舉例: ```java class PrintingEventListener extends EventListener { private long callStartNanos; private void printEvent(String name) { long nowNanos = System.nanoTime(); if (name.equals("callStart")) { callStartNanos = nowNanos; } long elapsedNanos = nowNanos - callStartNanos; System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name); } @Override public void callStart(Call call) { printEvent("callStart"); } @Override public void callEnd(Call call) { printEvent("callEnd"); } @Override public void dnsStart(Call call, String domainName) { printEvent("dnsStart"); } @Override public void dnsEnd(Call call, String domainNa