利用httpclient调用http请求接口

package com.jd.jdcommons.bi;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import net.sf.json.JSONObject;

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jd.framework.util.string.StringUtil;

public class BiTaskUtil {
private static final Logger logger = LoggerFactory.getLogger(BiTaskUtil.class);
private static final Map<String, String> configCache = new HashMap<String, String>();
private static final CloseableHttpClient httpClient;
private static final String CHARSET = "UTF-8";

private static ObjectMapper objectMapper = null;

static {
RequestConfig config = RequestConfig.custom().setConnectTimeout(60000).setSocketTimeout(15000).build();
httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();

String filePath = BiTaskUtil.class.getResource("/").getPath() + "biconfig/bi-config.properties";
Properties pps = new Properties();
try {
pps.load(new FileInputStream(filePath));
Enumeration<?> enum1 = pps.propertyNames();//得到配置文件的名字
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
if (!StringUtil.isEmpty(strKey)) {
String strValue = pps.getProperty(strKey);
strValue = (strValue == null ? "" : strValue.trim());
configCache.put(strKey.trim(), strValue);
}
}
objectMapper = new ObjectMapper();
[color=red][b]// 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性[/b][/color]
objectMapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);

objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
} catch (IOException e) {
logger.error("bi接口API调用工具类在初始化时,解析配置文件[" + filePath + "]异常:", e);
throw new RuntimeException("bi接口API调用工具类在初始化时,解析配置文件[" + filePath + "]异常:", e);
}
}

/**
* 调用BI保存任务的接口 http:aa/api/task/save?time="1234765464"&data="{}"
* @return SaveTaskRetVo
* @throws Exception
*/
public static SaveTaskRetVo saveTask(String erp, String sqlNum, String sqlContent, String jobName, String targetFileName) throws Exception {
logger.warn("调用bi保存任务接口执行开始!");
SaveTaskRetVo str = null;
List<NameValuePair> paramsList = new ArrayList<NameValuePair>();
Date now = new Date();
paramsList.add(new BasicNameValuePair("time", String.valueOf(now.getTime())));
String url = configCache.get("saveUrl");

JSONObject jsonParam = new JSONObject();
jsonParam.put("jssResourceId", configCache.get("jssResourceId"));
if (!StringUtil.isEmpty(configCache.get("groupName")))
jsonParam.put("groupName", configCache.get("groupName"));
paramsList.add(new BasicNameValuePair("data", jsonParam.toString()));

// 执行保存API接口请求
String result = post(url, paramsList);

if (!StringUtil.isEmpty(result)) {
JSONObject jsonObject = JSONObject.fromObject(result);
str = new SaveTaskRetVo();
if(0 == jsonObject.getInt("code")){
JSONObject responseBody = (JSONObject) jsonObject.get("obj");
str = objectMapper.readValue(responseBody.toString(), SaveTaskRetVo.class);

} else {
logger.warn("调用bi保存任务接口失败 ! url:" + url + ",SQL_NUM=" + sqlNum);
}
str.setCode(jsonObject.getInt("code"));
str.setSuccess(jsonObject.getBoolean("success"));
str.setMessage(jsonObject.getString("message"));
} else {
logger.warn("调用bi保存任务接口返回数据为空 ! url:" + url + ",SQL_NUM=" + sqlNum);
str = new SaveTaskRetVo();
str.setCode(-1);
str.setSuccess(false);
str.setMessage("调用bi保存任务接口,返回值为空!");
}
logger.warn("调用bi保存任务接口执行结束!");

return str;
}

/**
* 调用BI查询任务的接口 http:aa/api/task/query?time="1234765464"&data="{}"
* @return
* @throws Exception
*/
public static QueryTaskRetVo queryTask( int id) throws Exception {
logger.warn("调用bi查询任务接口执行开始!");
QueryTaskRetVo str = null;
List<NameValuePair> paramsList = new ArrayList<NameValuePair>();
Date now = new Date();
paramsList.add(new BasicNameValuePair("time", String.valueOf(now.getTime())));
String url = configCache.get("queryUrl");

JSONObject jsonParam = new JSONObject();
jsonParam.put("id", id);
paramsList.add(new BasicNameValuePair("data", jsonParam.toString()));

// 执行查询API接口请求
String result = post(url, paramsList);

if (!StringUtil.isEmpty(result)) {
JSONObject jsonObject = JSONObject.fromObject(result);
str = new QueryTaskRetVo();
if(0 == jsonObject.getInt("code")){
JSONObject responseBody = (JSONObject) jsonObject.get("obj");
str = objectMapper.readValue(responseBody.toString(), QueryTaskRetVo.class);

} else {
logger.warn("调用bi查询任务接口失败 ! url:" + url + ",任务ID:" + id);
}
str.setCode(jsonObject.getInt("code"));
str.setSuccess(jsonObject.getBoolean("success"));
str.setMessage(jsonObject.getString("message"));
} else {
logger.warn("调用bi查询任务接口返回数据为空 ! url:" + url + ",任务ID:" + id);
str = new QueryTaskRetVo();
str.setCode(-1);
str.setSuccess(false);
str.setMessage("调用bi查询任务接口,返回值为空!");
}
logger.warn("调用bi查询任务接口执行结束!");

return str;
}

/**
* 执行post请求
* @param url 请求url
* @param paramsList 参数列表
* @return
*/
public static String post(String url, List<NameValuePair> paramsList) {
String result = null;
try {
HttpPost httpPost = new HttpPost(url);
//添加参数 , 设置编码
httpPost.setEntity(new UrlEncodedFormEntity(paramsList, CHARSET));

CloseableHttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
if(statusCode != HttpStatus.SC_OK){
httpPost.abort();
logger.error("bi接口请求失败," + "url:" + url + ",返回状态信息:" + statusCode);
throw new RuntimeException("bi接口请求失败," + "url:" + url + ",返回状态信息:" + statusCode);
}
HttpEntity entity = response.getEntity();

if (entity != null){
result = EntityUtils.toString(entity, CHARSET);
}
EntityUtils.consume(entity);
response.close();
if (!StringUtil.isEmpty(result)) {
result = URLDecoder.decode(result, CHARSET);
}
} catch (Exception e) {
logger.error("bi接口请求失败," + "url:" + url + ",异常信息:" + e.getMessage());
throw new RuntimeException("bi接口请求失败," + "url:" + url + ",异常信息:" + e.getMessage());
}
return result;
}

/**
* 得到配置文件的属性值
* @param key
* @return
*/
public static String getString(String key) {
return configCache.get(key);
}

public static void main(String[] args) throws Exception {
/*System.out.println(configCache.get("bi.appId"));

JSONObject jsonParam = new JSONObject();
JSONObject obj = new JSONObject();
obj.put("id", 0001);

jsonParam.put("obj", obj.toString());
jsonParam.put("code", 0);
jsonParam.put("success", true);
jsonParam.put("message", "sssss");

JSONObject jsonObject = JSONObject.fromObject(jsonParam.toString());
SaveTaskRetVo str = new SaveTaskRetVo();
if(!StringUtil.isEmpty(jsonParam.toString()) && (0 == jsonObject.getInt("code"))){
JSONObject responseBody = (JSONObject) jsonObject.get("obj");
str=(SaveTaskRetVo) JSONObject.toBean(responseBody, SaveTaskRetVo.class);

str.setCode(jsonObject.getInt("code"));
str.setSuccess(jsonObject.getBoolean("success"));
str.setMessage(jsonObject.getString("message"));
} else {
logger.info("调用erp 接口 通过组织获取用户 返回失败 !response:" + " url:" + "ss" );
}

int jobType = 5;
jsonParam = new JSONObject();
jsonParam.put("jobName", "");
jsonParam.put("jobType", jobType);
jsonParam.put("marketCode",configCache.get("marketCode"));
jsonParam.put("dbName", configCache.get("dbName"));
jsonParam.put("content", "");
jsonParam.put("erp", "");
jsonParam.put("targetFileType", configCache.get("targetFileType"));
jsonParam.put("targetFileName", "");
jsonParam.put("spaceMark", configCache.get("spaceMark"));
jsonParam.put("targetType", Integer.valueOf(configCache.get("targetType")));
jsonParam.put("jssResourceId", configCache.get("jssResourceId"));
jsonParam.put("groupName", configCache.get("groupName"));

System.out.println("====" + jsonParam.toString());*/

QueryTaskRetVo obj = BiTaskUtil.queryTask(192903);
System.out.println(obj.getCode());

}
}


[color=red][b]补充:每次新建一个httpclient时,会发生connection reset异常;原因如下(来源:http://blog.csdn.net/lcx46/article/details/38984335)mark一下。[/b][/color]

HttpClient引起的TCP连接数高的问题分析
博客分类: Java网络编程
.



【问题现象】

系统上线后出现TCP连接数超过预期阀值,最高值达到8K左右,新上线代码中包含了一文件上传操作,使用的是apache的commons-httpclient包。



【问题分析】

1、先确认是否存在连接未关闭问题引起的。

观察发现,TCP连接数不是一直在增长,而是会有所下降。并且当业务低峰期TCP连接数TCP连接数会降到100左右,这说明TCP连接还是会关闭。



2、确定居高不下的TCP使用情况

使用"netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'"命令发现,处于ESTABLISHED状态的连接数最多,在查看了一下处于ESTABLISHED状态的目的IP,基本上都是文件服务器的IP,这说明还是跟新增加的文件上传操作有关。但是按照代码的逻辑来看,文件上传操作是多线程处理的,一个线程处理一个上传操作,线程池中一共有10个线程,照此分析正常的话应该有10个左右与文件服务器的链接,不应该出现几千个链接。因此怀疑是连接没有主动释放,而是等待连接超时才开始释放。



3、为什么会连接超时

查看了文件上传部分代码,主要代码如下:





Java代码
1.HttpClient client = new HttpClient();
2.MultipartPostMethod method = new MultipartPostMethod(config .getUploadInterface());
3.try{
4. client.executeMethod(method);
5.}catch (Exception e){
6. throw e;
7.}finally{
8. method.releaseConnection();
9.}

从代码里看是已经释放连接了,但是从结果上看没有释放连接,那就产生一个问题,这个地方真的能释放连接吗?我们在释放连接后面增加一行测试代码来看看:







Java代码
1.HttpConnection conn = client.getHttpConnectionManager().getConnection(client.getHostConfiguration());
2.System.out.println(conn.isOpen());

打印出的结果是true,也就是说虽然调用了releaseConnection,但是并没有释放连接!!





4、分析commons-httpclient相关代码

现在怀疑是我们使用的方式不对了,继续分析一下commons-https包中相关代码,首先看一下method.releaseConnection()的代码实现:





Java代码
1.public void releaseConnection() {
2. try {
3. if (this.responseStream != null) {
4. try {
5. // FYI - this may indirectly invoke responseBodyConsumed.
6. this.responseStream.close();
7. } catch (IOException ignore) {
8. }
9. }
10. } finally {
11. ensureConnectionRelease();
12. }
13. }





Java代码
1.private void ensureConnectionRelease() {
2. if (responseConnection != null) {
3. responseConnection.releaseConnection();
4. responseConnection = null;
5. }
6. }

经过debug发现responseStream为null,并且responseConnection也为null,这样改调用就没有实际意义。那么我们应该怎么来释放连接呢?





5、继续分析代码

我们发现在org.apache.commons.httpclient.HttpMethodDirector类的第208行已经在finally中释放连接了:





Java代码
1.finally {
2. if (this.conn != null) {
3. this.conn.setLocked(false);
4. }
5. // If the response has been fully processed, return the connection
6. // to the pool. Use this flag, rather than other tests (like
7. // responseStream == null), as subclasses, might reset the stream,
8. // for example, reading the entire response into a file and then
9. // setting the file as the stream.
10. if (
11. (releaseConnection || method.getResponseBodyAsStream() == null)
12. && this.conn != null
13. ) {
14. this.conn.releaseConnection();
15. }
16. }





Java代码
1.public void releaseConnection(HttpConnection conn) {
2. if (conn != httpConnection) {
3. throw new IllegalStateException("Unexpected release of an unknown connection.");
4. }
5.
6. finishLastResponse(httpConnection);
7.
8. inUse = false;
9.
10. // track the time the connection was made idle
11. idleStartTime = System.currentTimeMillis();
12. }

这个地方我们可以看到了所谓的释放连接并不是真的释放,还是return the connection to pool,照此分析,我们每个线程中new了一个HttpClient类,而每个HttpClient类中的链接都是没有close的,只是归还到httpClient中的pool而已,这些连接也必须等到连接超时才会被释放,由此可以分析出来连接数上涨的原因。那么我们应该怎么使用呢?按照代码的设计,看起来httpclient应该是单例的,但是在httpClient类的javadoc中并没有关于线程安全方面的说明,为此我们再回到官网上看相关文档,在文档(http://hc.apache.org/httpclient-3.x/performance.html)上我们看到如下的说明:







Java代码
1.HttpClient is fully thread-safe when used with a thread-safe connection manager such as MultiThreadedHttpConnectionManager

这说明在多线程环境下应该使用一个全局单例的HttpClient,并且使用MultiThreadHttpConnectionManager来管理Connection。



【相关结论】

1、HttpClient内部使用了池化技术,内部的链接是为了复用。在多线程条件下,可以使用一个全局的HttpClient实例,并且使用MultiThreadHttpConnectionManager来管理Connection。

2、使用开源软件之前一定要读读相关代码,看看官方推荐使用方式。

3、在解决此问题后,读了读httpclient中其他包中的代码,在读的时候发现对于理解http协议帮助很大,特别是文件上传,长连接,auth鉴权等。

[b][color=red]4、相对于httpurlconnection ,httpclient更加丰富,也更加强大,其中apache有两个项目都是httpclient,一个是commonts包下的,这个是通用的,更专业的是org.apache.http.包下的,所以我一般用后者;对于httpclient4.5,连接池管理变更为如下[/color][/b]


PoolingClientConnectionManager conMgr = new PoolingClientConnectionManager();

conMgr.setMaxTotal(200); //设置整个连接池最大连接数 根据自己的场景决定

//是路由的默认最大连接(该值默认为2),限制数量实际使用DefaultMaxPerRoute并非MaxTotal。

//设置过小无法支持大并发(ConnectionPoolTimeoutException: Timeout waiting for connection from pool),路由是对maxTotal的细分。

conMgr.setDefaultMaxPerRoute(conMgr.getMaxTotal());//(目前只有一个路由,因此让他等于最大值)

//另外设置http client的重试次数,默认是3次;当前是禁用掉(如果项目量不到,这个默认即可)

httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));

此处解释下MaxtTotal和DefaultMaxPerRoute的区别:

1、MaxtTotal是整个池子的大小;

2、DefaultMaxPerRoute是根据连接到的主机对MaxTotal的一个细分;比如:

MaxtTotal=400 DefaultMaxPerRoute=200

而我只连接到http://sishuok.com时,到这个主机的并发最多只有200;而不是400;

而我连接到http://sishuok.com 和 http://qq.com时,到每个主机的并发最多只有200;即加起来是400(但不能超过400);所以起作用的设置是DefaultMaxPerRoute。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值