我們想要一個(gè)什么樣的?
理想型的HTTP庫(kù)應(yīng)當(dāng)具備以下能力:
- 提供分析記錄請(qǐng)求各個(gè)階段的能力,如在什么時(shí)候發(fā)送數(shù)據(jù)、在什么接收數(shù)據(jù)、各階段的耗時(shí)
- 支持響應(yīng)緩存減少重復(fù)的網(wǎng)絡(luò)請(qǐng)求
- 支持重試
- 支持連接池能復(fù)用連接
- 簡(jiǎn)單易用
- 支持HTTP/1.0、HTTP/1.1、HTTP/2.0
- 支持同步和異步請(qǐng)求
- 支持取消請(qǐng)求
- 支持設(shè)置超時(shí)
目前有哪些可選的,哪一個(gè)和我們匹配度最高的?
可以發(fā)起http請(qǐng)求的組件:
- 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.
下面挑選主流且大家熟悉的做個(gè)對(duì)比:
結(jié)論
HttpURLConnection封裝層次太低,支持的特性也比較少,因此不在我們的選擇范圍內(nèi);對(duì)比Apache HttpClient跟OkHttp,在功能跟性能上來(lái)看是不相上下的;
- 在穩(wěn)定性上Apache HttpClient歷史悠久,穩(wěn)定性更好;
- 在社區(qū)活躍度上OkHttp更勝一籌,GitHub上星標(biāo)達(dá)到30000+,可以說(shuō)是當(dāng)下最流行的Http庫(kù)了;從安卓4.4版本開(kāi)始就使用okhttp來(lái)處理http請(qǐng)求。
- 在擴(kuò)展性上OkHttp更加方便用戶(hù)定制個(gè)性化的擴(kuò)展功能,如我們可以很方便的監(jiān)控Http請(qǐng)求,在無(wú)需客戶(hù)端配合的情況下,盡可能的保留更多的信息;
- 在易用性上,流式的構(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)用一次,因?yàn)檎{(diào)用后會(huì)關(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)用一次,因?yàn)檎{(diào)用后會(huì)關(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()); } });
-
注意點(diǎn)
①每次new OkHttpClient(); 都會(huì)創(chuàng)建一個(gè)連接池和線(xiàn)程池,所以要保證只new一次OkHttpClient()。可以使用newBuilder()來(lái)為不同的目標(biāo)地址配置參數(shù)。
②異步請(qǐng)求:最多并發(fā)64個(gè),同一個(gè)host的訪(fǎng)問(wèn)不能超過(guò)5個(gè),否則進(jìn)入等待隊(duì)列。
③連接池:默認(rèn)空閑連接數(shù)是5,每個(gè)連接最多存活時(shí)間是5分鐘,連接池大小取決于內(nèi)存大小(無(wú)限量)。
④超時(shí):默認(rèn)的connectTimeout,readTimeout,writeTimeout都是10s。
⑤緩存:OKHttp的網(wǎng)絡(luò)緩存是基于http協(xié)議,使用lru策略;目前只支持GET,其他請(qǐng)求方式需要自己實(shí)現(xiàn)。需要服務(wù)器配合,通過(guò)head設(shè)置相關(guān)頭來(lái)控制緩存,創(chuàng)建OkHttpClient時(shí)候需要配置Cache。
⑥Cookie:OKHttp默認(rèn)是沒(méi)有提供Cookie管理功能的,所以如果想增加Cookie管理需要重寫(xiě)CookieJar里面的方法。
⑦服務(wù)器主動(dòng)斷開(kāi)的鏈接如何處理?OkHttp則不會(huì)進(jìn)行連接判斷,直接發(fā)送請(qǐng)求,如果服務(wù)端在接收請(qǐng)求后回復(fù)RST消息,OkHttp會(huì)重新建立連接后再發(fā)送請(qǐng)求。OkHttp由于沒(méi)有檢查連接狀態(tài),在服務(wù)端主動(dòng)斷開(kāi)連接時(shí),會(huì)發(fā)送無(wú)效請(qǐng)求,對(duì)于服務(wù)端來(lái)說(shuō),就降低了請(qǐng)求成功率。可以通過(guò)設(shè)置ConnectionPool的Keep-Alive時(shí)間來(lái)解決這個(gè)問(wèn)題,默認(rèn)值為5min。只要配置時(shí)間短于服務(wù)端對(duì)應(yīng)的時(shí)間,就可以保證由客戶(hù)端主動(dòng)斷開(kāi)連接,就不會(huì)出現(xiàn)無(wú)效請(qǐng)求的問(wèn)題。
⑧過(guò)濾器:官網(wǎng)寫(xiě)的非常清楚,傳送門(mén):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)用層配置不在連接,默認(rèn)為true
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// 2\. 請(qǐng)求Request出錯(cuò)不能繼續(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\. 沒(méi)有更多線(xiàn)路可供選擇
// 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)用層配置的是不再連接(默認(rèn)為true),則不可恢復(fù)。(可通過(guò)設(shè)置retryOnConnectionFailure來(lái)改變)
請(qǐng)求Request是不可重復(fù)使用的Request,則不可恢復(fù)
根據(jù)Exception的類(lèi)型判斷是否可以恢復(fù)的 (isRecoverable()方法)
3.1、如果是協(xié)議錯(cuò)誤(ProtocolException)則不可恢復(fù)
3.2、如果是中斷異常(InterruptedIOException)則不可恢復(fù)
3.3、如果是SSL握手錯(cuò)誤(SSLHandshakeException && CertificateException)則不可恢復(fù)
3.4、certificate pinning錯(cuò)誤(SSLPeerUnverifiedException)則不可恢復(fù)沒(méi)有更多線(xiàn)路可供選擇 則不可恢復(fù)
-
如果上述條件都不滿(mǎn)足,則這個(gè)request可以恢復(fù),最大重試20次。
舉例:UnknownHostException不會(huì)重試;讀寫(xiě)超時(shí)導(dǎo)致的SocketTimeoutException,要看是否已經(jīng)發(fā)送了request,若已發(fā)送則不重試。連接超時(shí)導(dǎo)致的SocketTimeoutException,若沒(méi)有其他路由則不重試,有則重試。
- 請(qǐng)求響應(yīng)流程如下:
- 核心類(lèi)
-
調(diào)用流程說(shuō)明
如果有自定義的過(guò)濾器,先執(zhí)行自定義的。