【转】同步的HttpClient使用详解
http://blog.csdn.net/angjunqiang/article/details/54340398
背景
服务端以及客户端在开发过程中不可避免的会使用到网络请求,网络请求可以使用Java原生的URLConnection,也可以使用HttpClient。在日常工作中建议大家使用HttpClient。URLConnection需要自己去实现相关的方法,而HttpClient使用起来方便,且提供许多的API。
HttpClient
1、httpClient的特性
实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
支持自动转向
支持 HTTPS 协议
支持代理服务器等
2、httpClient版本
目前httpClient的最新版本为4.5.2,可以通过如下maven获取Jar包,这里以本人常用的4.2.2为例;
- <span style="font-size:14px;"> <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- <version>4.2.2</version>
- </dependency></span>
3、使用方法
- package com.netease.mail.be.appads.web.controller;
- import java.io.IOException;
- import java.io.UnsupportedEncodingException;
- import java.net.URI;
- import java.net.URISyntaxException;
- import java.util.List;
- import org.apache.commons.lang.StringUtils;
- import org.apache.http.HttpEntity;
- import org.apache.http.HttpResponse;
- import org.apache.http.client.ClientProtocolException;
- import org.apache.http.client.HttpClient;
- import org.apache.http.client.entity.UrlEncodedFormEntity;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.entity.StringEntity;
- import org.apache.http.impl.client.DefaultHttpClient;
- import org.apache.http.message.BasicNameValuePair;
- import org.apache.http.protocol.HTTP;
- import org.apache.http.util.EntityUtils;
- public class PostSample {
- public String setPost(String url, String reqBody,
- List<BasicNameValuePair> urlParams) {
- // step1: 构造HttpClient的实例,类似于打开浏览器
- HttpClient client = new DefaultHttpClient();
- // step2: 创建POST方法的实例,类似于在浏览器地址栏输入url
- HttpPost postMethod = new HttpPost(url);
- String returnValue = "";
- try {
- // step3:设置url相关参数
- if (null != urlParams && !urlParams.isEmpty()) {
- String params = EntityUtils.toString(new UrlEncodedFormEntity(
- urlParams, HTTP.UTF_8));
- postMethod.setURI(new URI(postMethod.getURI().toString() + "?"
- + params));
- }
- // step4:设置请求body参数及头部相关参数
- if (StringUtils.isNotBlank(reqBody)) {
- StringEntity se = new StringEntity(reqBody, "UTF-8");
- se.setContentType("application/text; charset=utf-8");
- postMethod.setEntity(se);
- }
- // step5:执行发送请求
- HttpResponse response = client.execute(postMethod);
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode == 200) {
- // 校验码
- HttpEntity he = response.getEntity();
- returnValue = new String(EntityUtils.toByteArray(he), "UTF-8");
- System.out.println("Bid Request Send Succeed." + returnValue);
- return returnValue;
- } else if (statusCode == 204) {
- System.out.println("Bid Request Send Error, No Content");
- } else {
- System.out.println("Error happens,http status code is "
- + statusCode + ".");
- }
- } catch (UnsupportedEncodingException e) {
- System.out.println(Thread.currentThread().getName()
- + "发送BidRequest请求错误,编码错误:" + e.getMessage());
- } catch (ClientProtocolException e) {
- System.out.println(Thread.currentThread().getName()
- + "发送BidRequest请求错误,协议错误错误:" + e.getMessage());
- } catch (IOException e) {
- System.out.println(Thread.currentThread().getName()
- + "发送BidRequest请求错误,网络IO错误:" + e.getMessage());
- } catch (URISyntaxException e) {
- System.out.println(Thread.currentThread().getName()
- + "发送BidRequest请求错误,URL语法错误:" + e.getMessage());
- } finally {
- // 释放连接,很关键
- postMethod.releaseConnection();
- }
- return returnValue;
- }
- }
注:此方法可以应付并发量不高的网络请求,但是对于高并发的网络请求,此方法存在很大的缺陷及性能问题。具体原因如下:
- 此方法每次都会new一个httpClient对象及HttpPost对象,new的时候需要申请系统资源会消耗一定的时间;
- 虽然请求执行完毕后都执行了postMethod.releaseConnection方法去释放连接,但是这只是应用层的释放,而真正的释放端口需等待一段时间, 这个时间由系统决定。所以在高并发的时候,端口不断的被占用达到系统的最大值,导致后面的网络请求会直接抛出 Cannot assign requested address[不能分配端口]的异常],从而不能进行请求。
高并发的网络请求
在高并发时,需要用到HttpClient的连接池,连接的作用主要是减少创建链接的次数,请求可以复用之前的连接,所以就可以指定一定数量的连接数让连接池去创建,避免了多次创建新的连接并在系统回收连接时不能及时释放端口的情况下达到可用端口的最大值的问题。具体实现如下:
- package com.netease.mail.be.appads.web.controller;
- import java.io.IOException;
- import java.io.UnsupportedEncodingException;
- import java.net.URI;
- import java.net.URISyntaxException;
- import java.nio.charset.Charset;
- import java.security.cert.CertificateException;
- import java.security.cert.X509Certificate;
- import java.util.List;
- import javax.net.ssl.SSLContext;
- import javax.net.ssl.TrustManager;
- import javax.net.ssl.X509TrustManager;
- import org.apache.commons.lang.StringUtils;
- import org.apache.http.HttpEntity;
- import org.apache.http.HttpResponse;
- import org.apache.http.HttpVersion;
- import org.apache.http.client.ClientProtocolException;
- import org.apache.http.client.entity.UrlEncodedFormEntity;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.conn.scheme.PlainSocketFactory;
- import org.apache.http.conn.scheme.Scheme;
- import org.apache.http.conn.scheme.SchemeRegistry;
- import org.apache.http.conn.ssl.SSLSocketFactory;
- import org.apache.http.entity.StringEntity;
- import org.apache.http.impl.client.DefaultHttpClient;
- import org.apache.http.impl.conn.PoolingClientConnectionManager;
- import org.apache.http.message.BasicNameValuePair;
- import org.apache.http.params.BasicHttpParams;
- import org.apache.http.params.CoreConnectionPNames;
- import org.apache.http.params.CoreProtocolPNames;
- import org.apache.http.params.HttpParams;
- import org.apache.http.util.EntityUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.stereotype.Service;
- /**
- * HTTP请求对象,支持post与get方法
- */
- @Service("httpRequesterService")
- public class HttpClientPoolSample {
- private Logger logger = LoggerFactory.getLogger(HttpClientPoolSample.class);
- private static int socketTimeout = 1000;// 设置等待数据超时时间5秒钟 根据业务调整
- private static int connectTimeout = 2000;// 连接超时
- private static int maxConnNum = 4000;// 连接池最大连接数
- private static int maxPerRoute = 1500;// 每个主机的并发最多只有1500
- private static PoolingClientConnectionManager cm;
- private static HttpParams httpParams;
- private static final String DEFAULT_ENCODING = Charset.defaultCharset()
- .name();
- static {
- SchemeRegistry sr = new SchemeRegistry();
- sr.register(new Scheme("http", 80, PlainSocketFactory
- .getSocketFactory()));
- SSLSocketFactory sslFactory;
- try {
- SSLContext sslContext = SSLContext.getInstance("SSL");
- X509TrustManager tm = new X509TrustManager() {
- @Override
- public void checkClientTrusted(X509Certificate[] chain,
- String authType) throws CertificateException {
- }
- @Override
- public void checkServerTrusted(X509Certificate[] chain,
- String authType) throws CertificateException {
- }
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return null;
- }
- };
- sslContext.init(null, new TrustManager[] { tm },
- new java.security.SecureRandom());
- sslFactory = new SSLSocketFactory(sslContext,
- SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
- sr.register(new Scheme("https", 443, sslFactory));
- } catch (Exception e) {
- e.printStackTrace();
- }
- // 初始化连接池
- cm = new PoolingClientConnectionManager(sr);
- cm.setMaxTotal(maxConnNum);
- cm.setDefaultMaxPerRoute(maxPerRoute);
- httpParams = new BasicHttpParams();
- httpParams.setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
- HttpVersion.HTTP_1_1);
- httpParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
- connectTimeout);// 请求超时时间
- httpParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT,
- socketTimeout);// 读取数据超时时间
- // 如果启用了NoDelay策略,httpclient和站点之间传输数据时将会尽可能及时地将发送缓冲区中的数据发送出去、而不考虑网络带宽的利用率,这个策略适合对实时性要求高的场景
- httpParams.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);
- httpParams.setBooleanParameter(
- CoreConnectionPNames.STALE_CONNECTION_CHECK, true);
- }
- public DefaultHttpClient getHttpClient() {
- return new DefaultHttpClient(cm, httpParams);
- }
- public String httpGet(String url, List<BasicNameValuePair> parameters) {
- DefaultHttpClient client = getHttpClient();// 默认会到池中查询可用的连接,如果没有就新建
- HttpGet getMethod = null;
- String returnValue = "";
- try {
- getMethod = new HttpGet(url);
- if (null != parameters) {
- String params = EntityUtils.toString(new UrlEncodedFormEntity(
- parameters, DEFAULT_ENCODING));
- getMethod.setURI(new URI(getMethod.getURI().toString() + "?"
- + params));
- logger.debug("httpGet-getUrl:{}", getMethod.getURI());
- }
- HttpResponse response = client.execute(getMethod);
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode == 200) {
- HttpEntity he = response.getEntity();
- returnValue = new String(EntityUtils.toByteArray(he),
- DEFAULT_ENCODING);
- return returnValue;
- }
- } catch (UnsupportedEncodingException e) {
- logger.error(Thread.currentThread().getName()
- + "httpGet Send Error,Code error:" + e.getMessage());
- } catch (ClientProtocolException e) {
- logger.error(Thread.currentThread().getName()
- + "httpGet Send Error,Protocol error:" + e.getMessage());
- } catch (IOException e) {
- logger.error(Thread.currentThread().getName()
- + "httpGet Send Error,IO error:" + e.getMessage());
- } catch (URISyntaxException e) {
- logger.error(Thread.currentThread().getName()
- + "httpGet Send Error,IO error:" + e.getMessage());
- } finally {// 释放连接,将连接放回到连接池
- getMethod.releaseConnection();
- }
- return returnValue;
- }
- public String httpPost(String url, List<BasicNameValuePair> parameters,
- String requestBody) {
- DefaultHttpClient client = getHttpClient();// 默认会到池中查询可用的连接,如果没有就新建
- HttpPost postMethod = null;
- String returnValue = "";
- try {
- postMethod = new HttpPost(url);
- if (null != parameters) {
- String params = EntityUtils.toString(new UrlEncodedFormEntity(
- parameters, DEFAULT_ENCODING));
- postMethod.setURI(new URI(postMethod.getURI().toString() + "?"
- + params));
- logger.debug("httpPost-getUrl:{}", postMethod.getURI());
- }
- if (StringUtils.isNotBlank(requestBody)) {
- StringEntity se = new StringEntity(requestBody,
- DEFAULT_ENCODING);
- postMethod.setEntity(se);
- }
- HttpResponse response = client.execute(postMethod);
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode == 200) {
- HttpEntity he = response.getEntity();
- returnValue = new String(EntityUtils.toByteArray(he),
- DEFAULT_ENCODING);
- return returnValue;
- }
- } catch (UnsupportedEncodingException e) {
- logger.error(Thread.currentThread().getName()
- + "httpPost Send Error,Code error:" + e.getMessage());
- } catch (ClientProtocolException e) {
- logger.error(Thread.currentThread().getName()
- + "httpPost Send Error,Protocol error:" + e.getMessage());
- } catch (IOException e) {
- logger.error(Thread.currentThread().getName()
- + "httpPost Send Error,IO error:" + e.getMessage());
- } catch (URISyntaxException e) {
- logger.error(Thread.currentThread().getName()
- + "httpPost Send Error,IO error:" + e.getMessage());
- } finally {// 释放连接,将连接放回到连接池
- postMethod.releaseConnection();
- // 释放池子中的空闲连接
- // client.getConnectionManager().closeIdleConnections(30L,
- // TimeUnit.MILLISECONDS);
- }
- return returnValue;
- }
- }
一些参数的解释:
- socketTimeout:socket超时时间,这个超时时间不是连接的超时时间,而是读取返回值的超时时间,即响应超时时间
- connectTimeout:请求连接对方的超时时间
- maxConnNum: 连接池最大并发数。这个设置与应用的并发量有关。比如应用的1s的请求量1000,每个请求需要10ms,则需要的最大并发数为 1000/(1s/10ms)=10,理论上设置的值最好比这个稍大一点点。
- maxPerRoute:请求后端的最大连接数,比如说后端服务有2台机器,而maxConnNum=10,则maxPerRoute=5
根据如上的配置理论上最多只可能会存在4000个连接,对于空闲的连接连接池会根据默认的设置进行回收,当然也可以在程序中进行回收,回收方法client.getConnectionManager().closeIdleConnections(30000L,TimeUnit.MILLSECONDS)表示这个连接维持30秒的空闲时间后进行回收。如果我们将30000L改为0L,则表示立即进行回收,那就失去了连接池的意义,不建议这样做。
【转】同步的HttpClient使用详解相关推荐
- HttpClient使用详解 get与post请求
转自:https://www.jianshu.com/p/375be5929bed 一.HttpClient使用详解与实战一:普通的GET和POST请求 简介 HttpClient是Apache Ja ...
- onenote怎么同步到电脑_详解onenote保存与同步④:本地笔记奇葩的丢失经历
(此文介绍的是我遇到过的,极其奇葩的,丢失onenote本地笔记的惨痛经历.不论是否具有普遍性,对进一步理解缓存还是很有帮助的.大家若有其他丢失经历,可以交流.) 对于onenote本地笔记本而言,由 ...
- mysql主从同步默认延迟_减少mysql主从数据同步延迟问题的详解
基于局域网的master/slave机制在通常情况下已经可以满足'实时'备份的要求了.如果延迟比较大,就先确认以下几个因素: 1. 网络延迟 2. master负载 3. slave负载 一般的做法是 ...
- java同步异步调用_详解java 三种调用机制(同步、回调、异步)
1:同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,jsPwwCe它是一种单向调用 2:回调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口: 3:异步调用:一种类似消 ...
- mysql 5.5 主从同步问题_MySQL 5.5 主从复制异步、半同步以及注意事项详解
大纲 一.前言 二.Mysql 基础知识 三.Mysql 复制(Replication) 四.Mysql 复制(Replication)类型 五.Mysql 主从复制基本步骤 六.Mysql 主从复制 ...
- MySQL 5.5 主从复制异步、半同步以及注意事项详解
大纲 一.前言 二.Mysql 基础知识 三.Mysql 复制(Replication) 四.Mysql 复制(Replication)类型 五.Mysql 主从复制基本步骤 六.Mysql 主从复制 ...
- kafka 同步提交 异步_详解Kafka设计架构核心——Kafka副本机制详解
所谓的副本机制(Replication),也可以称之为备份机制,通常是指分布式系统在多台网络互联的机器上保存有相同的数据拷贝.副本机制有什么好处呢? 1. 提供数据冗余.即使系统部分组件失效,系统依然 ...
- onenote怎么同步到电脑_详解onenote保存与同步⑤:笔记的备份
了解onenote保存与同步机制后,接下来该谈谈笔记备份的问题了.不论你是喜欢云端还是本地笔记,备份都是重中之重,它能最大限度地确保你的笔记不丢失. 前一篇文章谈到了我个人丢失本地笔记的惨痛经历,这个 ...
- rsync文件同步、Inotify-tools参数详解
inotifywait用于等待文件或文件集上的一个待定事件,可以监控任何文件和目录设置,并且可以递归地监控整个目录树: inotifywatch用于收集被监控的文件系统计数据,包括每个inotify事 ...
最新文章
- 【转】POJ 2104 K-th Number(2)
- android 开发--获取文件的扩展名和去掉文件的扩展名
- python3爬虫(9)分布式爬虫与对等分布式爬虫
- NASA告诉你四翼飞行器的飞行原理
- Gauss-Newton算法代码详细解释(转载+自己注释)
- linux centos 的编码格式,Linux/CentOS/fedora下vim显示的字符编码设置
- 搭建本地文件服务器实验报告,下搭建服务器实验报告.pdf
- LeetCode 869. 重新排序得到 2 的幂(排序 全排列)
- PCB参数计算神器-Saturn PCB Design Toolkit下载及安装指南
- kafka创建topic_ELK-基础系列(六)-ELK加入消息队列-Kafka部署
- VideoEdit+ User Manual
- 进度类计算_PERT
- Android 7.0 Settings Summary 小记
- 软件构建中的设计(二)
- shell批量修改文件后缀名
- beta值是一种风险指数
- 论文笔记:Editing-Based SQL Query Generation for Cross-Domain Context-Dependent Questions
- Eclipse的架构
- linux嵌入式在线升级,嵌入式Linux系统在线升级策略 Linuxers必知!
- 多线程下载王者荣耀壁纸
热门文章
- java斗地主怎么出牌_斗地主滑动选牌出牌(Cocos Creator)
- python井字棋ai_[Python100行系列]-井字棋游戏
- 毕业3年,我换了4份工作:好工作,是这样“熬”出来的
- (二)Linux 权限
- 多线程占用修改同一excel文件冲突_漫谈Excel报表移动化的逐级解决方案
- php语法中变量如何定义和使用,PHP语法—PHP的变量
- 用php写成绩switch,帮我看一下,这个php的switch我写的对不对?
- docker mysql扩容_docker pull mysql 时报错,说空间不足
- java程序设计 第三版_清华大学出版社-图书详情-《Java语言程序设计(第3版)》...
- 火牛单片机rtc时钟配置_怎么用STM32F103RTC进行时钟的配置