Java HTTP 组件库选型看这篇就够了

最近项目需要使用 Java 重度调用 HTTP API 接口,于是想着封装一个团队公用的 HTTP client lib. 这个库需要支持以下特性:

连接池管理,包括连接创建和超时、空闲连接数控制、每个 host 的连接数配置等。基本上,我们想要一个 go HTTP 标准库自带的连接池管理功能。
域名解析控制。因为调用量会比较大,因此希望在域名解析这一层做一个调用端可控的负载均衡,同时可以对每个服务器 IP 进行失败率统计和健康度检查。
Form/JSON 调用支持良好。
支持同步和异步调用。
在 Java 生态中,虽然有数不清的 HTTP client lib 组件库,但是大体可以分为这三类:

JDK 自带的 HttpURLConnection 标准库;
Apache HttpComponents HttpClient, 以及基于该库的 wrapper, 如 Unirest.
非基于 Apache HttpComponents HttpClient, 大量重写应用层代码的 HTTP client 组件库,典型代表是 OkHttp.
HttpURLConnection

使用 HttpURLConnection 发起 HTTP 请求最大的优点是不需要引入额外的依赖,但是使用起来非常繁琐,也缺乏连接池管理、域名机械控制等特性支持。以发起一个 HTTP POST 请求为例:

import java.io.BufferedReader;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.net.HttpURLConnection;

import java.net.URL;

public class HttpUrlConnectionDemo {

public static void main(String[] args) throws Exception {

    String urlString = "https://httpbin.org/post";

    String bodyString = "password=e10adc3949ba59abbe56e057f20f883e&username=test3";

    URL url = new URL(urlString);

    HttpURLConnection conn = (HttpURLConnection) url.openConnection();

    conn.setRequestMethod("POST");

    conn.setDoOutput(true);

    OutputStream os = conn.getOutputStream();

    os.write(bodyString.getBytes("utf-8"));

    os.flush();

    os.close();

    if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {

        InputStream is = conn.getInputStream();

        BufferedReader reader = new BufferedReader(new InputStreamReader(is));

        StringBuilder sb = new StringBuilder();

        String line;

        while ((line = reader.readLine()) != null) {

            sb.append(line);

        }

        System.out.println("rsp:" + sb.toString());

    } else {

        System.out.println("rsp code:" + conn.getResponseCode());

    }

}

}

可以看到,使用 HttpURLConnection 发起 HTTP 请求是比较原始(low level)的,基本上你可以理解为它就是对网络栈传输层(HTTP 一般为 TCP,HTTP over QUIC 是 UDP)进行了一次浅层次的封装,操作原语就是在打开的连接上面写请求 request 与读响应 response. 而且 HttpURLConnection 无法支持 HTTP/2. 显然,官方是知道这些问题的,因此在 Java 9 中,官方在标准库中引入了一个 high level、支持 HTTP/2 的 HttpClient. 这个库的接口封装就非常主流到位了,发起一个简单的 POST 请求:

HttpRequest request = HttpRequest.newBuilder()

.uri(new URI("https://postman-echo.com/post"))

.headers("Content-Type", "text/plain;charset=UTF-8")

.POST(HttpRequest.BodyProcessor.fromString("Sample request body"))

.build();

封装的最大特点是链式调用非常顺滑,支持连接管理等特性。但是这个库只能在 Java 9 及以后的版本使用,Java 9 和 Java 10 并不是 LTS 维护版本,而接下来的 Java 11 LTS 要在2018.09.25发布,应用到线上还需要等待一段时间。因此,虽然挺喜欢这个自带标准库(毕竟可以不引入三方依赖),但当前是无法在生产环境使用的。

Apache HttpComponents HttpClient

Apache HttpComponents HttpClient 的前身是 Apache Commons HttpClient, 但是 Apache Commons HttpClient 已经停止开发,如果你还在使用它,请切换到 Apache HttpComponents HttpClient 上来。

Apache HttpComponents HttpClient 支持的特性非常丰富,完全覆盖我们的需求,使用起来也非常顺手:

<br />import java.io.ByteArrayOutputStream;

import java.io.InputStream;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import org.apache.commons.io.IOUtils;

import org.apache.http.HttpResponse;

import org.apache.http.NameValuePair;

import org.apache.http.client.entity.UrlEncodedFormEntity;

import org.apache.http.client.fluent.Request;

import org.apache.http.client.methods.CloseableHttpResponse;

import org.apache.http.client.methods.HttpPost;

import org.apache.http.client.methods.RequestBuilder;

import org.apache.http.impl.client.CloseableHttpClient;

import org.apache.http.message.BasicNameValuePair;

import org.apache.http.impl.client.HttpClients;

public class HttpComponentsDemo {

final static CloseableHttpClient client = HttpClients.createDefault();

// 常规调用

private String sendPostForm(String url, Map&lt;String, String&gt; params) throws Exception {

    HttpPost request = new HttpPost(url);

    // set header

    request.setHeader("X-Http-Demo", HttpComponentsDemo.class.getSimpleName());

    // set params

    if (params != null) {

        List&lt;NameValuePair&gt; nameValuePairList = new ArrayList&lt;NameValuePair&gt;();

        for (Map.Entry&lt;String, String&gt; entry : params.entrySet()) {

            nameValuePairList.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));

        }

        UrlEncodedFormEntity bodyEntity = new UrlEncodedFormEntity(nameValuePairList, "UTF-8");

        //System.out.println("body:" + IOUtils.toString(bodyEntity.getContent()));

        request.setEntity(new UrlEncodedFormEntity(nameValuePairList));

    }

    // send request

    CloseableHttpResponse response = client.execute(request);

    // read rsp code

    System.out.println("rsp code:" + response.getStatusLine().getStatusCode());

    // return content

    String ret = readResponseContent(response.getEntity().getContent());

    response.close();

    return ret;

}

// fluent 链式调用

private String sendGet(String url) throws Exception {

    return Request.Get(url)

            .connectTimeout(1000)

            .socketTimeout(1000)

            .execute().returnContent().asString();

}

private String readResponseContent(InputStream inputStream) throws Exception {

    if (inputStream == null) {

        return "";

    }

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    byte[] buf = new byte[512];

    int len;

    while (inputStream.available() &gt; 0) {

        len = inputStream.read(buf);

        out.write(buf, 0, len);

    }

    return out.toString();

}

public static void main(String[] args) throws Exception {

    HttpComponentsDemo httpUrlConnectionDemo = new HttpComponentsDemo();

    String url = "https://httpbin.org/post";

    Map&lt;String, String&gt; params = new HashMap&lt;String, String&gt;();

    params.put("foo", "bar中文");

    String rsp = httpUrlConnectionDemo.sendPostForm(url, params);

    System.out.println("http post rsp:" + rsp);

    url = "https://httpbin.org/get";

    System.out.println("http get rsp:" + httpUrlConnectionDemo.sendGet(url));

}

}

对 Client 细致的配置和自定义支持也是非常到位的:

    // Create a connection manager with custom configuration.

    PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(

            socketFactoryRegistry, connFactory, dnsResolver);

    // Create socket configuration

    SocketConfig socketConfig = SocketConfig.custom()

        .setTcpNoDelay(true)

        .build();

    // Configure the connection manager to use socket configuration either

    // by default or for a specific host.

    connManager.setDefaultSocketConfig(socketConfig);

    connManager.setSocketConfig(new HttpHost("somehost", 80), socketConfig);

    // Validate connections after 1 sec of inactivity

    connManager.setValidateAfterInactivity(1000);

    // Create message constraints

    MessageConstraints messageConstraints = MessageConstraints.custom()

        .setMaxHeaderCount(200)

        .setMaxLineLength(2000)

        .build();

    // Create connection configuration

    ConnectionConfig connectionConfig = ConnectionConfig.custom()

        .setMalformedInputAction(CodingErrorAction.IGNORE)

        .setUnmappableInputAction(CodingErrorAction.IGNORE)

        .setCharset(Consts.UTF_8)

        .setMessageConstraints(messageConstraints)

        .build();

    // Configure the connection manager to use connection configuration either

    // by default or for a specific host.

    connManager.setDefaultConnectionConfig(connectionConfig);

    connManager.setConnectionConfig(new HttpHost("somehost", 80), ConnectionConfig.DEFAULT);

    // Configure total max or per route limits for persistent connections

    // that can be kept in the pool or leased by the connection manager.

    connManager.setMaxTotal(100);

    connManager.setDefaultMaxPerRoute(10);

    connManager.setMaxPerRoute(new HttpRoute(new HttpHost("somehost", 80)), 20);

完整示例请参考 ClientConfiguration.

基本上,在 Java 原生标准库不给力的情况下,Apache HttpComponents HttpClient 是最佳的 HTTP Client library 选择。但这个库当前还不支持 HTTP/2,支持 HTTP/2 的版本还处于 beta 阶段(2018.09.23),因此并不适合用于 Android APP 中使用。

OkHttp

由于当前 Apache HttpComponents HttpClient 版本并不支持 HTTP/2, 而 HTTP/2 对于移动客户端而言,无论是从握手延迟、响应延迟,还是资源开销看都有相当吸引力。因此这就给了高层次封装且支持 HTTP/2 的 http client lib 足够的生存空间。其中最典型的要数OkHttp.

<br /><br />import okhttp3.*;

import org.apache.http.util.CharsetUtils;

import java.util.HashMap;

import java.util.Map;

public class OkHttpDemo {

OkHttpClient client = new OkHttpClient();

private String sendPostForm(String url, final Map&lt;String, String&gt; params) throws Exception {

    FormBody.Builder builder = new FormBody.Builder(CharsetUtils.get("UTF-8"));

    if (params != null) {

        for (Map.Entry&lt;String, String&gt; entry: params.entrySet()) {

            builder.add(entry.getKey(), entry.getValue());

        }

    }

    RequestBody requestBody = builder.build();

    Request request = new Request.Builder().url(url).post(requestBody).build();

    return client.newCall(request).execute().body().string();

}

private String sendGet(String url) throws Exception {

    Request request = new Request.Builder().url(url).build();

    return  client.newCall(request).execute().body().string();

}

public static void main(String[] args) throws Exception {

    OkHttpDemo okHttpDemo = new OkHttpDemo();

    String url = "https://httpbin.org/post";

    Map&lt;String, String&gt; params = new HashMap&lt;String, String&gt;();

    params.put("foo", "bar中文");

    String rsp = okHttpDemo.sendPostForm(url, params);

    System.out.println("http post rsp:" + rsp);

    url = "https://httpbin.org/get";

    System.out.println("http get rsp:" + okHttpDemo.sendGet(url));

}

}

OkHttp 接口设计友好,支持 HTTP/2,并且在弱网和无网环境下有自动检测和恢复机制,因此,是当前 Android APP 开发中使用最广泛的 HTTP clilent lib 之一。

另一方面,OkHttp 提供的接口与 Java 9 中 HttpClint 接口比较类似 (严格讲,应该是 Java 9 借鉴了 OkHttp 等开源库的接口设计?),因此,对于喜欢减少依赖,钟情于原生标准库的开发者来说,在 Java 11 中,从 OkHttp 切换到标准库是相对容易的。因此,以 OkHttp 为代表的 http 库以后的使用场景可能会被蚕食一部分。

小结

HttpURLConnection 封装层次太低,并且支持特性太少,不建议在项目中使用。除非你的确不想引入第三方 HTTP 依赖(如减少包大小、目标环境不提供三方库支持等)。
Java 9 中引入的 HttpClient,封装层次和支持特性都不错。但是因为 Java 版本的原因,应用场景还十分有限,建议观望一段时间再考虑在线上使用。
如果你不需要 HTTP/2特性,Apache HttpComponents HttpClient 是你的最佳选择,比如在服务器之间的 HTTP 调用。否则,请使用 OkHttp, 如 Android 开发。

原文地址:http://blog.51cto.com/13952955/2287316

时间: 2024-11-01 21:09:56

Java HTTP 组件库选型看这篇就够了的相关文章

你真的完全了解Java动态代理吗?看这篇就够了

之前讲了<零基础带你看Spring源码--IOC控制反转>,本来打算下一篇讲讲Srping的AOP的,但是其中会涉及到Java的动态代理,所以先单独一篇来了解下Java的动态代理到底是什么,Java是怎么实现它的. 动态代理看起来好像是个什么高大上的名词,但其实并没有那么复杂,直接从字面就很容易理解.动态地代理,可以猜测一下它的含义,在运行时动态地对某些东西代理,代理它做了其他事情.先不去搞清楚这个动态代理真正的含义,我们来举个生动的例子来理解下它到底做了什么. 一个例子 一个程序员Devel

微服务架构组件分析,看这篇就够了

原文地址:https://www.cnblogs.com/lemonrel/p/11773812.html

.NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了

作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9985451.html 本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新手朋友),但是转念一想不如来点猛的(考虑到急性子的朋友),让你通过本文的学习就能快速的入门ASP.NET Core.既然是快速入门所以过多过深的内容我这里就一笔带过了!然后在后面的一些列文章中再慢慢的对其中的概念进行阐述. 本文已收录至.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规

使用Visual Studio Code开发.NET Core看这篇就够了

原文:使用Visual Studio Code开发.NET Core看这篇就够了 作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9926078.html 在本文中,我将带着大家一步一步的通过图文的形式来演示如何在Visual Studio Code中进行.NET Core程序的开发,测试以及调试.尽管Visual Studio Code的部分功能还达不到Visual Studio的水平,但它实际上已经足够强大来满足我们的日常开发.而且其轻量化,插件化

ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了

引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者的心情.或者详细点,或者简单点.那么有没有一种快速有效的方法来构建api说明文档呢?答案是肯定的, Swagger就是最受欢迎的REST APIs文档生成工具之一! 为什么使用Swagger作为REST APIs文档生成工具 Swagger 可以生成一个具有互动性的API控制台,开发者可以用来快速学

Pycharm新手教程,只需要看这篇就够了

pycharm是一款高效的python IDE工具,它非常强大,且可以跨平台,是新手首选工具!下面我给第一次使用这款软件的朋友做一个简单的使用教程,希望能给你带来帮助! 目前pycharm一共有两个版本,Professional(专业版本)」和「社区版」,专业版功能最强大,主要是为 Python 和 web 开发者而准备,是需要付费的.社区版是专业版的阉割版,比较轻量级,主要是为 Python 和数据专家而准备的.一般我们做开发,下载专业版本比较合适. 文章转自:django中文网---Pych

如何搭建组件库(基础实战篇)

一.搭建组件库有什么好处? 让设计更高效.开发更迅速.产品体验更一致.很多大厂也做了自己的组件库,比如Ant Design,Element等,一个成熟的组件库确实让产品的体验更好,团队的效率更高.如果你也想开始搭建你们产品自己的组件库,希望本文能帮助到你. 二.什么样的模块可以成为组件? 重复使用,如果一个模块需要重复使用,那么可以认为它是一个组件,比如说Search Bar,Tab等. 三.如何来搭建组件库? 1.对所有组件进行分类 参考Ant Design的分类方式,如果你们的开发采用Ant

学习Java JDBC,看这篇就够了

JDBC (Java DB Connection)---Java数据库连接 JDBC是一种可用于执行SQL语句的JAVA API(ApplicationProgramming Interface应用程序设计接口).它由一些Java语言编写的类和界面组成. JDBC为数据库应用开发人员和数据库前台工具开发人员提供了一种标准的应用程序设计接口,使开发人员可以用纯JAVA语言编写完整的数据库应用程序. JDBC代表JAVA数据库连接.它是一个软件层,允许开发者在JAVA中编写客户端/服务器应用. 一.

分布式锁看这篇就够了

什么是锁?在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量.而同步的本质是通过锁来实现的.为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记.这个标记可以理解为锁.不同地方实现锁的方式也不一样,只要能满足所有线程都