logo头像
Snippet 博客主题

RESTful风格的微服务-HTTP client

随着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灵感来源于RetrofitJAXRS-2.0WebSocket。Feign最初是为了降低统一绑定Denominator 到HTTP API的复杂度,不区分是否支持 RESTful。

Feign还以子项目的方式提供了多种Client实现,比如(feign-httpclientfeign-okhttpfeign-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 的实现,它们分别是:

  1. 基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory
  2. 基于 Apache HttpComponents Client 的
    HttpComponentsClientHttpRequestFactory
  3. 基于 OkHttp 3 的 OkHttpClientHttpRequestFactory
  4. 基于 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)的坑

  1. 默认情况不允许修改受限制的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。

  1. 在使用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的使用做了一些调研,总结出了这篇文章,有不全面的或者偏差的点,请在评论中讨论。

上一篇