HTTP客户端调研-20190125

我们想要一个什么样的?

理想型的HTTP库应当具备以下能力:

  • 提供分析记录请求各个阶段的能力,如在什么时候发送数据、在什么接收数据、各阶段的耗时
  • 支持响应缓存减少重复的网络请求
  • 支持重试
  • 支持连接池能复用连接
  • 简单易用
  • 支持HTTP/1.0、HTTP/1.1、HTTP/2.0
  • 支持同步和异步请求
  • 支持取消请求
  • 支持设置超时

目前有哪些可选的,哪一个和我们匹配度最高的?

可以发起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.

下面挑选主流且大家熟悉的做个对比:

结论

HttpURLConnection封装层次太低,支持的特性也比较少,因此不在我们的选择范围内;对比Apache HttpClient跟OkHttp,在功能跟性能上来看是不相上下的;

  • 在稳定性上Apache HttpClient历史悠久,稳定性更好;
  • 在社区活跃度上OkHttp更胜一筹,GitHub上星标达到30000+,可以说是当下最流行的Http库了;从安卓4.4版本开始就使用okhttp来处理http请求。
  • 在扩展性上OkHttp更加方便用户定制个性化的扩展功能,如我们可以很方便的监控Http请求,在无需客户端配合的情况下,尽可能的保留更多的信息;
  • 在易用性上,流式的构建者模式和不可变模式使得OkHttp更胜一筹;

在最终权衡下,决定使用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()方法只能调用一次,因为调用后会关闭流
          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()方法只能调用一次,因为调用后会关闭流
          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(); 都会创建一个连接池和线程池,所以要保证只new一次OkHttpClient()。可以使用newBuilder()来为不同的目标地址配置参数。

    ②异步请求:最多并发64个,同一个host的访问不能超过5个,否则进入等待队列。

    ③连接池:默认空闲连接数是5,每个连接最多存活时间是5分钟,连接池大小取决于内存大小(无限量)。

    ④超时:默认的connectTimeout,readTimeout,writeTimeout都是10s。

    ⑤缓存:OKHttp的网络缓存是基于http协议,使用lru策略;目前只支持GET,其他请求方式需要自己实现。需要服务器配合,通过head设置相关头来控制缓存,创建OkHttpClient时候需要配置Cache。

    ⑥Cookie:OKHttp默认是没有提供Cookie管理功能的,所以如果想增加Cookie管理需要重写CookieJar里面的方法。

    ⑦服务器主动断开的链接如何处理?OkHttp则不会进行连接判断,直接发送请求,如果服务端在接收请求后回复RST消息,OkHttp会重新建立连接后再发送请求。OkHttp由于没有检查连接状态,在服务端主动断开连接时,会发送无效请求,对于服务端来说,就降低了请求成功率。可以通过设置ConnectionPool的Keep-Alive时间来解决这个问题,默认值为5min。只要配置时间短于服务端对应的时间,就可以保证由客户端主动断开连接,就不会出现无效请求的问题。

    ⑧过滤器:官网写的非常清楚,传送门: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\. 应用层配置不在连接,默认为true
    // The application layer has forbidden retries.
    if (!client.retryOnConnectionFailure()) return false;
     // 2\. 请求Request出错不能继续使用
    // We can't send the request body again.
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
    //  是否可以恢复的
    // 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. 若应用层配置的是不再连接(默认为true),则不可恢复。(可通过设置retryOnConnectionFailure来改变)

  2. 请求Request是不可重复使用的Request,则不可恢复

  3. 根据Exception的类型判断是否可以恢复的 (isRecoverable()方法)
    3.1、如果是协议错误(ProtocolException)则不可恢复
    3.2、如果是中断异常(InterruptedIOException)则不可恢复
    3.3、如果是SSL握手错误(SSLHandshakeException && CertificateException)则不可恢复
    3.4、certificate pinning错误(SSLPeerUnverifiedException)则不可恢复

  4. 没有更多线路可供选择 则不可恢复

  5. 如果上述条件都不满足,则这个request可以恢复,最大重试20次。

    举例:UnknownHostException不会重试;读写超时导致的SocketTimeoutException,要看是否已经发送了request,若已发送则不重试。连接超时导致的SocketTimeoutException,若没有其他路由则不重试,有则重试。

  • 请求响应流程如下:
  • 核心类
  • 调用流程说明

    如果有自定义的过滤器,先执行自定义的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • https://blog.csdn.net/joye123/article/details/82115562?ut...
    kkgo阅读 4,316评论 0 0
  • 6.1 公钥密钥加密原理 6.1.1 基础知识 密钥:一般就是一个字符串或数字,在加密或者解密时传递给加密/解密算...
    AndroidMaster阅读 4,000评论 1 8
  • 这篇文章主要讲 Android 网络请求时所使用到的各个请求库的关系,以及 OkHttp3 的介绍。(如理解有误,...
    小庄bb阅读 1,140评论 0 4
  • 王艺楠 害怕:五岁的时候,我想自己睡。可是到了晚上,由于房间里太黑了,我就不敢一个人睡了。 ...
    幽兰_b9a1阅读 233评论 0 0
  • 时间给生活刻上厚厚的印迹 带不走的沟壑越陷越深 挑不起的大梁越压越重 翻不过去的昨天 来来回回 新的灰尘压着旧的灰...
    追风_defb书写人生阅读 198评论 0 6