搜索系统14:HttpClient怎么有两个超时参数,都该怎么配?

如果HttpClient不设置超时选项,可以么?

可以,但调用方的命令由服务方来决定,可能一个请求等2分钟才响应,那么请求方是否可接受,是否有重复请求等逻辑错误。

先明确有两个超时,一个是ConnectTimeout连接超时,一个是readTimeout转数据超时。

httpClient老版本的设置方法:

httpClient = new DefaultHttpClient();
httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000);
httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 1000);
httpPost = new HttpPost(url);
      新版本httpClient的设置方法:

httpClient =HttpClientBuilder.create().build();
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(1000).setConnectTimeout(1000).build();
httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);

httpClient的doPost,把返回值String改成HttpResponse就可以查到更详细的情况:

public static String doHttpPost(String url, Map<String, String> headerMap, Map<String, String> paramMap,String paramType) {
        HttpClient httpClient = null;
        HttpPost httpPost = null;
        String result = null;
        try {
        	httpClient =HttpClientBuilder.create().build();
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
            httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
            
            //请求头
            httpPost.setHeader("Accept", ContentTypeJson);
           
            if (headerMap != null) {
                Iterator iteratorHeader = headerMap.entrySet().iterator();
                while (iteratorHeader.hasNext()) {
                    Map.Entry<String, String> elem = (Map.Entry<String, String>) iteratorHeader.next();
                    httpPost.addHeader(elem.getKey(), elem.getValue());
                }
            }
            if(paramType==null||paramType.equals(PARAM_FORM)) {
            	httpPost.setHeader("Content-Type", ContentType);
            	//设置请求参数
                ArrayList<namevaluepair> nameValuePairs = new ArrayList<namevaluepair>();
                if (paramMap != null) {
                    Iterator iteratorParam = paramMap.entrySet().iterator();
                    while (iteratorParam.hasNext()) {
                        Map.Entry<String, String> elem = (Map.Entry<String, String>) iteratorParam.next();
                        nameValuePairs.add(new BasicNameValuePair(elem.getKey(), elem.getValue()));
                    }
                }
                httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8));
                
            }else if(paramType!=null&¶mType.equals(PARAM_JSON)) {
            	//设置请求参数
            	httpPost.setHeader("Content-Type", ContentTypeJson);
                String json = JsonUtils.toJson(paramMap);
                if (json != null) {
                    StringEntity httpEntity = new StringEntity("JSON: "+json, HTTP.UTF_8);
                    httpEntity.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
                    httpPost.setEntity(httpEntity);
                }
            }
            

            HttpResponse response = httpClient.execute(httpPost);
            if (response != null && HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                HttpEntity resEntity = response.getEntity();
                if (resEntity != null) {
                    result = EntityUtils.toString(resEntity, "UTF-8");
                }
            }
        } catch (Exception ex) {
            logger.error(ex.getMessage(),ex);
        }
        return result;
    }


调用与返回判断:

try {
	//调用接口
	//开始时间
	wc.setStartTime(new Date());
	Map<String,String> param=new HashMap<String,String>();
	param.put("url", imgUrl);
	HttpResponse response=HttpUtil.doPost(dealUrl, null, param,10000,10000);
	if(response==null) {
		return WritingChallengeVo.buildError("接口系统返回为空");
	}else {
		if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
            HttpEntity resEntity = response.getEntity();
            if (resEntity != null) {
                json = EntityUtils.toString(resEntity, "UTF-8");
            }else {
            	return WritingChallengeVo.buildError("接口系统返回为空");
            }
        }else {
        	return WritingChallengeVo.buildError("接口系统异常"+response.getStatusLine().getStatusCode());
        }
		
	}
	if(json==null) {
		return WritingChallengeVo.buildError("接口系统返回为空");
	}
	//正常返回的vo
	WritingDealVo dealVo=JSON.parseObject(json,WritingDealVo.class);
}catch(Exception e) {
	log.error(e.getMessage(),e);
	if(debug) {
		return WritingChallengeVo.buildError("服务端异常"+e.getMessage()+" url:"+dealUrl);
	}else {
		return WritingChallengeVo.buildError("服务端异常");
	}

}finally {
	log.info("rst:"+json);
	//记录调用日志到数据库
}




Solrj中用HttpClientUtil把httpClient的超时参数封装了一下。

在Java的网络应用中,apache的HttpClient用的很多,比如Solrj中就用的是这个来给服务器发请求。其中有两个超时参数可配置,一个是HttpClientUtil.setConnectionTimeout,另一个是HttpClientUtil.setSoTimeout。通过debug代码发现在类org.apache.http.conn.scheme.PlainSocketFactory的connectSocket方法中,这两个参数是要Socket用,HttpClient基本就是转一下。如下图:


进入java.net.Socket类的setSoTimeout方法,可以看到注释:


好了,我们了解到了soTimeout是读取数据的超时时间。那connectionTimeout呢?通过debug我们能看到最终使用的地方:本地的waitForConnect方法。


还是看一下堆栈信息,这个代码位置是java.net.DualStackPlainSocketImpl的85行。直接进不去,可import,然后eclipse下Ctrl+左键进入。



这时有个疑问,既然soTimeout是InputStream.read方法用的,那connectionTimeout是在这个之前还是之后呢?一般来讲,网络程序的执行过程都是先建立连接再读取数据。我还是通过代码来证明下,
在org.apache.http.impl.client.DefaultRequestDirector的execute方法实现里可以查到。先调用了tryConnect方法再调用tryExecute方法,正好对应了connectionTimeout与soTimeout的执行时机,这个代码跨度太大就不贴图了。


这两种超时抛出的异常也不一样,soTimeout会明确的抛一个SocketTimeoutException并且加一个read time out。如下图:


而onnectionTimeout在HttpClient会被包装为一个ConnectionTimeoutException的对象,如本文第一张图所示。



这两个超时参数是给Socket用的,而几乎所有的Java Tcp连接都基于这个Socket,所以这两个的参数的应用适用于很多其它地方。比如,数据库连接、基于HttpClient的爬虫、本文的solrj等。在HttpClient中如果不设置这两个参数,那么就都给默认0。soTimeout的0表示读取时间不限,而connectionTimeout表示连接时间为无穷大。

这个图更直观些:

文/程忠 浏览次数:0次   2017-11-03 17:54:25

相关阅读


评论:
点击刷新

↓ 广告开始-头部带绿为生活 ↓
↑ 广告结束-尾部支持多点击 ↑