HTTP客戶端調(diào)研-20190125

我們想要一個什么樣的?

理想型的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

  • 官網(wǎng)介紹:

  • 官方示例

    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;
  }
  1. 若應(yīng)用層配置的是不再連接(默認為true)线婚,則不可恢復(fù)。(可通過設(shè)置retryOnConnectionFailure來改變)

  2. 請求Request是不可重復(fù)使用的Request盆均,則不可恢復(fù)

  3. 根據(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ù)

  4. 沒有更多線路可供選擇 則不可恢復(fù)

  5. 如果上述條件都不滿足,則這個request可以恢復(fù)肮砾,最大重試20次诀黍。

    舉例:UnknownHostException不會重試;讀寫超時導(dǎo)致的SocketTimeoutException仗处,要看是否已經(jīng)發(fā)送了request眯勾,若已發(fā)送則不重試。連接超時導(dǎo)致的SocketTimeoutException婆誓,若沒有其他路由則不重試吃环,有則重試。

  • 請求響應(yīng)流程如下:
  • 核心類
  • 調(diào)用流程說明

    如果有自定義的過濾器洋幻,先執(zhí)行自定義的郁轻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市文留,隨后出現(xiàn)的幾起案子好唯,更是在濱河造成了極大的恐慌,老刑警劉巖燥翅,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骑篙,死亡現(xiàn)場離奇詭異,居然都是意外死亡权旷,警方通過查閱死者的電腦和手機替蛉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門贯溅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躲查,你說我怎么就攤上這事它浅。” “怎么了镣煮?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵姐霍,是天一觀的道長。 經(jīng)常有香客問我典唇,道長镊折,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任介衔,我火速辦了婚禮恨胚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘炎咖。我一直安慰自己赃泡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布乘盼。 她就那樣靜靜地躺著升熊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绸栅。 梳的紋絲不亂的頭發(fā)上级野,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音粹胯,去河邊找鬼蓖柔。 笑死,一個胖子當(dāng)著我的面吹牛矛双,可吹牛的內(nèi)容都是我干的渊抽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼议忽,長吁一口氣:“原來是場噩夢啊……” “哼懒闷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起栈幸,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤愤估,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后速址,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玩焰,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年芍锚,在試婚紗的時候發(fā)現(xiàn)自己被綠了昔园。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔓榄。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖默刚,靈堂內(nèi)的尸體忽然破棺而出甥郑,到底是詐尸還是另有隱情,我是刑警寧澤荤西,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布澜搅,位于F島的核電站,受9級特大地震影響邪锌,放射性物質(zhì)發(fā)生泄漏勉躺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一觅丰、第九天 我趴在偏房一處隱蔽的房頂上張望饵溅。 院中可真熱鬧,春花似錦舶胀、人聲如沸概说。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至萍丐,卻和暖如春轩端,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逝变。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工基茵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壳影。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓拱层,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宴咧。 傳聞我的和親對象是個殘疾皇子根灯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容