Apache HttpClient 5 笔记: SSL, Proxy 和 Multipart Upload
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, 因为是函数式接口, 所以很方便在方法中直接定义, 处理的逻辑是:
- 如果 T 泛型为String, 直接将 body 作为数据返回
- 其它的 T 泛型, 用 Jackson 解开之后返回
- 将 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相关推荐
- Apache错误日志提示AH02004: SSL Proxy: Peer certificate is expired
1 .问题 apache错误日志提示如下 AH02004: SSL Proxy: Peer certificate is expired 接下来日志会打印ssl握手失败 然后抓包分析的时候错误提示如下 ...
- 一个封装的使用Apache HttpClient进行Http请求(GET、POST、PUT等)的类。
一个封装的使用Apache HttpClient进行Http请求(GET.POST.PUT等)的类. import com.qunar.payment.gateway.front.channel.mp ...
- 新旧apache HttpClient 获取httpClient方法
在apache httpclient 4.3版本中对很多旧的类进行了deprecated标注,通常比较常用的就是下面两个类了. DefaultHttpClient -> CloseableHtt ...
- 【Http】Apache HttpClient 4.5实现https
全部信任的 import org.apache.commons.collections.MapUtils; import org.apache.http.*; import org.apache.ht ...
- Apache HttpClient连接池泄露问题排查
Apache HttpClient连接池泄露问题排查 问题背景 业务系统主要的业务是一个数据聚合管理平台,其中系统有一个功能是同步所有资源(简称 大同步) 业务同步数据请求数据工具是适配 Apache ...
- okhttp3测试框架_easy-okhttp: 这是一个对okhttp3进行封装的工具,提供了更为便捷的方法调用。目的是为了替换难用的apache HttpClient。...
easy-okhttp 简介 项目easy-okhttp是对okhttp网络框架(https://github.com/square/okhttp)上层封装, 支持文件上传和下载,表单(含文件)提交, ...
- 在Apache服务器上安装SSL证书
在Apache服务器上安装SSL证书 本页目录 前提条件 操作步骤 后续操作 相关文档 阿里云SSL证书服务支持下载证书安装到Apache服务器,从而使Apache服务器支持HTTPS安全访问.本文介 ...
- 用Apache HttpClient实现URL重定向
分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow <用Apac ...
- Apache HttpClient 4 3开发指南
<Apache HttpClient 4.3开发指南> 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.概述 Apache HttpCli ...
最新文章
- 第六十二课、单例类模板
- ​2012年至今,细数深度学习领域这些年取得的经典成果
- Office Tab免费版:标签化浏览和编辑Office文档
- 电脑系统哪个最好用_什么除湿机好用_家用除湿机哪个牌子最好用-装修攻略
- springboot 优雅的参数校验_SpringBoot 2.x 开发案例之优雅的校验参数
- 2017/Province_Java_B/2、纸牌三角形
- 2263: neighbor(贪心)
- 简单六步上手spring aop,通过各种类型通知,面向切面编程,实现代码解耦(超详细)
- Java-HashMap实现原理
- arcmap中图斑面积代表_arcmap计算面积_ArcMap怎么重计算图斑面积?arcmap使用手册_arcmap计算面积...
- android bilibili sd卡,将bilibili缓存视频移动到SD卡
- 判断一个数字是不是素数
- arm云服务器虚拟安卓,ARM搭建云手机
- 计算机网络 | 实验二 WINPCWP编程
- 【MAC M1芯片】PS已解决在M1苹果电脑上出现“液化”和WEB等黑屏问题
- jy-12-SPRINGMYBATIS02——云笔记04-刘苍松
- 类名.claa 的含义
- 计算机操作实训总结,计算机操作系统安全实训心得总结.doc
- dns缓存、cnd缓存、浏览器缓存
- 高阶累积量四阶矩_概率论中「矩」(moment)的实际含义是什么,高阶矩表示数据的哪些状态?...
热门文章
- 新旧Tier 1巨头中国市场“交锋”,华为模式战斗力如何?
- DDGScreenShot--iOS 图片处理--多图片拼接 (swift)
- 电子技术——电流镜负载的差分放大器
- LuaPlus学习(三)
- ckeditor5+vue2 实现自定义文件上传插件
- 计算机打开硬盘响应慢,windows10机械硬盘运行速度慢的解决方法介绍
- fifa15android教程,FIFA15安卓离线单机版
- AMD显卡安装驱动错误182 – AMD Installer 无法正常识别 AMD显卡
- 如何删除电脑正在运行的文件。
- 自制小笔记_C#后期相关问题解决方案