搜索系统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表示连接时间为无穷大。
这个图更直观些:
