本文基于httpClient4.5版本
maven依赖
org.apache.httpcomponents httpclient 4.5.3
本文不详细展开对httpclient的详细讲解, 只展示具体用法
httpclient 4.5版本设置超时有三种
1connectTimeOut
建立连接超时时间, 众所周知 http的三次握手机智, 在正式进行数据传输之前需要先完成三次握手. connectTimeOut就是指在进行三次握手行为时所设置的超时. 例如在文末例子中,我访问了一个虚假的域名,http://74.125.203.100, 因为域名本身就不存在, 所以三次握手行为肯定是失败的, 所以链接肯定是无法建立的. 根据如下代码, 设置的时间是5000毫秒 所以运行程序会在五秒之后抛出链接超时异常
2socketTimeOut
建立链接成功数据传输导致的超时时间, 当三次握手行为成功后, 即可通过所建立的http通道进行数据传输, 此时如果超过设置时间并没有获取到对应的数据包就会抛出超时异常, 此处有个需要注意点是socketTimeOut所处理的超时时间是指相邻两个数据包传输之间所经历的时间. 例如链接建立成功后 由于数据过大 服务端每隔1秒传送一个数据包给客户端, 此时设置的超时时间为3秒,一共发送了10个数据包,总共耗时10秒, 请求总共花费10秒, 但是并不会报超时异常, 是因为每次数据包传输之间的时间都不超过3秒,所以不会抛出异常, 总结一下 socketTimeOut是指链接建立成功后,数据包传输之间时间超时限制.
3 connectionRequestTimeOut
从httpclient连接池获取连接超时限制 这个超时没有测试, 以后详细解释
时间单位是毫秒
.setConnectionRequestTimeout(1000) // 从数据库连接池获取连接超时时间设置
.setSocketTimeout(1000) // socket连接建立成功, 数据传输响应超时
.setConnectTimeout(5000) // 建立socket链接超时时间
https访问超时无效问题
由于我在项目中既要访问http接口也要访问https接口,设置超时后发现https不起作用,经过排查跟https 对应的HttpClient对象有关,下面贴上新旧代码
初步排查是创建httpsClient时候导致的超时不生效(此时是https接口访问生效, https接口访问不生效), 目前尚未发现是什么原因,待以后详细查看后再解决
旧代码
// 构造方法 public SSLClient() throws Exception{ // 调用父类构造方法 super(); SSLContext ctx = SSLContext.getInstance("TLS"); 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; } }; ctx.init(null, new TrustManager[]{tm}, null); SSLSocketFactory ssf = new SSLSocketFactory(ctx,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); ClientConnectionManager ccm = this.getConnectionManager(); SchemeRegistry sr = ccm.getSchemeRegistry(); sr.register(new Scheme("https", 443, ssf)); }
新代码
public static CloseableHttpClient createSSLClientDefault(CookieStore cookieStore) { SSLContext sslContext; try { sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { //信任所有 @Override public boolean isTrusted(X509Certificate[] xcs, String string){ return true; } }).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); if(cookieStore == null){ return HttpClients.custom().setSSLSocketFactory(sslsf).build(); }else{ return HttpClients.custom().setSSLSocketFactory(sslsf).setDefaultCookieStore(cookieStore).build(); } } catch (KeyStoreException ex) { logger.info(ex); } catch (NoSuchAlgorithmException ex) { logger.info(ex); } catch (KeyManagementException ex) { logger.info(ex); } return HttpClients.createDefault(); }
超时设置不生效问题
在查看相关文档的时候,发现有朋友说这个问题, 但是自己没有遇到过,所以暂时先不研究, 待研究后再写
测试完整代码, 不包括https
httpclient客户端访问测试代码
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.impl.conn.BasicHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.net.SocketTimeoutException; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * SocketTimeout 服务端链接成功后之间响应超时时间设置 * ConnectTimeout 建立socket链接超时设置 */ public class TimeoutTestControllerTest { public static void main(String[] args) { try { //new TimeoutTestControllerTest().connectionTimeout(); //new TimeoutTestControllerTest().socketTimeout(); //new TimeoutTestControllerTest().socketTimeoutNo(); new TimeoutTestControllerTest().connectionRequestTimeoutWithPoolingConnectionManager(); } catch (Exception e) { e.printStackTrace(); } } /** * 1.ConnectTimeout:IP无法链接,链接超时 链接无法建立时间限制 * * @throws Exception */ public void connectionTimeout() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet("http://74.125.203.100"); RequestConfig requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(1000) .setSocketTimeout(1000) // 服务端相应超时 .setConnectTimeout(5000) // 建立socket链接超时时间 .build(); httpGet.setConfig(requestConfig); long start = 0; try { start = System.currentTimeMillis(); httpclient.execute(httpGet); System.out.println("链接成功"); } catch (ConnectTimeoutException exception) { System.out.println("链接成失败"); exception.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("花费时间:" + (end-start)/1000 + "秒"); } /** * 2.socketTimeout测试,服务端没有指定时间内任何响应,会超时 * * @throws Exception */ public void socketTimeout() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet("http://127.0.0.1:9999/testHttpClientTimeOutController/socket_timeout.do"); RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(4000).build(); httpGet.setConfig(requestConfig); long start = 0; try { start = System.currentTimeMillis(); httpclient.execute(httpGet); } catch (SocketTimeoutException exception) { exception.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("花费时间:" + (end-start)/1000 + "秒"); } /** * 3.socketTimeout测试:服务端隔800ms返回一点数据,不会超时 * * @throws Exception */ public void socketTimeoutNo() { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet("http://127.0.0.1:9999/testHttpClientTimeOutController/socket_timeout_2.do"); RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(100) .setSocketTimeout(900).setConnectTimeout(100).build(); httpGet.setConfig(requestConfig); long start = 0; try { start = System.currentTimeMillis(); httpclient.execute(httpGet); CloseableHttpResponse response = httpclient.execute(httpGet); System.out.println(String.format("socketTimeoutNo, %s", EntityUtils.toString(response.getEntity()))); } catch (Exception e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("花费时间:" + (end-start)/1000 + "秒"); } /** * 4.connectionRequestTimeout测试:指从连接管理器(例如连接池)中拿到连接的超时时间 * * @throws Exception */ public void connectionRequestTimeoutWithPoolingConnectionManager() throws Exception { PoolingHttpClientConnectionManager conMgr = new PoolingHttpClientConnectionManager(); conMgr.setMaxTotal(2); final CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(conMgr).build(); final HttpGet httpGet = new HttpGet("http://127.0.0.1:9999/testHttpClientTimeOutController/connection_request_timeout.do"); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(1000) .setConnectionRequestTimeout(1000).setSocketTimeout(1000).build(); httpGet.setConfig(requestConfig); // 如下多线程占满连接池 ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.submit(new Runnable() { @Override public void run() { try { CloseableHttpResponse response = httpclient.execute(httpGet); System.out.println(String.format("connectionRequestTimeoutTest: %s", EntityUtils.toString(response.getEntity()))); } catch (SocketTimeoutException exception) { System.out.println(exception.getMessage()); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }); } // 在连接池占满的情况下,拿不到就会抛异常 long start = 0; try { start = System.currentTimeMillis(); CloseableHttpResponse response = httpclient.execute(httpGet); System.out.println(String.format("connectionRequestTimeoutTest: %s", EntityUtils.toString(response.getEntity()))); } catch (Exception exception) { // 异常是从连接池拿到连接超时 System.out.println(exception.getMessage()); } long end = System.currentTimeMillis(); System.out.println("花费时间:" + (end-start)/1000 + "秒"); } /** * 5.connectionRequestTimeout测试,指从连接管理器中拿到连接的超时时间,由于使用基本的连接管理器,链接被占用时,直接无法分配链接 * connectionRequestTimeout并未生效,目前看来该参数只在连接池奏效. * 该链接管理器(BasicHttpClientConnectionManager)是单线程情况下可以使用,多线程情况下不要使用。 * * @throws Exception */ public void connectionRequestTimeoutWithBasicConnectionManager() throws Exception { BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(); final CloseableHttpClient httpclient = HttpClients.custom() .setConnectionManager(connManager).setMaxConnPerRoute(1).build(); final HttpGet httpGet = new HttpGet("http://127.0.0.1:8080/test/connection_request_timeout"); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(100000) .setConnectionRequestTimeout(1000000).setSocketTimeout(1000000).build(); httpGet.setConfig(requestConfig); // 如下多线程占满连接 ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.submit(new Runnable() { @Override public void run() { CloseableHttpResponse response = null; try { response = httpclient.execute(httpGet); System.out.println(String.format("connectionRequestTimeoutTest: %s", EntityUtils.toString(response.getEntity()))); } catch (Exception exception) { exception.printStackTrace(); } finally { if (response != null) { try { response.close(); httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } } } }); } System.out.println(new Date()); // 在连接池占满的情况下,拿不到就会抛异常 try { CloseableHttpResponse response = httpclient.execute(httpGet); System.out.println(String.format("connectionRequestTimeoutTest: %s", EntityUtils.toString(response.getEntity()))); } catch (Exception exception) { System.out.println(new Date()); exception.printStackTrace(); // 异常是从连接池拿到连接超时 System.out.println(exception.getMessage()); } } }
服务端代码 用于测试长时间响应
import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 测试HttpClient链接超时机制 */ @Controller @RequestMapping("testHttpClientTimeOutController") public class TestHttpClientTimeOutController { private static final Logger logger = Logger.getLogger(TestHttpClientTimeOutController.class); /** * 1.测试socketOutTimeout,三秒后返回数据 * * @return * @throws InterruptedException */ @RequestMapping(value = {"/socket_timeout"}, method = {RequestMethod.GET}) @ResponseBody String socketTimeout() throws InterruptedException { logger.info("socket_timeout"); Thread.sleep(3000); return "socket_timeout"; } /** * 2.测试socketOutTimeout,每隔0.8秒返回数据 * * @return * @throws InterruptedException */ @RequestMapping(value = {"/socket_timeout_2"}, method = {RequestMethod.GET}) void socketTimeout2(HttpServletResponse response) throws InterruptedException, IOException { logger.info("socket_timeout_2"); for (int i = 0; i < 10; i++) { logger.info("{}" + i); response.getWriter().println("" + i); response.flushBuffer(); Thread.sleep(800); } } /** * 3.测试connectionRequestTimeout用的服务,三秒后返回数据 * * @param request * @return * @throws InterruptedException */ @RequestMapping(value = {"/connection_request_timeout"}, method = {RequestMethod.GET}) @ResponseBody String connectionRequestTimeout(HttpServletRequest request) throws InterruptedException { logger.info(request.getRequestURI()); Thread.sleep(3000); return "connectionRequestTimeout"; } }