利用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(); 
        // 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性 
        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());
   
}
}


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

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鉴权等。

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


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。

猜你喜欢

转载自xiayanghui.iteye.com/blog/2237975