HttpClient4.5X使用-集成微服务
HttpClient4.5X使用-集成微服务
1.什么是HttpClient
HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。现在HttpClient最新版本为 HttpClient 4.5 .6(官网)
实际应用中我们通常使用在复杂soap协议类型的调用。Springboot一般推荐有RestTemplate 。
2. 整合 HttpClient
1.配置类,主要配置httpClient 连接池的信息
@ConfigurationProperties(prefix = HttpClientProperties.PREFIX) public class HttpClientProperties { public static final String PREFIX = "http.config"; //默认连接池最大连接数 private Integer httpMaxTotal = 300; //默认整个连接池默认最大连接数 private Integer httpDefaultMaxPerRoute = 100; //设置请求超时 默认30s private Integer httpConnectTimeout = 30 * 1000; //设置请求响应超时时间 默认5min private Integer getHttpReadTimeout = 5 * 60 * 1000; //设置从连接池获取中获取到连接最长时间 private Integer httpConnectionRequestTimeout = 500; //设置数据传输的最长时间 private Integer httpSocketTimeout = 5 * 60 * 1000; //设置默认请求类型http 、https private String hostType ="http"; public Integer getHttpMaxTotal() { return httpMaxTotal; } public void setHttpMaxTotal(Integer httpMaxTotal) { this.httpMaxTotal = httpMaxTotal; } public Integer getHttpDefaultMaxPerRoute() { return httpDefaultMaxPerRoute; } public void setHttpDefaultMaxPerRoute(Integer httpDefaultMaxPerRoute) { this.httpDefaultMaxPerRoute = httpDefaultMaxPerRoute; } public Integer getHttpConnectTimeout() { return httpConnectTimeout; } public void setHttpConnectTimeout(Integer httpConnectTimeout) { this.httpConnectTimeout = httpConnectTimeout; } public Integer getGetHttpReadTimeout() { return getHttpReadTimeout; } public void setGetHttpReadTimeout(Integer getHttpReadTimeout) { this.getHttpReadTimeout = getHttpReadTimeout; } public Integer getHttpConnectionRequestTimeout() { return httpConnectionRequestTimeout; } public void setHttpConnectionRequestTimeout(Integer httpConnectionRequestTimeout) { this.httpConnectionRequestTimeout = httpConnectionRequestTimeout; } public Integer getHttpSocketTimeout() { return httpSocketTimeout; } public void setHttpSocketTimeout(Integer httpSocketTimeout) { this.httpSocketTimeout = httpSocketTimeout; } public String getHostType() { return hostType; } public void setHostType(String hostType) { this.hostType = hostType; } }
2.初始化连接池信息
@Configuration @ConditionalOnClass(HttpClientConfig.class) @EnableConfigurationProperties(HttpClientProperties.class) public class HttpClientConfig { @Autowired HttpClientProperties httpClientProperties; @Autowired private PoolingHttpClientConnectionManager manager; @Bean public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() { PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(); // 最大连接数 poolingHttpClientConnectionManager.setMaxTotal(httpClientProperties.getHttpMaxTotal()); // 每个主机的最大并发数 poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpClientProperties.getHttpDefaultMaxPerRoute()); return poolingHttpClientConnectionManager; } @Bean public IdleConnectionEvictor idleConnectionEvictor(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager ) { // 定期清理无效连接 return new IdleConnectionEvictor(poolingHttpClientConnectionManager, 1L, TimeUnit.HOURS); } @Bean public RequestConfig requestConfig() { // 请求配置 // 创建连接的最长时间 return RequestConfig.custom().setConnectTimeout(httpClientProperties.getHttpConnectTimeout()) // 从连接池中获取到连接的最长时间 .setConnectionRequestTimeout(httpClientProperties.getHttpConnectTimeout()) // 数据传输的最长时间 .setSocketTimeout(httpClientProperties.getHttpSocketTimeout()) .build(); } /** * 通过httpClients直接获取 * @param requestConfig 配置信息 * @return */ @Bean @Scope("prototype") //此处是多例模式,因为考虑连接池使用都会手动关闭,同时单例会存在未知问题 public CloseableHttpClient closeableHttpClient(RequestConfig requestConfig) { return HttpClients.custom().setConnectionManager(this.manager).setDefaultRequestConfig(requestConfig).build(); } /** * 通过HttpClientBuilder 间接获取 * @param requestConfig 配置信息 * @return */ @Bean public HttpClientBuilder httpClientBuilder(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager, RequestConfig requestConfig){ return HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager).setDefaultRequestConfig(requestConfig); } }
3.httpclientUtil
@Component public class HttpClientUtil { @Autowired private CloseableHttpClient httpClient; /** * get请求用于普通接口调用 * * @param url 请求地址 * @param headers 请求头 * @param params 参数 * @return * @throws Exception */ public String doGet(String url, Map<String, String> headers, Map<String, String> params) { // //创建一个默认可关闭的httpclient对象 // CloseableHttpClient httpClient = HttpClients.createDefault(); //定义httpResponse 对象 CloseableHttpResponse response = null; //反馈信息 String resultMsg = null; try { //设置请求地址 URIBuilder builder = new URIBuilder(url); //拼接地址 if (!CollectionUtils.isEmpty(params)) { params.forEach((k, v) -> { builder.addParameter(k, v); }); } //设置请求头 HttpGet httpGet = new HttpGet(builder.build()); if (!CollectionUtils.isEmpty(headers)) { headers.entrySet().stream().map(e -> { httpGet.addHeader(e.getKey(), e.getValue()); return httpGet; }); } // 执行请求 response = httpClient.execute(httpGet); // 判断返回状态为200则给返回值赋值 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { //构造反馈 resultMsg = EntityUtils.toString(response.getEntity(), "UTF-8"); } } catch (Exception e) { //构造反馈SRM e.printStackTrace(); } finally { //连接池关闭 httpClientCloseResponse(httpClient, response); } return resultMsg; } /** * 无请求体post请求 post form * * @param url 请求地址 * @param headers 请求头 * @param params 参数 * @return * @throws Exception */ public String doPost(String url, Map<String, String> headers, Map<String, String> params) { //创建一个默认可关闭的httpclient对象 // CloseableHttpClient httpClient = HttpClients.createDefault(); //定义httpResponse 对象 CloseableHttpResponse response = null; //反馈信息 String resultMsg = null; try { //设置请求地址 URIBuilder builder = new URIBuilder(url); //拼接地址 if (!CollectionUtils.isEmpty(params)) { params.forEach((k, v) -> { builder.addParameter(k, v); }); } //设置请求头 HttpPost httpPost = new HttpPost(builder.build()); if (!CollectionUtils.isEmpty(headers)) { headers.forEach((k, v) -> { httpPost.addHeader(k, v); }); } // 执行请求 response = httpClient.execute(httpPost); // 判断返回状态为200则给返回值赋值 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { //构造反馈 resultMsg = EntityUtils.toString(response.getEntity(), "UTF-8"); } } catch (Exception e) { //构造反馈SRM e.printStackTrace(); } finally { //连接池关闭 httpClientCloseResponse(httpClient, response); } return resultMsg; } /** * 请求体post请求 post * * @param url 请求地址 * @param headers 请求头 * @param params 参数信息 * @param body 请求体 * @return 反馈结果 * @throws Exception */ public String doPost(String url, Map<String, String> headers, Map<String, String> params, String body) { //创建一个默认可关闭的httpclient对象 // CloseableHttpClient httpClient = HttpClients.createDefault(); //定义httpResponse 对象 CloseableHttpResponse response = null; //反馈信息 String resultMsg = null; try { //设置请求地址 URIBuilder builder = new URIBuilder(url); //拼接地址 if (!CollectionUtils.isEmpty(params)) { List<NameValuePair> nameValuePairs = params.entrySet().stream().map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue())).collect(Collectors.toList()); builder.setParameters(nameValuePairs); } //设置请求头 HttpPost httpPost = new HttpPost(builder.build()); if (!CollectionUtils.isEmpty(headers)) { headers.forEach((k, v) -> { httpPost.addHeader(k, v); }); } //设置请求体 if (StringUtils.isNotBlank(body)) { httpPost.setEntity(new StringEntity(body, "utf-8")); } // 执行请求 response = httpClient.execute(httpPost); // 判断返回状态为200则给返回值赋值 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { //构造反馈 resultMsg = EntityUtils.toString(response.getEntity(), "UTF-8"); } } catch (Exception e) { //构造反馈SRM e.printStackTrace(); } finally { //连接池关闭 httpClientCloseResponse(httpClient, response); } return resultMsg; } /** * 构建请求的 url * * @param uri * @param querys * @return * @throws UnsupportedEncodingException */ private String buildUrl(String uri, Map<String, String> querys) throws UnsupportedEncodingException { StringBuilder sbUrl = new StringBuilder(); sbUrl.append(uri); if (null != querys) { StringBuilder sbQuery = new StringBuilder(); for (Map.Entry<String, String> query : querys.entrySet()) { if (0 < sbQuery.length()) { sbQuery.append("&"); } if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) { sbQuery.append(query.getValue()); } if (!StringUtils.isBlank(query.getKey())) { sbQuery.append(query.getKey()); if (!StringUtils.isBlank(query.getValue())) { sbQuery.append("="); sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8")); } } } if (0 < sbQuery.length()) { sbUrl.append("?").append(sbQuery); } } return sbUrl.toString(); } /** * 将结果转换成JSONObject * * @param httpResponse * @return * @throws IOException */ public JSONObject getJson(HttpResponse httpResponse) throws IOException { HttpEntity entity = httpResponse.getEntity(); String resp = EntityUtils.toString(entity, "UTF-8"); EntityUtils.consume(entity); return JSON.parseObject(resp); } /** * 关闭连接池反馈 * * @param httpResponse */ public void httpClientCloseResponse(CloseableHttpClient httpClient, CloseableHttpResponse httpResponse) { try { if (httpResponse != null) { httpResponse.close(); } httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } }
3.为什么使用HttpClient并且使用连接池
日常调用时候我们经常碰见这样的问题:Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.ssl.Alerts.getSSLException(Unknown Source) at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source) at sun.security.ssl.Handshaker.fatalSE(Unknown Source) at sun.security.ssl.Handshaker.fatalSE(Unknown Source) at sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source) at sun.security.ssl.ClientHandshaker.processMessage(Unknown Source) at sun.security.ssl.Handshaker.processLoop(Unknown Source) at sun.security.ssl.Handshaker.process_record(Unknown Source) at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source) at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source) at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source) at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(Unknown Source) at com.hx.net.Test3.main(Test3.java:21) Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.validator.PKIXValidator.doBuild(Unknown Source) at sun.security.validator.PKIXValidator.engineValidate(Unknown Source) at sun.security.validator.Validator.validate(Unknown Source) at sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source) at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source) ... 12 more Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.provider.certpath.SunCertPathBuilder.build(Unknown Source) at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source) at java.security.cert.CertPathBuilder.build(Unknown Source) ... 18 more
证书认证问题SSL,网上处理方式参差不齐。因此通过 查看PoolingHttpClientConnectionManager实例化源码我们发现
private static Registry<ConnectionSocketFactory> getDefaultRegistry() { return RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", SSLConnectionSocketFactory.getSocketFactory()) .build(); } public PoolingHttpClientConnectionManager() { this(getDefaultRegistry()); }
httpclient 连接池已经对ssl证书初始化注册是有非常良好的集成。