使用httpclient作为http请求的客户端时,我们一般都会设置超时时间,这样就可以避免因为接口长时间无响应或者建立连接耗时比较久导致自己的系统崩溃。通常它里面设置的几个超时时间如下:
RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(3000).setConnectTimeout(3000).setSocketTimeout(3000).build();
setConnectionRequestTimeout():从httpclient连接池中获取连接的超时时间,如果连接池中的连接都被占用,那么超过该时间还未获取到连接就会抛出异常。
setConnectTimeout():建立连接发生三次握手时的超时时间,如果超过该设置的时间连接还未建立成功就会抛出异常。
setSocketTimeout():读取数据超时时间,注意这个时间不是接口响应耗时时间,是接口返回数据两次数据包之间间隔时间,如果超过这个时间间隔还没有新的数据返回,那么就会抛出异常。
下面就分别测试三种超时时间:
在测试超时时间前,需要创建一个接口用于请求:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;/*** 测试接口* * @Author xingo* @Date 2023/11/15*/
@RestController
public class DemoController {@GetMapping("/demo1")public String socketTimeout() throws InterruptedException {TimeUnit.MILLISECONDS.sleep(5000);return "ok";}@GetMapping("/demo2")public void socketTimeout2(HttpServletResponse response) throws InterruptedException, IOException {for (int i = 0; i < 10; i++) {System.out.println(i);response.getWriter().print(i);response.flushBuffer();TimeUnit.MILLISECONDS.sleep(800);}}
}
- 从连接池获取连接超时:
要想测试连接超时时间,需要调整连接池大小,有关连接池配置请参考:httpclient工具类封装 ,这里有两个参数:
// 连接池配置
PoolingHttpClientConnectionManager poolingManager = new PoolingHttpClientConnectionManager(sf);
// 最大连接数
poolingManager.setMaxTotal(2);
// 每个路由最大连接数
poolingManager.setDefaultMaxPerRoute(2);
将两个连接池参数调整为2个,这样就可以很快达到线程池大小,设置一段循环代码一直请求上面的耗时接口:http://localhost:8081/demo1 。测试代码如下:
/*** @Author xingo* @Date 2023/11/15*/
public class TestHttpUtils {public static void main(String[] args) {final String url = "http://localhost:8081/demo1";for(int i = 0; i < 10; i++) {new Thread(() -> {HttpResult result = HttpUtils.getInstance().doGet(url, null, null, "UTF-8", 10000, 1000, 100);System.out.println(result.getBody());}, "thread-" + i).start();}}
}
这里面从连接池中获取连接超时时间设置为100ms,而请求响应耗时是5s,这样很快就会达到线程池大小而不会返回连接,这个测试代码会抛出如下异常:
org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from poolat org.apache.http.impl.conn.PoolingHttpClientConnectionManager.leaseConnection(PoolingHttpClientConnectionManager.java:316)at org.apache.http.impl.conn.PoolingHttpClientConnectionManager$1.get(PoolingHttpClientConnectionManager.java:282)at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:190)at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)at org.example.config.HttpUtils.doGet(HttpUtils.java:677)at org.example.config.TestHttpUtils.lambda$main$0(TestHttpUtils.java:22)at java.lang.Thread.run(Thread.java:748)
所以当项目中的请求并发量比较大,而且耗时较久时就需要调整几个参数:第一个就是连接池大小和单个路由的连接数量;第二个就是要调整从连接池中获取连接的超时时间,避免获取连接超时。
- 建立连接超时
建立连接超时一般是服务端无响应或者服务端无法快速建立连接导致的三次握手超时,这时候就会抛出连接超时异常,如我们调整连接地址为:http://localhost:8082/demo1 。这是一个根本不存在的服务,这时调整测试代码:
/*** @Author xingo* @Date 2023/11/15*/
public class TestHttpUtils {public static void main(String[] args) {final String url = "http://localhost:8082/demo1";HttpResult result = HttpUtils.getInstance().doGet(url);System.out.println(result.getBody());}
}
这时就会抛出建立连接异常:
org.apache.http.conn.HttpHostConnectException: Connect to localhost:8082 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connectat org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:156)at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376)at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)at org.example.config.HttpUtils.doGet(HttpUtils.java:677)at org.example.config.HttpUtils.doGet(HttpUtils.java:584)at org.example.config.TestHttpUtils.main(TestHttpUtils.java:11)
Caused by: java.net.ConnectException: Connection refused: connectat java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)at java.net.Socket.connect(Socket.java:589)at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:75)at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)... 12 more
- 响应超时
响应超时是获取服务端返回数据超时,但是这里的超时不是服务端返回数据整个过程超时,而是返回两个数据包之间的时间间隔超过设置的超时时间。
测试的第二个接口返回数据分多次输出,每次输出数据间隔是800ms,整个响应耗时是8s。第一次测试设置超时时间是1s,超过两次时间间隔,这时不会发生读取数据超时:
/*** @Author xingo* @Date 2023/11/15*/
public class TestHttpUtils {public static void main(String[] args) {final String url = "http://localhost:8081/demo2";HttpResult result = HttpUtils.getInstance().doGet(url, 1000);System.out.println(result.getBody());}
}
结果正常输出:0123456789
如果将超时时间设置为500ms,就会发生读取数据超时异常:
java.net.SocketTimeoutException: Read timed outat java.net.SocketInputStream.socketRead0(Native Method)at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)at java.net.SocketInputStream.read(SocketInputStream.java:171)at java.net.SocketInputStream.read(SocketInputStream.java:141)at org.apache.http.impl.conn.LoggingInputStream.read(LoggingInputStream.java:84)at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)at org.apache.http.impl.io.ChunkedInputStream.getChunkSize(ChunkedInputStream.java:261)at org.apache.http.impl.io.ChunkedInputStream.nextChunk(ChunkedInputStream.java:222)at org.apache.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:183)at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:135)at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)at java.io.InputStreamReader.read(InputStreamReader.java:184)at java.io.Reader.read(Reader.java:140)at org.apache.http.util.EntityUtils.toString(EntityUtils.java:227)at org.apache.http.util.EntityUtils.toString(EntityUtils.java:270)at org.apache.http.util.EntityUtils.toString(EntityUtils.java:290)at org.example.config.HttpUtils.doGet(HttpUtils.java:680)at org.example.config.HttpUtils.doGet(HttpUtils.java:594)at org.example.config.TestHttpUtils.main(TestHttpUtils.java:11)
以上三个超时时间就是在使用apache的httpclient时需要注意的,避免因为设置错误导致程序崩溃。