OkHttp 官方中文文档
OkHttp官方中文文檔
本文結(jié)構(gòu)
- Calls
- Connections
- Recipes
- Interceptors
- HTTPS
本文翻譯來(lái)自 官方OkHttp Wiki
- OkHttp官方中文文檔
- 一Calls
- 1 請(qǐng)求
- 2 響應(yīng)
- 3重寫(xiě)請(qǐng)求
- 4重寫(xiě)響應(yīng)
- 5后續(xù)請(qǐng)求
- 6請(qǐng)求重試
- 7 呼叫
- 8調(diào)度
- 二Connections
- 1URLs
- URLs摘要
- 2 Addresses
- 3 Routes
- 4Connections
- 1URLs
- 三Recipes
- 1同步獲取
- 2異步獲取
- 3訪問(wèn)頭
- 4Posting a String
- 5 Post Streaming
- 6 Posting a File
- 7 發(fā)布表單參數(shù)
- 8 發(fā)布multipart請(qǐng)求
- 9 通過(guò)GSON解析響應(yīng)的JSON
- 10 響應(yīng)緩存
- 11 取消Call
- 12 超時(shí)
- 13 每個(gè)呼叫配置
- 14 認(rèn)證處理
- 四攔截器
- 1 應(yīng)用攔截器
- 2 網(wǎng)絡(luò)攔截器
- 3 應(yīng)用程序和網(wǎng)絡(luò)攔截之間進(jìn)行選擇
- 應(yīng)用攔截器
- 網(wǎng)絡(luò)攔截器
- 4重寫(xiě)請(qǐng)求
- 5 重寫(xiě)響應(yīng)
- 6 可用性
- 五 HTTPS
- 1證書(shū)釘扎
- 2定制信任證書(shū)
- 一Calls
一、Calls
HTTP客戶(hù)端的工作是接受你的request,并產(chǎn)生它的response。這個(gè)在理論上是簡(jiǎn)單的,但在實(shí)踐中確是很棘手。
1.1 請(qǐng)求
每一個(gè)HTTP請(qǐng)求中都包含一個(gè)URL,一個(gè)方法(如GET或POST),和一個(gè)請(qǐng)求頭列表(headers)。請(qǐng)求還可以含有一個(gè)請(qǐng)求體(body):一個(gè)特定內(nèi)容類(lèi)型的數(shù)據(jù)流。
1.2 響應(yīng)
每一個(gè)HTTP響應(yīng)中都包含一個(gè)狀態(tài)碼(如200代表成功,404代表未找??到),一個(gè)響應(yīng)頭列表(headers)和一個(gè)可選的響應(yīng)體(body)。
1.3重寫(xiě)請(qǐng)求
當(dāng)你的OkHttp發(fā)送一個(gè)HTTP請(qǐng)求,你在描述一個(gè)高層次的要求:“給我獲取這個(gè)網(wǎng)址中的這些請(qǐng)求頭。”對(duì)于正確性和效率,OkHttp發(fā)送前會(huì)重寫(xiě)你的請(qǐng)求。
OkHttp可以在原先的請(qǐng)求中添加請(qǐng)求頭(headers),包括Content-Length, Transfer-Encoding, User-Agent, Host, Connection, 和 Content-Type。除非請(qǐng)求頭已經(jīng)存在壓縮響應(yīng),否則它還將添加一個(gè)Accept-Encoding請(qǐng)求頭。如果你有cookies,OkHttp還將添加一個(gè)Cookie請(qǐng)求頭。
一些請(qǐng)求會(huì)有一個(gè)緩存的響應(yīng)。當(dāng)這個(gè)緩存的響應(yīng)不是最新的時(shí)候,OkHttp會(huì)發(fā)送一個(gè)有條件的GET來(lái)下載更新的響應(yīng),如果它比緩存還新。它將會(huì)添加需要的請(qǐng)求頭,如IF-Modified-Since和If-None-Match。
1.4重寫(xiě)響應(yīng)
如果使用的是透明壓縮,OkHttp會(huì)丟失相應(yīng)的響應(yīng)頭Content-Encoding和Content-Length,這是因?yàn)樗鼈儾荒苡糜诮鈮喉憫?yīng)體(body)。
如果一個(gè)條件GET是成功的,在指定的規(guī)范下,響應(yīng)來(lái)自于網(wǎng)絡(luò)和緩存的合并。
1.5后續(xù)請(qǐng)求
當(dāng)你的請(qǐng)求的URL已經(jīng)移動(dòng),Web服務(wù)器將返回一個(gè)響應(yīng)碼像302,以表明本文檔的新的URL。OkHttp將按照重定向檢索最終響應(yīng)。
如果響應(yīng)問(wèn)題是一個(gè)的授權(quán)盤(pán)問(wèn),OkHttp將會(huì)要求身份驗(yàn)證(如果有一個(gè)已經(jīng)配置好),以滿(mǎn)足盤(pán)問(wèn)。如果身份驗(yàn)證提供憑據(jù),請(qǐng)求將會(huì)帶著憑證進(jìn)行重試。
1.6請(qǐng)求重試
有時(shí)連接失敗:要么是連接池已經(jīng)過(guò)時(shí)和斷開(kāi),或是Web服務(wù)器本身無(wú)法達(dá)成。如果有一個(gè)是可用的,OkHttp將會(huì)使用不同的路由進(jìn)行請(qǐng)求重試。
1.7 呼叫
隨著重寫(xiě),重定向,后續(xù)和重試,你簡(jiǎn)單的要求可能會(huì)產(chǎn)生很多請(qǐng)求和響應(yīng)。OkHttp使用呼叫(Call)并通過(guò)許多必要的中間請(qǐng)求和響應(yīng)來(lái)滿(mǎn)足你請(qǐng)求的任務(wù)模型。通常情況,這是不是很多!如果您的網(wǎng)址被重定向,或者如果您故障轉(zhuǎn)移到另一個(gè)IP地址,但它會(huì)欣慰的知道你的代碼會(huì)繼續(xù)工作。
通過(guò)以下兩種方式進(jìn)行呼叫:
- 同步:直到響應(yīng),你的線(xiàn)程塊是可讀的。
- 異步:你在任何線(xiàn)程進(jìn)行排隊(duì)請(qǐng)求,并且當(dāng)響應(yīng)是可讀的時(shí)候,你會(huì)在另一個(gè)線(xiàn)程得到回調(diào)。
呼叫(Calls)可以在任何線(xiàn)程中取消。如果它尚未完成,它將作為失敗的呼叫(Calls)!當(dāng)呼叫(Call)被取消的時(shí)候,如果代碼試圖進(jìn)行寫(xiě)請(qǐng)求體(request body)或讀取響應(yīng)體(response body)會(huì)遭受IOException異常。
1.8調(diào)度
對(duì)于同步調(diào)用,你帶上你自己的線(xiàn)程,并負(fù)責(zé)管理并發(fā)請(qǐng)求。并發(fā)連接過(guò)多浪費(fèi)資源; 過(guò)少的危害等待時(shí)間。
對(duì)于異步調(diào)用,調(diào)度實(shí)現(xiàn)了最大同時(shí)請(qǐng)求策略。您可以設(shè)置每個(gè)Web服務(wù)器最大值(默認(rèn)值為5),和整體值(默認(rèn)為64)。
二、Connections
雖然只提供了URL,但是OkHttp計(jì)劃使用三種類(lèi)型連接到你的web服務(wù)器:URL, Address, 和 Route。
2.1URLs
URLs(如https://github.com/square/okhttp)是HTTP和因特網(wǎng)的基礎(chǔ)。除了是網(wǎng)絡(luò)上通用和分散的命名方案,他們還指定了如何訪問(wèn)網(wǎng)絡(luò)資源。
URLs摘要:
- 它們指定該呼叫(Call)可以被明文(HTTP)或加密的(HTTPS),但不指定用哪種加密算法。他們也不指定如何驗(yàn)證對(duì)方的證書(shū)(HostnameVerifier)或證書(shū)可以信任(SSLSocketFactory)。
- 他們不指定是否應(yīng)使用特定的代理服務(wù)器或如何與該代理服務(wù)器進(jìn)行身份驗(yàn)證。
他們還具體:每個(gè)URL識(shí)別特定的路徑(如 /square/okhttp)和查詢(xún)(如 ?q=sharks&lang=en)。每個(gè)Web服務(wù)器主機(jī)的網(wǎng)址。
2.2 Addresses
Addresses指定網(wǎng)絡(luò)服務(wù)器(如github.com)和所有的靜態(tài)必要的配置,以及連接到該服務(wù)器:端口號(hào),HTTPS設(shè)置和首選的網(wǎng)絡(luò)協(xié)議(如HTTP / 2或SPDY)。
共享相同地址的URL也可以共享相同的基礎(chǔ)TCP套接字連接。共享一個(gè)連接有實(shí)實(shí)在在的性能優(yōu)點(diǎn):更低的延遲,更高的吞吐量(由于TCP慢啟動(dòng))和保養(yǎng)電池。OkHttp使用的ConnectionPool自動(dòng)重用HTTP / 1.x的連接和多樣的HTTP/ 2和SPDY連接。
在OkHttp地址的某些字段來(lái)自URL(scheme, hostname, port),其余來(lái)自O(shè)kHttpClient。
2.3 Routes
Routes提供連接到一個(gè)網(wǎng)絡(luò)服務(wù)器所必需的動(dòng)態(tài)信息。就是嘗試特定的IP地址(如由DNS查詢(xún)發(fā)現(xiàn)),使用確切的代理服務(wù)器(如果一個(gè)特定的IP地址的ProxySelector在使用中)和協(xié)商的TLS版本(HTTPS連接)。
可能有單個(gè)地址對(duì)應(yīng)多個(gè)路由。例如,在多個(gè)數(shù)據(jù)中心托管的Web服務(wù)器,它可能會(huì)在其DNS響應(yīng)產(chǎn)生多個(gè)IP地址。
2.4Connections
當(dāng)你使用OkHttp進(jìn)行一個(gè)URL請(qǐng)求時(shí),下面是它的操作流程:
如果有連接出現(xiàn)問(wèn)題,OkHttp將選擇另一條route,然后再試一次。這帶來(lái)的好處是當(dāng)一個(gè)服務(wù)器的地址的一個(gè)子集是不可達(dá)時(shí),OkHttp能夠自動(dòng)恢復(fù)。當(dāng)連接池是過(guò)時(shí)或者試圖TLS版本不受支持時(shí),這種方式是很有用的。
一旦響應(yīng)已經(jīng)被接收到,該連接將被返回到池中,以便它可以在將來(lái)的請(qǐng)求中被重用。連接在池中閑置一段時(shí)間后,它會(huì)被趕出。
三、Recipes
我們已經(jīng)寫(xiě)了一些方法,演示了如何解決OkHttp常見(jiàn)問(wèn)題。通過(guò)閱讀他們了解一切是如何正常工作的。可以自由剪切和粘貼這些例子。
3.1同步獲取
下載文件,打印其頭部,并以字符串形式打印其響應(yīng)體。
該string() 方法在響應(yīng)體中是方便快捷的小型文件。但是,如果響應(yīng)體較大(大于1 MIB以上),它會(huì)將整個(gè)較大文件加載到內(nèi)存中,所以應(yīng)該避免string() 。在這種情況下,更傾向于將響應(yīng)體作為流進(jìn)行處理。
private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);Headers responseHeaders = response.headers();for (int i = 0; i < responseHeaders.size(); i++) {System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));}System.out.println(response.body().string());}3.2異步獲取
下載一個(gè)工作線(xiàn)程的文件,當(dāng)響應(yīng)是可讀的時(shí)候,獲取回調(diào)(Callback)。當(dāng)響應(yīng)頭已經(jīng)準(zhǔn)備好后,將產(chǎn)生回調(diào)(Callback)。讀取響應(yīng)體可能一直阻塞。目前OkHttp不提供異步API來(lái)接收響應(yīng)體的部位。
private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();client.newCall(request).enqueue(new Callback() {@Override public void onFailure(Call call, IOException e) {e.printStackTrace();}@Override public void onResponse(Call call, Response response) throws IOException {if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);Headers responseHeaders = response.headers();for (int i = 0, size = responseHeaders.size(); i < size; i++) {System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));}System.out.println(response.body().string());}});}3.3訪問(wèn)頭
典型的HTTP頭工作就像一個(gè)Map<String, String> :每個(gè)字段都有一個(gè)值或無(wú)值。但是,一些頭部(headers)允許多個(gè)值,比如Guava的Multimap。例如,它共同為一個(gè)HTTP響應(yīng)提供多個(gè)Vary頭。OkHttp的API,試圖使這兩種情況下都能舒適使用。
當(dāng)寫(xiě)請(qǐng)求頭,用header(name, value)來(lái)為唯一出現(xiàn)的name設(shè)置value。如果它本身存在值,在添加新的value之前,他們會(huì)被移除。使用addHeader(name, value)來(lái)添加頭部不需要移除當(dāng)前存在的headers。
當(dāng)讀取響應(yīng)頭,用header(name)返回最后設(shè)置name的value。如果沒(méi)有value,header(name)將返回null。可以使用headers(name)來(lái)讀取所有列表字段的值,。
要訪問(wèn)所有的頭部,用Headers類(lèi),它支持索引訪問(wèn)。
private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {Request request = new Request.Builder().url("https://api.github.com/repos/square/okhttp/issues").header("User-Agent", "OkHttp Headers.java").addHeader("Accept", "application/json; q=0.5").addHeader("Accept", "application/vnd.github.v3+json").build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println("Server: " + response.header("Server"));System.out.println("Date: " + response.header("Date"));System.out.println("Vary: " + response.headers("Vary"));}3.4Posting a String
使用HTTP POST的請(qǐng)求體發(fā)送到服務(wù)。下面例子post了一個(gè)markdown文檔到一個(gè)的Web服務(wù)(將markdown作為HTML)。由于整個(gè)請(qǐng)求體是同時(shí)在內(nèi)存中,應(yīng)避免使用此API發(fā)送較大(大于1 MIB)的文件。
public static final MediaType MEDIA_TYPE_MARKDOWN= MediaType.parse("text/x-markdown; charset=utf-8");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {String postBody = ""+ "Releases\n"+ "--------\n"+ "\n"+ " * _1.0_ May 6, 2013\n"+ " * _1.1_ June 15, 2013\n"+ " * _1.2_ August 11, 2013\n";Request request = new Request.Builder().url("https://api.github.com/markdown/raw").post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody)).build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println(response.body().string());}3.5 Post Streaming
在這里,我們POST請(qǐng)求體作為stream。將正在生成請(qǐng)求體的內(nèi)容寫(xiě)入到stream中。下面例子streams直接進(jìn)入 Okio緩沖水槽。你的程序可能更喜歡使用OutputStream,你可以通過(guò)BufferedSink.outputStream()獲得 OutputStream。
public static final MediaType MEDIA_TYPE_MARKDOWN= MediaType.parse("text/x-markdown; charset=utf-8");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {RequestBody requestBody = new RequestBody() {@Override public MediaType contentType() {return MEDIA_TYPE_MARKDOWN;}@Override public void writeTo(BufferedSink sink) throws IOException {sink.writeUtf8("Numbers\n");sink.writeUtf8("-------\n");for (int i = 2; i <= 997; i++) {sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));}}private String factor(int n) {for (int i = 2; i < n; i++) {int x = n / i;if (x * i == n) return factor(x) + " × " + i;}return Integer.toString(n);}};Request request = new Request.Builder().url("https://api.github.com/markdown/raw").post(requestBody).build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println(response.body().string());}3.6 Posting a File
將文件作為請(qǐng)求體是很容易的。
public static final MediaType MEDIA_TYPE_MARKDOWN= MediaType.parse("text/x-markdown; charset=utf-8");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {File file = new File("README.md");Request request = new Request.Builder().url("https://api.github.com/markdown/raw").post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file)).build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println(response.body().string());}3.7 發(fā)布表單參數(shù)
使用FormBody.Builder建立一個(gè)請(qǐng)求體,它就像一個(gè)HTML 的標(biāo)記。Names 和values將使用HTML兼容的表單URL編碼進(jìn)行編碼。
private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {RequestBody formBody = new FormBody.Builder().add("search", "Jurassic Park").build();Request request = new Request.Builder().url("https://en.wikipedia.org/w/index.php").post(formBody).build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println(response.body().string());}3.8 發(fā)布multipart請(qǐng)求
MultipartBody.Builder可以構(gòu)建與HTML文件上傳表單兼容的復(fù)雜請(qǐng)求主體。multipart請(qǐng)求體的每一部分本身就是請(qǐng)求體,并且可以定義自己的頭部。如果存在,這些頭應(yīng)該描述的部分請(qǐng)求體,如它的Content-Disposition。如果Content-Length 和 Content-Type頭部可以使用,則他們會(huì)自動(dòng)添加。
private static final String IMGUR_CLIENT_ID = "...";private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/imageRequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("title", "Square Logo").addFormDataPart("image", "logo-square.png",RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png"))).build();Request request = new Request.Builder().header("Authorization", "Client-ID " + IMGUR_CLIENT_ID).url("https://api.imgur.com/3/image").post(requestBody).build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println(response.body().string());}3.9 通過(guò)GSON解析響應(yīng)的JSON
GSON是實(shí)現(xiàn)JSON和Java對(duì)象之間便利轉(zhuǎn)換的API。這里,我們用它來(lái)解碼從GitHub的API 響應(yīng)的JSON。
需要注意的是ResponseBody.charStream()使用的Content-Type響應(yīng)頭進(jìn)行解碼時(shí),所使用的字符集默認(rèn)為UTF-8 。
private final OkHttpClient client = new OkHttpClient();private final Gson gson = new Gson();public void run() throws Exception {Request request = new Request.Builder().url("https://api.github.com/gists/c2a7c39532239ff261be").build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);Gist gist = gson.fromJson(response.body().charStream(), Gist.class);for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {System.out.println(entry.getKey());System.out.println(entry.getValue().content);}}static class Gist {Map<String, GistFile> files;}static class GistFile {String content;}3.10 響應(yīng)緩存
要緩存響應(yīng),你需要有一個(gè)緩存目錄來(lái)進(jìn)行讀取和寫(xiě)入,并限制緩存的大小。緩存目錄應(yīng)該是私有的,不被信任的應(yīng)用程序不能夠閱讀其內(nèi)容!
多個(gè)緩存同時(shí)訪問(wèn)相同的緩存目錄,這是錯(cuò)誤的。大多數(shù)應(yīng)用程序應(yīng)該調(diào)用一次new OkHttpClient(),在任何地方都使用相同的實(shí)例和自己的緩存配置。否則,這兩個(gè)緩存實(shí)例將踩到對(duì)方,破壞響應(yīng)緩存,這可能使你的程序崩潰。
響應(yīng)緩存使用HTTP頭進(jìn)行配置。您可以添加請(qǐng)求頭Cache-Control: max-stale=3600,這樣OkHttp的緩存就會(huì)遵循他們。你的網(wǎng)絡(luò)服務(wù)器可以通過(guò)自己的響應(yīng)頭配置緩存多長(zhǎng)時(shí)間的響應(yīng),如Cache-Control: max-age=9600。有緩存頭強(qiáng)制緩存的響應(yīng),強(qiáng)制網(wǎng)絡(luò)響應(yīng),或強(qiáng)制使用條件GET驗(yàn)證的網(wǎng)絡(luò)響應(yīng)。
private final OkHttpClient client;public CacheResponse(File cacheDirectory) throws Exception {int cacheSize = 10 * 1024 * 1024; // 10 MiBCache cache = new Cache(cacheDirectory, cacheSize);client = new OkHttpClient.Builder().cache(cache).build();}public void run() throws Exception {Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();Response response1 = client.newCall(request).execute();if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);String response1Body = response1.body().string();System.out.println("Response 1 response: " + response1);System.out.println("Response 1 cache response: " + response1.cacheResponse());System.out.println("Response 1 network response: " + response1.networkResponse());Response response2 = client.newCall(request).execute();if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);String response2Body = response2.body().string();System.out.println("Response 2 response: " + response2);System.out.println("Response 2 cache response: " + response2.cacheResponse());System.out.println("Response 2 network response: " + response2.networkResponse());System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));}使用CacheControl.FORCE_NETWORK可以禁止使用緩存的響應(yīng)。使用CacheControl.FORCE_CACHE可以禁止使用網(wǎng)絡(luò)。警告:如果您使用FORCE_CACHE和響應(yīng)來(lái)自網(wǎng)絡(luò),OkHttp將會(huì)返回一個(gè)504不可滿(mǎn)足請(qǐng)求的響應(yīng)。
3.11 取消Call
通過(guò)Call.cancel()來(lái)立即停止正在進(jìn)行的Call。如果一個(gè)線(xiàn)程目前正在寫(xiě)請(qǐng)求或讀響應(yīng),它還將收到一個(gè)IOException異常。當(dāng)一個(gè)Call不需要時(shí),使用取消Call來(lái)保護(hù)網(wǎng)絡(luò); 例如,當(dāng)用戶(hù)從應(yīng)用程序?qū)Ш诫x開(kāi)。同步和異步調(diào)用可以被取消。
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {Request request = new Request.Builder().url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay..build();final long startNanos = System.nanoTime();final Call call = client.newCall(request);// Schedule a job to cancel the call in 1 second.executor.schedule(new Runnable() {@Override public void run() {System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);call.cancel();System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);}}, 1, TimeUnit.SECONDS);try {System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);Response response = call.execute();System.out.printf("%.2f Call was expected to fail, but completed: %s%n",(System.nanoTime() - startNanos) / 1e9f, response);} catch (IOException e) {System.out.printf("%.2f Call failed as expected: %s%n",(System.nanoTime() - startNanos) / 1e9f, e);}}3.12 超時(shí)
當(dāng)無(wú)法訪問(wèn)查詢(xún)時(shí),將調(diào)用超時(shí)失敗。超時(shí)在網(wǎng)絡(luò)劃分中可以是由于客戶(hù)端連接問(wèn)題,服務(wù)器可用性的問(wèn)題,或兩者之間的任何東西。OkHttp支持連接,讀取和寫(xiě)入超時(shí)。
private final OkHttpClient client;public ConfigureTimeouts() throws Exception {client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).build();}public void run() throws Exception {Request request = new Request.Builder().url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay..build();Response response = client.newCall(request).execute();System.out.println("Response completed: " + response);}3.13 每個(gè)呼叫配置
所有的HTTP客戶(hù)端都在OkHttpClient中配置,這包括代理設(shè)置,超時(shí)和緩存。當(dāng)你需要改變單一Call的配置時(shí),調(diào)用OkHttpClient.newBuilder() 。這將返回共享相同的連接池,調(diào)度和配置的原客戶(hù)端的建造器。在下面的例子中,我們做了500毫秒超時(shí),另外一個(gè)3000毫秒超時(shí)請(qǐng)求。
private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {Request request = new Request.Builder().url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay..build();try {// Copy to customize OkHttp for this request.OkHttpClient copy = client.newBuilder().readTimeout(500, TimeUnit.MILLISECONDS).build();Response response = copy.newCall(request).execute();System.out.println("Response 1 succeeded: " + response);} catch (IOException e) {System.out.println("Response 1 failed: " + e);}try {// Copy to customize OkHttp for this request.OkHttpClient copy = client.newBuilder().readTimeout(3000, TimeUnit.MILLISECONDS).build();Response response = copy.newCall(request).execute();System.out.println("Response 2 succeeded: " + response);} catch (IOException e) {System.out.println("Response 2 failed: " + e);}}3.14 認(rèn)證處理
OkHttp能夠自動(dòng)重試未經(jīng)授權(quán)的請(qǐng)求。當(dāng)響應(yīng)是401 Not Authorized,一個(gè)Authenticator被要求提供憑據(jù)。應(yīng)該建立一個(gè)包含缺少憑據(jù)的新要求。如果沒(méi)有憑證可用,則返回null跳過(guò)重試。
使用Response.challenges()獲得任何認(rèn)證挑戰(zhàn)方案和領(lǐng)域。當(dāng)完成一個(gè)基本的挑戰(zhàn),用Credentials.basic(username, password)編碼請(qǐng)求頭。
private final OkHttpClient client;public Authenticate() {client = new OkHttpClient.Builder().authenticator(new Authenticator() {@Override public Request authenticate(Route route, Response response) throws IOException {System.out.println("Authenticating for response: " + response);System.out.println("Challenges: " + response.challenges());String credential = Credentials.basic("jesse", "password1");return response.request().newBuilder().header("Authorization", credential).build();}}).build();}public void run() throws Exception {Request request = new Request.Builder().url("http://publicobject.com/secrets/hellosecret.txt").build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println(response.body().string());}為了避免驗(yàn)證時(shí)不工作的重試,你可以返回null放棄。例如,當(dāng)這些確切的憑據(jù)已經(jīng)嘗試,您可以跳過(guò)重試:
if (credential.equals(response.request().header("Authorization"))) {return null; //如果我們已經(jīng)使用這些憑據(jù)失敗,不重試}當(dāng)你的應(yīng)用嘗試的次數(shù)超過(guò)了限制的次數(shù)時(shí),你可以跳過(guò)重試:
if (responseCount(response) >= 3) {return null; //如果我們已經(jīng)失敗了3次,放棄。 .}這上面的代碼依賴(lài)于下面的responseCount()方法:
private int responseCount(Response response) {int result = 1;while ((response = response.priorResponse()) != null) {result++;}return result;}四、攔截器
攔截器是一個(gè)強(qiáng)大的機(jī)制,它可以監(jiān)控,重寫(xiě)和重試Calls。下面是一個(gè)簡(jiǎn)單記錄傳出請(qǐng)求和響應(yīng)傳入的攔截器。
class LoggingInterceptor implements Interceptor {@Override public Response intercept(Interceptor.Chain chain) throws IOException {Request request = chain.request();long t1 = System.nanoTime();logger.info(String.format("Sending request %s on %s%n%s",request.url(), chain.connection(), request.headers()));Response response = chain.proceed(request);long t2 = System.nanoTime();logger.info(String.format("Received response for %s in %.1fms%n%s",response.request().url(), (t2 - t1) / 1e6d, response.headers()));return response;} }呼叫chain.proceed(request)是實(shí)現(xiàn)每個(gè)攔截器的的重要組成部分。這個(gè)看起來(lái)簡(jiǎn)單的方法是,所有的HTTP工作情況,產(chǎn)生滿(mǎn)足請(qǐng)求的響應(yīng)。
攔截器可以鏈接。假設(shè)你有一個(gè)可以壓縮和校驗(yàn)的攔截器:你需要確定數(shù)據(jù)是否可以壓縮,然后再執(zhí)行校驗(yàn),或者是先校驗(yàn)然后再壓縮。為了攔截器被調(diào)用,OkHttp使用列表來(lái)跟蹤攔截器,。
4.1 應(yīng)用攔截器
攔截器可以注冊(cè)為應(yīng)用攔截器或網(wǎng)絡(luò)攔截器。我們將使用LoggingInterceptor來(lái)區(qū)別。
通過(guò)在OkHttpClient.Builder上調(diào)用addInterceptor()來(lái)注冊(cè)應(yīng)用程序攔截器:
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new LoggingInterceptor()).build();Request request = new Request.Builder().url("http://www.publicobject.com/helloworld.txt").header("User-Agent", "OkHttp Example").build();Response response = client.newCall(request).execute(); response.body().close();該URL http://www.publicobject.com/helloworld.txt重定向到https://publicobject.com/helloworld.txt,并OkHttp遵循這種自動(dòng)重定向。我們的應(yīng)用攔截器被調(diào)用一次,并且從返回的響應(yīng)chain.proceed()具有重定向:
INFO: Sending request http://www.publicobject.com/helloworld.txt on null User-Agent: OkHttp ExampleINFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms Server: nginx/1.4.6 (Ubuntu) Content-Type: text/plain Content-Length: 1759 Connection: keep-alive我們可以看到,我們被重定向是因?yàn)閞esponse.request().url()不同于request.url() 。這兩個(gè)日志語(yǔ)句記錄兩個(gè)不同的URL。
4.2 網(wǎng)絡(luò)攔截器
注冊(cè)網(wǎng)絡(luò)攔截器很類(lèi)似。調(diào)用addNetworkInterceptor()代替addInterceptor() :
OkHttpClient client = new OkHttpClient.Builder().addNetworkInterceptor(new LoggingInterceptor()).build();Request request = new Request.Builder().url("http://www.publicobject.com/helloworld.txt").header("User-Agent", "OkHttp Example").build();Response response = client.newCall(request).execute(); response.body().close();當(dāng)我們運(yùn)行這段代碼,攔截器運(yùn)行兩次。一個(gè)是初始請(qǐng)求http://www.publicobject.com/helloworld.txt,另一個(gè)是用于重定向到https://publicobject.com/helloworld.txt。
INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1} User-Agent: OkHttp Example Host: www.publicobject.com Connection: Keep-Alive Accept-Encoding: gzipINFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms Server: nginx/1.4.6 (Ubuntu) Content-Type: text/html Content-Length: 193 Connection: keep-alive Location: https://publicobject.com/helloworld.txtINFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1} User-Agent: OkHttp Example Host: publicobject.com Connection: Keep-Alive Accept-Encoding: gzipINFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms Server: nginx/1.4.6 (Ubuntu) Content-Type: text/plain Content-Length: 1759 Connection: keep-alive網(wǎng)絡(luò)請(qǐng)求還包含很多數(shù)據(jù),如OkHttp加入Accept-Encoding: gzip頭部通知支持壓縮響應(yīng)。網(wǎng)絡(luò)攔截器的鏈具有非空的連接,它可用于詢(xún)問(wèn)IP地址和連接到網(wǎng)絡(luò)服務(wù)器的TLS配置。
4.3 應(yīng)用程序和網(wǎng)絡(luò)攔截之間進(jìn)行選擇
每個(gè)攔截器鏈(interceptor chain)都具有相對(duì)優(yōu)勢(shì)。
應(yīng)用攔截器
- 不必?fù)?dān)心像重定向和重試的中間響應(yīng)。
- 總是被調(diào)用一次,即使HTTP響應(yīng)來(lái)自緩存服務(wù)。
- 觀察應(yīng)用程序的原意。不關(guān)心OkHttp注入的頭文件,如 If-None-Match。
- 允許短路和不調(diào)用Chain.proceed() 。
- 允許重試,并多次調(diào)用Chain.proceed() 。
網(wǎng)絡(luò)攔截器
- 能夠操作像重定向和重試的中間響應(yīng)。
- 在短路網(wǎng)絡(luò)上不調(diào)用緩存的響應(yīng)。
- 觀察在網(wǎng)絡(luò)上傳輸?shù)臄?shù)據(jù)。
- 訪問(wèn)Connection承載請(qǐng)求。
4.4重寫(xiě)請(qǐng)求
攔截器可以添加,刪除或替換請(qǐng)求頭。他們還可以轉(zhuǎn)換請(qǐng)求體。例如,如果你連接到已知支持它的網(wǎng)絡(luò)服務(wù)器,你可以使用應(yīng)用程序攔截器添加請(qǐng)求體的壓縮。
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */ final class GzipRequestInterceptor implements Interceptor {@Override public Response intercept(Interceptor.Chain chain) throws IOException {Request originalRequest = chain.request();if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {return chain.proceed(originalRequest);}Request compressedRequest = originalRequest.newBuilder().header("Content-Encoding", "gzip").method(originalRequest.method(), gzip(originalRequest.body())).build();return chain.proceed(compressedRequest);}private RequestBody gzip(final RequestBody body) {return new RequestBody() {@Override public MediaType contentType() {return body.contentType();}@Override public long contentLength() {return -1; // We don't know the compressed length in advance!}@Override public void writeTo(BufferedSink sink) throws IOException {BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));body.writeTo(gzipSink);gzipSink.close();}};} }4.5 重寫(xiě)響應(yīng)
相對(duì)應(yīng)的,攔截器也可以重寫(xiě)響應(yīng)頭和轉(zhuǎn)換響應(yīng)體。通常不要重寫(xiě)請(qǐng)求頭,因?yàn)樗赡苓`反了Web服務(wù)器的期望導(dǎo)致更危險(xiǎn)!
在一個(gè)棘手的情況下,如果已經(jīng)做好應(yīng)對(duì)的后果,重寫(xiě)響應(yīng)頭是解決問(wèn)題的有效方式。例如,您可以修復(fù)服務(wù)器的配置錯(cuò)誤的Cache-Control響應(yīng)頭以便更好地響應(yīng)緩存:
/** Dangerous interceptor that rewrites the server's cache-control header. */ private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {@Override public Response intercept(Interceptor.Chain chain) throws IOException {Response originalResponse = chain.proceed(chain.request());return originalResponse.newBuilder().header("Cache-Control", "max-age=60").build();} };通常此方法效果最好,它補(bǔ)充了在Web服務(wù)器上相應(yīng)的修復(fù)!
4.6 可用性
OkHttp的攔截器需要OkHttp 2.2或更高。不幸的是,攔截器不能與OkUrlFactory工作,或者建立在這之上的庫(kù),包括 Retrofit ≤1.8和 Picasso≤2.4。
五、 HTTPS
OkHttp試圖平衡兩個(gè)相互競(jìng)爭(zhēng)的擔(dān)憂(yōu):
- 連接到盡可能多的主機(jī)越好。這包括運(yùn)行最新版本的先進(jìn)主機(jī)boringssl和運(yùn)行舊版的日期主機(jī)OpenSSL。
- 安全的連接。這包括遠(yuǎn)程Web服務(wù)器證書(shū)的驗(yàn)證和強(qiáng)密碼交換的數(shù)據(jù)隱私。
當(dāng)涉及到HTTPS服務(wù)器的連接,OkHttp需要知道提供哪些TLS版本和密碼套件。如果客戶(hù)端想要最大限度地連接包括過(guò)時(shí)的TLS版本和弱由設(shè)計(jì)的密碼套件。通過(guò)使用最新版本的TLS和實(shí)力最強(qiáng)的加密套件來(lái)最大限度地提高客戶(hù)端的安全性。
具體的安全與連接是由ConnectionSpec接口決定。OkHttp包括三個(gè)內(nèi)置的連接規(guī)格:
- MODERN_TLS是連接到現(xiàn)代的HTTPS服務(wù)器安全的配置。
- COMPATIBLE_TLS是連接到一個(gè)安全,但不是現(xiàn)代的-HTTPS服務(wù)器的安全配置。
- CLEARTEXT是用于不安全配置的http://網(wǎng)址。
默認(rèn)情況下,OkHttp先嘗試MODERN_TLS連接,如果現(xiàn)代配置失敗的話(huà)將退回到COMPATIBLE_TLS連接。
在每一個(gè)規(guī)范的TLS版本和密碼套件都可隨每個(gè)發(fā)行版而更改。例如,在OkHttp 2.2,我們下降支持響應(yīng)POODLE攻擊的SSL 3.0。而在OkHttp 2.3我們下降的支持RC4。對(duì)于桌面Web瀏覽器,保持最新的OkHttp是保持安全的最好辦法。
你可以用一組自定義TLS版本和密碼套件建立自己的連接規(guī)格。例如,限制配置三個(gè)備受推崇的密碼套件。它的缺點(diǎn)是,它需要的Andr??oid 5.0+和一個(gè)類(lèi)似的電流網(wǎng)絡(luò)服務(wù)器
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(TlsVersion.TLS_1_2).cipherSuites(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256).build();OkHttpClient client = new OkHttpClient.Builder() .connectionSpecs(Collections.singletonList(spec)).build();5.1證書(shū)釘扎
默認(rèn)情況下,OkHttp信任主機(jī)平臺(tái)的證書(shū)頒發(fā)機(jī)構(gòu)。這種策略最多的連接,但它受證書(shū)頒發(fā)機(jī)構(gòu)的襲擊,如2011 DigiNotar的攻擊。它還假定您的HTTPS服務(wù)器的證書(shū)是由證書(shū)頒發(fā)機(jī)構(gòu)簽署。
使用CertificatePinner來(lái)限制哪些證書(shū)和證書(shū)頒發(fā)機(jī)構(gòu)是可信任的。證書(shū)釘扎增強(qiáng)了安全性,但這會(huì)限制你的服務(wù)器團(tuán)隊(duì)更新自己的TLS證書(shū)。在沒(méi)有你的服務(wù)器的TLS管理員的同意下,不要使用證書(shū)釘扎!
public CertificatePinning() {client = new OkHttpClient.Builder().certificatePinner(new CertificatePinner.Builder().add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=").build()).build();}public void run() throws Exception {Request request = new Request.Builder().url("https://publicobject.com/robots.txt").build();Response response = client.newCall(request).execute();if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);for (Certificate certificate : response.handshake().peerCertificates()) {System.out.println(CertificatePinner.pin(certificate));}}5.2定制信任證書(shū)
下面完整的代碼示例演示了如何用自定義證書(shū)替換主機(jī)平臺(tái)的證書(shū)。如上所述,在沒(méi)有你的服務(wù)器的TLS管理員的同意下,不要使用自定義證書(shū)!
private final OkHttpClient client;public CustomTrust() {SSLContext sslContext = sslContextForTrustedCertificates(trustedCertificatesInputStream());client = new OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory()).build();}public void run() throws Exception {Request request = new Request.Builder().url("https://publicobject.com/helloworld.txt").build();Response response = client.newCall(request).execute();System.out.println(response.body().string());}private InputStream trustedCertificatesInputStream() {... // Full source omitted. See sample.}public SSLContext sslContextForTrustedCertificates(InputStream in) {... // Full source omitted. See sample.}總結(jié)
以上是生活随笔為你收集整理的OkHttp 官方中文文档的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: LEAD_LAG:提前和滞后算法
- 下一篇: B2C电子商务系统研发