随着spring boot快速发展和HTTP2.0的支持力度增加,现在restful标准的微服务接口越来越多,选择一个优秀的HTTP client也越来越重要了。
前言
当我们在maven仓库中搜索关键字(http client)时,会出现十几页的搜索结果,可见在Java社区中http client之多,但是这些当中我们常见的不多。
列举几个常见的:
- HttpURLConnection(JDK)
- Apache Commons HttpClient(或被称为 Apache HttpClient 3.x)
- Apache HttpComponents Client(或被称为 Apache HttpClient 4.x)
- OKHttp(Square,Android应用中常见)
- Asynchronous Http Client(Twitter等)
- google-http-client-xxx(google各种版本)
以上这些HTTP client相对功能简单的,主要实现的功能是调用http协议饿接口,主要在协议层面的控制,比如,设置http method、设置超时时间(连接超时、读取超时)、请求参数、header、cookies以及响应的处理等。还有类似于Apache CXF中的client组件,它是一个经常用在web service服务开发中的组件。它们是在上述基础的http client之上封装了一层,对某一特定使用场景做一些定制化的包装,增加使用的便利性。
Retrofit(Square)、RestTemplate(Spring)、Feign(Netflix)这一类HTTP client是在RESTful标准的微服务中常见的,这一类将client的易用性做到了更好,并且更方便编写restful api的调用。一般还会提供消息转换、参数映射、提供注解等方式,在使用上写少量的代码即可完成功能,更像是一个RPC调用的client编写方式。
Feign
Feign使得Java HTTP客户端编写更方便。Feign灵感来源于Retrofit、JAXRS-2.0和WebSocket。Feign最初是为了降低统一绑定Denominator 到HTTP API的复杂度,不区分是否支持 RESTful。
Feign还以子项目的方式提供了多种Client实现,比如(feign-httpclient、feign-okhttp、feign-ribbon),它们集成了当前比较流行的Http Client组件,如Apache HttpClient、Okhttp、Ribbon等,且其默认的Client实现为HttpURLConnection。
Feign还提供了请求和响应数据格式的编码解码器,用于解析json报文的有feign-gson、feign-jackson等,以及用于解析xml报文的有feign-sax、feign-jaxb等。
RestTemplate
RestTemplate是spring web框架中提供的restful接口调用工具,它也是针对多个基础的http client组件做了集成,如Apache HttpClient、Okhttp等,且其默认的Client实现为HttpURLConnection。
目前(5.0.4.RELEASE)的 RestTemplate 主要有四种 ClientHttpRequestFactory 的实现,它们分别是:
- 基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory
- 基于 Apache HttpComponents Client 的
HttpComponentsClientHttpRequestFactory - 基于 OkHttp 3 的 OkHttpClientHttpRequestFactory
- 基于 Netty4 的 Netty4ClientHttpRequestFactory(已弃用,已经被reactor相关取代)
其中每个都有异步的http client实现,但是已经已经弃用(AsyncRestTemplate已经被WebClient取代)。
但是与Feign相比的缺点是:HTTP Get方法不可以在request body中传递参数,只能是基于URL Template的方式传递参数(虽然很多人不提倡使用HTTP get发送数据);调用方式相对繁琐;设置请求的Http header参数复杂。
优点就是有具有spring框架的优良传统,扩展性特别好。
后来通过查阅资料发现 RestTemplate 默认是使用 spring 自身的 SimpleClientHttpRequestFactory 创建请求对象和对其进行相关设置(如请求头、请求体等),它只支持 PUT 和 POST 方法带请求体,RestTemplate 的 DELETE 方法不支持传入请求体是因为 JDK 中 HttpURLConnection 对象的 delete 方法不支持传入请求体(如果对 HttpURLConnection 对象的 delete 方法传入请求体,在运行时会抛出 IOException)。我尝试使用HttpComponentsClientHttpRequestFactory创建请求对象,依然能在Get方法中带请求体。
如何选择一个优秀的HTTP client
虽然目前来看服务之间调用大部分还是采用的RPC和消息队列,但是目前随着微服务的解决方案越来越多样性,也有很多人选择的HTTP这种通用性很强的协议。
RPC的优势在于,它们基本都使用了基于NIO的高效的网络传输模型,并且针对服务调用场景专门设计了协议和序列化技术,还对传输数据做了压缩处理。HTTP的优势在于成熟稳定、实现简单、支持广泛、兼容性良好、防火墙友好、消息的可读性高。一般在开放API、跨平台的服务间调用和对性能要求不苛刻的场景(HTTP/2可提高性能)中广泛使用。
优秀的HTTP client需要具备的特性:
- 连接池
- 超时间的设置(连接超时、读取超时等)
- 是否支持异步
- 请求和响应的编解码
- 可扩展性
经过我的使用对比,还是觉得Feign的HTTP client使用起来比较方便,推荐使用。
HttpURLConnection(JDK)的坑
- 默认情况不允许修改受限制的Header中的值,受限制的Header如下:
private static final String[] restrictedHeaders = {
/* Restricted by XMLHttpRequest2 */
//"Accept-Charset",
//"Accept-Encoding",
"Access-Control-Request-Headers",
"Access-Control-Request-Method",
"Connection", /* close is allowed */
"Content-Length",
//"Cookie",
//"Cookie2",
"Content-Transfer-Encoding",
//"Date",
//"Expect",
"Host",
"Keep-Alive",
"Origin",
// "Referer",
// "TE",
"Trailer",
"Transfer-Encoding",
"Upgrade",
//"User-Agent",
"Via"
};
可以通过在JVM启动参数中添加:-Dsun.net.http.allowRestrictedHeaders=true,来设置允许修改这些HTTP Header。
- 在使用feign调用http接口时,如果在请求体中写入数据,GET方法会被转成POST方法发送请求,导致服务端报出不支持POST方法的405错误,其实是get方法不能有request body,会制造一定的问题排查困难。查看HttpURLConnection的源码发现如下代码块:
if (!this.doOutput) {
throw new ProtocolException("cannot write to a URLConnection if doOutput=false - call setDoOutput(true)");
} else {
if (this.method.equals("GET")) {
this.method = "POST";
}
}
HTTP的GET方法是否可以带请求体
- HttpURLConnection 不可以
- OkHttpClient 报错:method GET must not have a request body.
- ApacheHttpClient 可以
这几天抽空对HTTP client的使用做了一些调研,总结出了这篇文章,有不全面的或者偏差的点,请在评论中讨论。