最近项目中在使用HttpClient有三个超时(connectionRequestTimeout,connectTimeout,socketTimeout)时间理解得不是很透彻,API文档不是很理解,专门写Demo理解了一下。
API的描述如下:
/**
* Returns the timeout in milliseconds used when requesting a connection
* from the connection manager. A timeout value of zero is interpreted
* as an infinite timeout.
*
* A timeout value of zero is interpreted as an infinite timeout.
* A negative value is interpreted as undefined (system default).
*
*
* Default: {@code -1}
*
*/
public int getConnectionRequestTimeout() {
return connectionRequestTimeout;
}
/**
* Determines the timeout in milliseconds until a connection is established.
* A timeout value of zero is interpreted as an infinite timeout.
*
* A timeout value of zero is interpreted as an infinite timeout.
* A negative value is interpreted as undefined (system default).
*
*
* Default: {@code -1}
*
*/
public int getConnectTimeout() {
return connectTimeout;
}
/**
* Defines the socket timeout ({@code SO_TIMEOUT}) in milliseconds,
* which is the timeout for waiting for data or, put differently,
* a maximum period inactivity between two consecutive data packets).
*
* A timeout value of zero is interpreted as an infinite timeout.
* A negative value is interpreted as undefined (system default).
*
*
* Default: {@code -1}
*
*/
public int getSocketTimeout() {
return socketTimeout;
}
API中不能看出正式的含义是什么,经过demo之后,终于知道了各自含义
首先是为这次demo写的服务代码,包含几个controller方法(Spring MVC)
package me.nabil.demo.springbootdemo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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;
/**
* 超时测试
*
* @author nabilzhang
*/
@Controller
@EnableAutoConfiguration
@RequestMapping(value = {"/test"}, method = {RequestMethod.GET})
public class TimeoutTestController {
private static final Logger logger = LoggerFactory.getLogger(TimeoutTestController.class);
/**
* main方法
*
* @param args 参数数组
*/
public static void main(String args[]) {
SpringApplication.run(TimeoutTestController.class, args);
}
/**
* 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";
}
}
如下是客户端的测试Case,下面几个Case分别测试了几个超时时间的各种情况,Case全部通过才可以
package me.nabil.demo.springbootdemo;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
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.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 测试HttpClient超时参数
*
* @author nabilzhang
*/
public class TimeoutTestControllerTest {
/**
* 1.connectionTimeout测试:IP无法链接,链接超时
*
* @throws Exception
*/
@Test
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(1000).build();
httpGet.setConfig(requestConfig);
try {
httpclient.execute(httpGet);
} catch (ConnectTimeoutException exception) {
Assert.assertTrue(exception.getMessage().contains("connect timed out"));
}
}
/**
* 2.socketTimeout测试,服务端没有指定时间内任何响应,会超时
*
* @throws Exception
*/
@Test
public void socketTimeout() throws Exception {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://127.0.0.1:8080/test/socket_timeout");
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(1000).build();
httpGet.setConfig(requestConfig);
try {
httpclient.execute(httpGet);
} catch (SocketTimeoutException exception) {
Assert.assertEquals("Read timed out", exception.getMessage());
}
}
/**
* 3.socketTimeout测试:服务端隔800ms返回一点数据,不会超时
*
* @throws Exception
*/
@Test
public void socketTimeoutNo() {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://127.0.0.1:8080/test/socket_timeout_2");
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(1000)
.setSocketTimeout(1000).setConnectTimeout(1000).build();
httpGet.setConfig(requestConfig);
try {
httpclient.execute(httpGet);
CloseableHttpResponse response = httpclient.execute(httpGet);
System.out.println(String.format("socketTimeoutNo, %s", EntityUtils.toString(response.getEntity())));
} catch (Exception e) {
Assert.fail("服务端隔800ms返回一点数据,不会超时");
}
}
/**
* 4.connectionRequestTimeout测试:指从连接管理器(例如连接池)中拿到连接的超时时间
*
* @throws Exception
*/
@Test
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:8080/test/connection_request_timeout");
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();
}
}
});
}
// 在连接池占满的情况下,拿不到就会抛异常
try {
CloseableHttpResponse response = httpclient.execute(httpGet);
System.out.println(String.format("connectionRequestTimeoutTest: %s",
EntityUtils.toString(response.getEntity())));
Assert.fail();
} catch (Exception exception) {
// 异常是从连接池拿到连接超时
Assert.assertEquals("Timeout waiting for connection from pool", exception.getMessage());
System.out.println(exception.getMessage());
}
}
/**
* 5.connectionRequestTimeout测试,指从连接管理器中拿到连接的超时时间,由于使用基本的连接管理器,链接被占用时,直接无法分配链接
* connectionRequestTimeout并未生效,目前看来该参数只在连接池奏效.
* 该链接管理器(BasicHttpClientConnectionManager)是单线程情况下可以使用,多线程情况下不要使用。
*
* @throws Exception
*/
@Test
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())));
Assert.fail();
} catch (Exception exception) {
System.out.println(new Date());
exception.printStackTrace();
// 异常是从连接池拿到连接超时
Assert.assertEquals("Connection is still allocated", exception.getMessage());
System.out.println(exception.getMessage());
}
}
}
注:上面Case是使用httpclient版本4.5.2测试所得。
org.apache.httpcomponents
httpclient
4.5.2
jar
更深理解可以参照:http://www.baeldung.com/httpclient-connection-management