Apache HttpClient 5

最近要在非SpringBoot环境调用OpenFeign接口, 需要用到httpclient, 注意到现在 HttpClient 版本已经到 5.2.1 了. 之前在版本4中的一些方法已经变成 deprecated, 于是将之前的工具类升级一下, 顺便把中间遇到的问题记录一下

基础使用方法

首先参考Apache官方的快速开始 httpcomponents-client-5.2.x quickstart, 这是页面上给的例子

Post请求

try (CloseableHttpClient httpclient = HttpClients.createDefault()) {HttpPost httpPost = new HttpPost("http://httpbin.org/post");List<NameValuePair> nvps = new ArrayList<>();nvps.add(new BasicNameValuePair("username", "vip"));nvps.add(new BasicNameValuePair("password", "secret"));httpPost.setEntity(new UrlEncodedFormEntity(nvps));try (CloseableHttpResponse response2 = httpclient.execute(httpPost)) {System.out.println(response2.getCode() + " " + response2.getReasonPhrase());HttpEntity entity2 = response2.getEntity();// do something useful with the response body// and ensure it is fully consumedEntityUtils.consume(entity2);}
}

Get请求

try (CloseableHttpClient httpclient = HttpClients.createDefault()) {HttpGet httpGet = new HttpGet("http://httpbin.org/get");try (CloseableHttpResponse response1 = httpclient.execute(httpGet)) {System.out.println(response1.getCode() + " " + response1.getReasonPhrase());HttpEntity entity1 = response1.getEntity();// do something useful with the response body// and ensure it is fully consumedEntityUtils.consume(entity1);}
}

替换 Deprecated 的 execute 方法

上面的例子可以正常运行, 但是在HttpClient5中, CloseableHttpResponse execute(ClassicHttpRequest request) 这个方法已经被标记为 Deprecated

@Deprecated
HttpResponse execute(ClassicHttpRequest var1) throws IOException;@Deprecated
HttpResponse execute(ClassicHttpRequest var1, HttpContext var2) throws IOException;@Deprecated
ClassicHttpResponse execute(HttpHost var1, ClassicHttpRequest var2) throws IOException;@Deprecated
HttpResponse execute(HttpHost var1, ClassicHttpRequest var2, HttpContext var3) throws IOException;

取而代之的, 是这四个带 HttpClientResponseHandler 参数的 execute 方法

<T> T execute(ClassicHttpRequest var1, HttpClientResponseHandler<? extends T> var2) throws IOException;
<T> T execute(ClassicHttpRequest var1, HttpContext var2, HttpClientResponseHandler<? extends T> var3) throws IOException;
<T> T execute(HttpHost var1, ClassicHttpRequest var2, HttpClientResponseHandler<? extends T> var3) throws IOException;
<T> T execute(HttpHost var1, ClassicHttpRequest var2, HttpContext var3, HttpClientResponseHandler<? extends T> var4) throws IOException;
}

这个 HttpClientResponseHandler 作为响应的处理方法, 里面只有一个接口方法(要注意到这是一个函数式接口)

@FunctionalInterface
public interface HttpClientResponseHandler<T> {T handleResponse(ClassicHttpResponse var1) throws HttpException, IOException;
}

JDK中提供了一个默认的实现, BasicHttpClientResponseHandler(基于 AbstractHttpClientResponseHandler)

public abstract class AbstractHttpClientResponseHandler<T> implements HttpClientResponseHandler<T> {public AbstractHttpClientResponseHandler() {}public T handleResponse(ClassicHttpResponse response) throws IOException {HttpEntity entity = response.getEntity();if (response.getCode() >= 300) {EntityUtils.consume(entity);throw new HttpResponseException(response.getCode(), response.getReasonPhrase());} else {return entity == null ? null : this.handleEntity(entity);}}public abstract T handleEntity(HttpEntity var1) throws IOException;
}public class BasicHttpClientResponseHandler extends AbstractHttpClientResponseHandler<String> {public BasicHttpClientResponseHandler() {}public String handleEntity(HttpEntity entity) throws IOException {try {return EntityUtils.toString(entity);} catch (ParseException var3) {throw new ClientProtocolException(var3);}}public String handleResponse(ClassicHttpResponse response) throws IOException {return (String)super.handleResponse(response);}
}

这样基础的使用方式就改为了下面的形式

public static void main(final String[] args) throws Exception {final CloseableHttpClient httpClient = HttpClients.createDefault();try (httpClient) {final HttpGet httpGet = new HttpGet("https://www.baidu.com/home/other/data/weatherInfo");final String responseBody = httpClient.execute(httpGet, new BasicHttpClientResponseHandler());System.out.println(responseBody);}
}

使用自定义的函数式方法处理响应

可以看到, 上面的 BasicHttpClientResponseHandler 是一个比较简单的实现, 大于300的响应状态码直接抛出异常, 其它的读出字符串. 这样的处理方式对于更精细的使用场景是不够的

  • 需要根据响应状态码判断时
  • 需要对响应解析为Java类时
  • 需要压住异常, 统一返回类对象时

之前在 HttpClient4 时, 可以通过CloseableHttpResponse response = client.execute(httpRequest), 对 response 进行判断, 现在response已经完全被handler 包裹, 需要通过自定义函数式方法处理响应, 看下面的例子

首先定义一个响应结果类, 数据部分使用泛型

@Data
public class Client5Resp<T> implements Serializable {private int code;private String raw;private T data;public Client5Resp(int code, String raw, T data) {this.code = code;this.raw = raw;this.data = data;}
}

然后对响应结果自定义 handler, 因为是函数式接口, 所以很方便在方法中直接定义, 处理的逻辑是:

  1. 如果 T 泛型为String, 直接将 body 作为数据返回
  2. 其它的 T 泛型, 用 Jackson 解开之后返回
  3. 将 status code 一并返回
private static <T> Client5Resp<T> httpRequest(TypeReference<T> tp,HttpUriRequest httpRequest) {try (CloseableHttpClient client = HttpClients.createDefault()) {Client5Resp<T> resp = client.execute(httpRequest, response -> {if (response.getEntity() != null) {String body = EntityUtils.toString(response.getEntity());if (tp.getType() == String.class) {return new Client5Resp<>(response.getCode(), body, (T)body);} // 当需要区分更多类型时可以增加定义else {T t = JacksonUtil.extractByType(body, tp);return new Client5Resp<>(response.getCode(), body, t);}} else {return new Client5Resp<>(response.getCode(), null, null);}});log.info("rsp:{}, body:{}", resp.getCode(), resp.getRaw());return resp;} catch (IOException|NoSuchAlgorithmException|KeyStoreException|KeyManagementException e) {// 当异常也需要返回 Client5Resp 类型对象时可以在catch中封装log.error(e.getMessage(), e);}return null;
}

自定义请求设置和Header

首先是超时设置

RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectionRequestTimeout(60, TimeUnit.SECONDS).setResponseTimeout(60, TimeUnit.SECONDS).build();

然后是 Header

List<Header> headers = List.of(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"),new BasicHeader(HttpHeaders.ACCEPT, "application/json"));

在 HttpGet/HttpPost 中设置

HttpGet httpGet = new HttpGet(...);
if (headers != null) {for (Header header : headers) {httpGet.addHeader(header);}
}
RequestConfig requestConfig = RequestConfig.copy(defaultRequestConfig).build();
httpGet.setConfig(requestConfig);

自定义 HttpClient 增加 SSL TrustAllStrategy

在 HttpClient5 中, 增加 SSL TrustAllStrategy 的方法也有变化, 这是获取 CloseableHttpClient 的代码

final RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectionRequestTimeout(60, TimeUnit.SECONDS).setResponseTimeout(60, TimeUnit.SECONDS).build();
final BasicCookieStore defaultCookieStore = new BasicCookieStore();
final SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(null, new TrustAllStrategy()).build();
final SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create().setSslContext(sslcontext).build();
final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(sslSocketFactory).build();
return HttpClients.custom().setDefaultCookieStore(defaultCookieStore).setDefaultRequestConfig(defaultRequestConfig).setConnectionManager(cm).evictExpiredConnections().build();

增加 Http Proxy

固定的 Proxy

在 HttpClient5 中, RequestConfig.Builder.setProxy()方法已经 Deprecated

@Deprecated
public RequestConfig.Builder setProxy(HttpHost proxy) {this.proxy = proxy;return this;
}

需要使用 HttpClientBuilder.setRoutePlanner(HttpRoutePlanner routePlanner) 进行设置, 和SSL一起, 获取client的代码变成

final HttpHost proxy = new HttpHost(proxyHost, proxyPort);
final DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
return HttpClients.custom().setDefaultCookieStore(defaultCookieStore).setDefaultRequestConfig(defaultRequestConfig).setRoutePlanner(routePlanner).setConnectionManager(cm).evictExpiredConnections().build();

如果需要用户名密码, 需要再增加一个 CredentialsProvider, 变成

final HttpHost proxy = new HttpHost(proxyHost, proxyPort);
final DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(proxyHost, proxyPort),new UsernamePasswordCredentials(authUser, authPasswd.toCharArray()));return HttpClients.custom().setDefaultCookieStore(defaultCookieStore).setDefaultRequestConfig(defaultRequestConfig).setDefaultCredentialsProvider(credsProvider).setRoutePlanner(routePlanner).setConnectionManager(cm).evictExpiredConnections().build();

动态 Proxy

如果需要随时切换 proxy, 需要自己实现一个 HttpRoutePlanner

public static class DynamicProxyRoutePlanner implements HttpRoutePlanner {private DefaultProxyRoutePlanner planner;public DynamicProxyRoutePlanner(HttpHost host){planner = new DefaultProxyRoutePlanner(host);}public void setProxy(HttpHost host){planner = new DefaultProxyRoutePlanner(host);}public HttpRoute determineRoute(HttpHost target, HttpContext context) throws HttpException {return planner.determineRoute(target, context);}
}

然后在代码中进行切换

HttpHost proxy = new HttpHost("127.0.0.1", 1080);
DynamicProxyRoutePlanner routePlanner = new DynamicProxyRoutePlanner(proxy);
CloseableHttpClient httpclient = HttpClients.custom().setRoutePlanner(routePlanner).build();
// 换代理
routePlanner.setProxy(new HttpHost("192.168.0.1", 1081));

构造 Multipart 文件上传请求

首先是构造 HttpEntity 的方法, 这个方法中设置请求为 1个文件 + 多个随表单参数

public static HttpEntity httpEntityBuild(NameValuePair fileNvp, List<NameValuePair> nvps) {File file = new File(fileNvp.getValue());MultipartEntityBuilder builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.STRICT);if (nvps != null && nvps.size() > 0) {for (NameValuePair nvp : nvps) {builder.addTextBody(nvp.getName(), nvp.getValue(), ContentType.DEFAULT_BINARY);}}builder.addBinaryBody(fileNvp.getName(), file, ContentType.DEFAULT_BINARY, fileNvp.getValue());return builder.build();
}

请求流程

// 构造一个文件参数, 其它参数留空
NameValuePair fileNvp = new BasicNameValuePair("sendfile", filePath);
HttpEntity entity = httpEntityBuild(fileNvp, null);
HttpPost httpPost = new HttpPost(api);
httpPost.setEntity(entity);try (CloseableHttpClient client = getClient(...)) {Client5Resp<T> resp = client.execute(httpPost, response->{...});

注意: 在使用 HttpMultipartMode 时对 HttpEntity 设置 Header 要谨慎, 因为 HttpClient 会对 Content-Type增加 Boundary 后缀, 而这个是服务端判断文件边界的重要参数. 如果设置自定义 Header, 需要检查 boundary 是否正确生成. 如果没有的话需要自定义 Content-Type 将 boundary 加进去, 并且通过 EntityBuilder.setBoundary() 将自定义的 boundary 值传给 HttpEntity.

Links

  • Apache httpcomponents-client-5.2.x 文档
  • Apache HttpClient 4.3.5 set proxy

Apache HttpClient 5 笔记: SSL, Proxy 和 Multipart Upload相关推荐

  1. Apache错误日志提示AH02004: SSL Proxy: Peer certificate is expired

    1 .问题 apache错误日志提示如下 AH02004: SSL Proxy: Peer certificate is expired 接下来日志会打印ssl握手失败 然后抓包分析的时候错误提示如下 ...

  2. 一个封装的使用Apache HttpClient进行Http请求(GET、POST、PUT等)的类。

    一个封装的使用Apache HttpClient进行Http请求(GET.POST.PUT等)的类. import com.qunar.payment.gateway.front.channel.mp ...

  3. 新旧apache HttpClient 获取httpClient方法

    在apache httpclient 4.3版本中对很多旧的类进行了deprecated标注,通常比较常用的就是下面两个类了. DefaultHttpClient -> CloseableHtt ...

  4. 【Http】Apache HttpClient 4.5实现https

    全部信任的 import org.apache.commons.collections.MapUtils; import org.apache.http.*; import org.apache.ht ...

  5. Apache HttpClient连接池泄露问题排查

    Apache HttpClient连接池泄露问题排查 问题背景 业务系统主要的业务是一个数据聚合管理平台,其中系统有一个功能是同步所有资源(简称 大同步) 业务同步数据请求数据工具是适配 Apache ...

  6. okhttp3测试框架_easy-okhttp: 这是一个对okhttp3进行封装的工具,提供了更为便捷的方法调用。目的是为了替换难用的apache HttpClient。...

    easy-okhttp 简介 项目easy-okhttp是对okhttp网络框架(https://github.com/square/okhttp)上层封装, 支持文件上传和下载,表单(含文件)提交, ...

  7. 在Apache服务器上安装SSL证书

    在Apache服务器上安装SSL证书 本页目录 前提条件 操作步骤 后续操作 相关文档 阿里云SSL证书服务支持下载证书安装到Apache服务器,从而使Apache服务器支持HTTPS安全访问.本文介 ...

  8. 用Apache HttpClient实现URL重定向

    分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow <用Apac ...

  9. Apache HttpClient 4 3开发指南

    <Apache HttpClient 4.3开发指南> 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.概述 Apache HttpCli ...

最新文章

  1. 第六十二课、单例类模板
  2. ​2012年至今,细数深度学习领域这些年取得的经典成果
  3. Office Tab免费版:标签化浏览和编辑Office文档
  4. 电脑系统哪个最好用_什么除湿机好用_家用除湿机哪个牌子最好用-装修攻略
  5. springboot 优雅的参数校验_SpringBoot 2.x 开发案例之优雅的校验参数
  6. 2017/Province_Java_B/2、纸牌三角形
  7. 2263: neighbor(贪心)
  8. 简单六步上手spring aop,通过各种类型通知,面向切面编程,实现代码解耦(超详细)
  9. Java-HashMap实现原理
  10. arcmap中图斑面积代表_arcmap计算面积_ArcMap怎么重计算图斑面积?arcmap使用手册_arcmap计算面积...
  11. android bilibili sd卡,将bilibili缓存视频移动到SD卡
  12. 判断一个数字是不是素数
  13. arm云服务器虚拟安卓,ARM搭建云手机
  14. 计算机网络 | 实验二 WINPCWP编程
  15. 【MAC M1芯片】PS已解决在M1苹果电脑上出现“液化”和WEB等黑屏问题
  16. jy-12-SPRINGMYBATIS02——云笔记04-刘苍松
  17. 类名.claa 的含义
  18. 计算机操作实训总结,计算机操作系统安全实训心得总结.doc
  19. dns缓存、cnd缓存、浏览器缓存
  20. 高阶累积量四阶矩_概率论中「矩」(moment)的实际含义是什么,高阶矩表示数据的哪些状态?...

热门文章

  1. 新旧Tier 1巨头中国市场“交锋”,华为模式战斗力如何?
  2. DDGScreenShot--iOS 图片处理--多图片拼接 (swift)
  3. 电子技术——电流镜负载的差分放大器
  4. LuaPlus学习(三)
  5. ckeditor5+vue2 实现自定义文件上传插件
  6. 计算机打开硬盘响应慢,windows10机械硬盘运行速度慢的解决方法介绍
  7. fifa15android教程,FIFA15安卓离线单机版
  8. AMD显卡安装驱动错误182 – AMD Installer 无法正常识别 AMD显卡
  9. 如何删除电脑正在运行的文件。
  10. 自制小笔记_C#后期相关问题解决方案