我們想要一個什么樣的?
理想型的HTTP庫應(yīng)當(dāng)具備以下能力:
- 提供分析記錄請求各個階段的能力列林,如在什么時候發(fā)送數(shù)據(jù)碾阁、在什么接收數(shù)據(jù)杠氢、各階段的耗時
- 支持響應(yīng)緩存減少重復(fù)的網(wǎng)絡(luò)請求
- 支持重試
- 支持連接池能復(fù)用連接
- 簡單易用
- 支持HTTP/1.0、HTTP/1.1续崖、HTTP/2.0
- 支持同步和異步請求
- 支持取消請求
- 支持設(shè)置超時
目前有哪些可選的敲街,哪一個和我們匹配度最高的?
可以發(fā)起http請求的組件:
- JDK's URLConnection uses traditional thread-blocking I/O.
- Apache HTTP Client uses traditional thread-blocking I/O with thread-pools.
- Apache Async HTTP Client uses NIO.
- Jersey is a RESTful client/server framework; the client API can use several HTTP client backends including URLConnection and Apache HTTP Client.
- OkHttp uses traditional thread-blocking I/O with thread-pools.
- Retrofit turns your HTTP API into a Java interface and can use several HTTP client backends including Apache HTTP Client.
- Grizzly is network framework with low-level HTTP support; it was using NIO but it switched to AIO .
- Netty is a network framework with HTTP support (low-level), multi-transport, includes NIO and native (the latter uses epoll on Linux).
- Jetty Async HTTP Client uses NIO.
- Async HTTP Client wraps either Netty, Grizzly or JDK's HTTP support.
- clj-http wraps the Apache HTTP Client.is an async subset of clj-http implemented partially in Java directly on top of NIO.
- http async client wraps the Async HTTP Client for Java.
下面挑選主流且大家熟悉的做個對比:
結(jié)論
HttpURLConnection封裝層次太低严望,支持的特性也比較少聪富,因此不在我們的選擇范圍內(nèi);對比Apache HttpClient跟OkHttp著蟹,在功能跟性能上來看是不相上下的墩蔓;
- 在穩(wěn)定性上Apache HttpClient歷史悠久,穩(wěn)定性更好萧豆;
- 在社區(qū)活躍度上OkHttp更勝一籌奸披,GitHub上星標達到30000+,可以說是當(dāng)下最流行的Http庫了涮雷;從安卓4.4版本開始就使用okhttp來處理http請求阵面。
- 在擴展性上OkHttp更加方便用戶定制個性化的擴展功能,如我們可以很方便的監(jiān)控Http請求,在無需客戶端配合的情況下样刷,盡可能的保留更多的信息仑扑;
- 在易用性上,流式的構(gòu)建者模式和不可變模式使得OkHttp更勝一籌置鼻;
在最終權(quán)衡下镇饮,決定使用OkHttp。
OkHttp:
官方文檔:http://square.github.io/okhttp/
git源碼:https://github.com/square/okhttp
-
-
官方示例
package okhttp3.guide; import java.io.IOException; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class GetExample { 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()) { //string()方法只能調(diào)用一次箕母,因為調(diào)用后會關(guān)閉流 return response.body().string(); } } public static void main(String[] args) throws IOException { GetExample example = new GetExample(); String response = example.run("https://raw.github.com/square/okhttp/master/README.md"); System.out.println(response); } }
package okhttp3.guide; import java.io.IOException; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class PostExample { 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()) { //string()方法只能調(diào)用一次储藐,因為調(diào)用后會關(guān)閉流 return response.body().string(); } } String bowlingJson(String player1, String player2) { return "{'winCondition':'HIGH_SCORE'," + "'name':'Bowling'," + "'round':4," + "'lastSaved':1367702411696," + "'dateStarted':1367702378785," + "'players':[" + "{'name':'" + player1 + "','history':[10,8,6,7,8],'color':-13388315,'total':39}," + "{'name':'" + player2 + "','history':[6,10,5,10,10],'color':-48060,'total':41}" + "]}"; } public static void main(String[] args) throws IOException { PostExample example = new PostExample(); String json = example.bowlingJson("Jesse", "Jake"); String response = example.post("http://www.roundsapp.com/post", json); System.out.println(response); } }
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build(); Request request = new Request.Builder().url("http://www.baidu.com").get().build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { System.out.println("Fail"); } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println(response.body().string()); } });
-
注意點
①每次new OkHttpClient(); 都會創(chuàng)建一個連接池和線程池,所以要保證只new一次OkHttpClient()嘶是「撇可以使用newBuilder()來為不同的目標地址配置參數(shù)。
②異步請求:最多并發(fā)64個聂喇,同一個host的訪問不能超過5個辖源,否則進入等待隊列。
③連接池:默認空閑連接數(shù)是5希太,每個連接最多存活時間是5分鐘克饶,連接池大小取決于內(nèi)存大小(無限量)跛十。
④超時:默認的connectTimeout彤路,readTimeout,writeTimeout都是10s芥映。
⑤緩存:OKHttp的網(wǎng)絡(luò)緩存是基于http協(xié)議洲尊,使用lru策略;目前只支持GET奈偏,其他請求方式需要自己實現(xiàn)坞嘀。需要服務(wù)器配合,通過head設(shè)置相關(guān)頭來控制緩存惊来,創(chuàng)建OkHttpClient時候需要配置Cache丽涩。
⑥Cookie:OKHttp默認是沒有提供Cookie管理功能的,所以如果想增加Cookie管理需要重寫CookieJar里面的方法裁蚁。
⑦服務(wù)器主動斷開的鏈接如何處理矢渊?OkHttp則不會進行連接判斷,直接發(fā)送請求枉证,如果服務(wù)端在接收請求后回復(fù)RST消息矮男,OkHttp會重新建立連接后再發(fā)送請求。OkHttp由于沒有檢查連接狀態(tài)室谚,在服務(wù)端主動斷開連接時毡鉴,會發(fā)送無效請求崔泵,對于服務(wù)端來說,就降低了請求成功率猪瞬≡魅常可以通過設(shè)置ConnectionPool的Keep-Alive時間來解決這個問題,默認值為5min陈瘦。只要配置時間短于服務(wù)端對應(yīng)的時間幌甘,就可以保證由客戶端主動斷開連接,就不會出現(xiàn)無效請求的問題甘晤。
⑧過濾器:官網(wǎng)寫的非常清楚含潘,傳送門:https://github.com/square/okhttp/wiki/Interceptors#choosing-between-application-and-network-interceptors
⑨重試策略:
/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
* be recovered if the body is buffered or if the failure occurred before the request has been
* sent.
*/
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// 1\. 應(yīng)用層配置不在連接饲做,默認為true
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// 2\. 請求Request出錯不能繼續(xù)使用
// We can't send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// 是否可以恢復(fù)的
// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;
// 4\. 沒有更多線路可供選擇
// No more routes to attempt.
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
若應(yīng)用層配置的是不再連接(默認為true)线婚,則不可恢復(fù)。(可通過設(shè)置retryOnConnectionFailure來改變)
請求Request是不可重復(fù)使用的Request盆均,則不可恢復(fù)
根據(jù)Exception的類型判斷是否可以恢復(fù)的 (isRecoverable()方法)
3.1塞弊、如果是協(xié)議錯誤(ProtocolException)則不可恢復(fù)
3.2、如果是中斷異常(InterruptedIOException)則不可恢復(fù)
3.3泪姨、如果是SSL握手錯誤(SSLHandshakeException && CertificateException)則不可恢復(fù)
3.4游沿、certificate pinning錯誤(SSLPeerUnverifiedException)則不可恢復(fù)沒有更多線路可供選擇 則不可恢復(fù)
-
如果上述條件都不滿足,則這個request可以恢復(fù)肮砾,最大重試20次诀黍。
舉例:UnknownHostException不會重試;讀寫超時導(dǎo)致的SocketTimeoutException仗处,要看是否已經(jīng)發(fā)送了request眯勾,若已發(fā)送則不重試。連接超時導(dǎo)致的SocketTimeoutException婆誓,若沒有其他路由則不重試吃环,有則重試。
- 請求響應(yīng)流程如下:
- 核心類
-
調(diào)用流程說明
如果有自定義的過濾器洋幻,先執(zhí)行自定義的郁轻。