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 支持的特性非常丰富,完全覆盖我们的需求,使用起来也非常顺手:
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<String, String> params) throws Exception {
HttpPost request = new HttpPost(url);
// set header
request.setHeader("X-Http-Demo", HttpComponentsDemo.class.getSimpleName());
// set params
if (params != null) {
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> 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() > 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<String, String> params = new HashMap<String, String>();
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.
https://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientConfiguration.java
基本上,在 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.
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<String, String> params) throws Exception {
FormBody.Builder builder = new FormBody.Builder(CharsetUtils.get("UTF-8"));
if (params != null) {
for (Map.Entry<String, String> 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<String, String> params = new HashMap<String, String>();
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 开发。
Java HTTP 组件库选型看这篇就够了的更多相关文章
- 你真的完全了解Java动态代理吗?看这篇就够了
之前讲了<零基础带你看Spring源码--IOC控制反转>,本来打算下一篇讲讲Srping的AOP的,但是其中会涉及到Java的动态代理,所以先单独一篇来了解下Java的动态代理到底是什么 ...
- 面试常问的Java虚拟机内存模型,看这篇就够了!
一.虚拟机 同样的java代码在不同平台生成的机器码肯定是不一样的,因为不同的操作系统底层的硬件指令集是不同的. 同一个java代码在windows上生成的机器码可能是0101.......,在lin ...
- Java NIO全面详解(看这篇就够了)
很多技术框架都使用NIO技术,学习和掌握Java NIO技术对于高性能.高并发网络的应用是非常关键的@mikechen NIO简介 NIO 中的 N 可以理解为 Non-blocking,不单纯是 N ...
- Java 集合基础入门,看这篇就够了
集合 1.父接口:Collection java.util.Collection 是进行单对象保存的最大父接口,即每次利用 Collection 接口都只能保存一个对象信息.定义如下: public ...
- Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) JAVA日志的前世今生 .NET MVC采用SignalR更新在线用户数 C#多线程编程系列(五)- 使用任务并行库 C#多线程编程系列(三)- 线程同步 C#多线程编程系列(二)- 线程基础 C#多线程编程系列(一)- 简介
Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) 一.前言 由于本篇文章较长,所以下面给出内容目录方便跳转阅读,当然也可以用博客页面最右侧的文章目录导航栏进行跳转查阅. 一.前言 ...
- .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了
作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9985451.html 本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新 ...
- net core体系-web应用程序-4asp.net core2.0 项目实战(CMS)-第二章 入门篇-快速入门ASP.NET Core看这篇就够了
.NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了 原文链接:https://www.cnblogs.com/yilezhu/p/9985451.ht ...
- Vue学习看这篇就够
Vue -渐进式JavaScript框架 介绍 vue 中文网 vue github Vue.js 是一套构建用户界面(UI)的渐进式JavaScript框架 库和框架的区别 我们所说的前端框架与库的 ...
- C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志
C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...
随机推荐
- SoapUI中Code多行显示设置
你们的SoapUI 有设置下面的选项吗? Before adding your project, we recommend that you enable the following ReadyAPI ...
- jackson实体为NULL或者为空不显示
1.实体上 @JsonInclude(JsonInclude.Include.NON_NULL) 将该注解放在属性上,如果该属性为null则不参与序列化: 如果放在类上边,那对这个类的全部属性起作用 ...
- 提高 Linux 上 socket 性能 加速网络应用程序的 4 种方法
使用 Sockets API,我们可以开发客户机和服务器应用程序,它们可以在本地网络上进行通信,也可以通过 Internet 在全球范围内进行通信.与其他 API 一样,您可以通过一些方法使用 Soc ...
- UVA11419 SAM I AM —— 最小点覆盖 + 输出覆盖点集
题目链接:https://vjudge.net/problem/UVA-11419 题解: 1.二分图匹配之最小点覆盖.:把x坐标和y坐标看成是点, 图中的目标看成是边,所以最终的目的是求出用最少的点 ...
- POJ - 1422 Air Raid(DAG的最小路径覆盖数)
1.一个有向无环图(DAG),M个点,K条有向边,求DAG的最小路径覆盖数 2.DAG的最小路径覆盖数=DAG图中的节点数-相应二分图中的最大匹配数 3. /* 顶点编号从0开始的 邻接矩阵(匈牙利算 ...
- 转载:百为STM32开发板教程之十一——NOR FLASH
转载:http://bbs.21ic.com/icview-586199-1-1.html 百为STM32开发板教程之十一——NOR FLASH 参考文档:百为stm32开发板光盘\st官方参考资料\ ...
- 461. Hamming Distance(汉明距离)
The Hamming distance between two integers is the number of positions at which the corresponding bits ...
- PCB Web版SI9000阻抗计算器
在几个月前写过一遍关于: PCB SI9000阻抗计算引擎Web方式实现方法 ,最近开始参考Polar SI9000的界面,将阻抗计算器转为网页版的方式实现. 一.Web版SI9000阻抗计算器 ...
- centos 7更换阿里源
转自 https://blog.csdn.net/jameshadoop/article/details/54881295 centos7 修改yum源为阿里源,某下网络下速度比较快 首先是到yum源 ...
- Lodash 方法库 原生实现 待完结。。。
'use strict' let _ = { // 将数组(array)拆分成多个 size 长度的区块,并将这些区块组成一个新数组. 如果array 无法被分割成全部等长的区块,那么最后剩余的元素将 ...