HttpClient,RestTemplate超时设置

文章通过一个实例展示了在使用RestTemplate和HttpClient时,如果没有设置超时时间,当目标服务异常如挂起或断网时,请求可能会卡住。通过设置连接超时和读取超时时间,可以避免这种无响应的情况,防止程序卡死。测试中发现,未设置超时时间时,服务器挂掉会导致NoHttpResponseException,而服务器断网或挂起则可能导致SocketTimeoutException。
摘要由CSDN通过智能技术生成

最近在使用RestTemplate的时候发现一个坑,就是某一次发送请求的时候突然卡住不动了,而且没有任何报错提示,重启下服务器就好,可见是因为某些特殊原因导致的,在网上找了下资料发现是因为没有设置超时时间,默认超时时间为永久,就想做一些试验复现下这个bug.

HttpClient试验

不设置HttpClient超时时间,看看发送请求的过程中如果目标服务突然挂掉会怎么样

public class HttpTimeoutTest {
    @Test
    public void testHttpClient() {
        String url = "http://localhost:8081/httpTest/testSleep";
        Map params = new HashMap();
        params.put("time", 30);
        String result = doGet(url, params, null);
        System.out.println(result);
    }

    private static CloseableHttpClient buildClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        //设置连接超时时间
        RequestConfig.Builder builder = RequestConfig.custom();
        /*builder.setConnectTimeout(60 * 1000);
        builder.setSocketTimeout(180 * 1000);*/
        RequestConfig requestConfig = builder.build();
        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
            public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                return true;
            }
        }).build();
        SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext,
                NoopHostnameVerifier.INSTANCE);
        CloseableHttpClient httpclient = HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .setSSLSocketFactory(sslSocketFactory).build();
        return httpclient;
    }

    public static String doGet(String url, Map<String, Object> params, Map<String, String> headersMap) {
        String result = null;
        CloseableHttpClient httpClient = null;
        try {
            httpClient = buildClient();
            String apiUrl = url;
            if (null != params && params.size() > 0) {
                StringBuffer param = new StringBuffer();
                int i = 0;
                for (String key : params.keySet()) {
                    if (i == 0)
                        param.append("?");
                    else
                        param.append("&");
                    param.append(key).append("=").append(params.get(key));
                    i++;
                }
                apiUrl += param;
            }

            HttpGet httpGet = new HttpGet(apiUrl);
            if (null != headersMap && headersMap.size() > 0) {
                for (Map.Entry<String, String> entry : headersMap.entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue();
                    httpGet.addHeader(new BasicHeader(key, value));
                }
            }
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                HttpEntity entity = response.getEntity();
                if (null != entity) {
                    result = EntityUtils.toString(entity, "utf-8");
                }
            } finally {
                response.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IoUtil.close(httpClient);
        }
        return result;
    }
}

发现抛NoHttpResponseException了,目标服务突然断开不会造成卡死

org.apache.http.NoHttpResponseException: The target server failed to respond
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:141)
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)
    at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
    at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
    at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:157)

这是为什么呢?

我们知道,建立tcp连接需要三次握手,断开连接需要四次挥手,当进程主动close掉连接时,请求方是可以正常接收到,如果是进程突然挂掉呢?那操作系统就会去关闭连接,所以请求方还是能知道

那如果是服务器突然断网或者服务器挂掉了呢?这时服务器没法主动断开连接,请求方就会一直等待,这时就会造成卡死现象。

我们给HttpClient设置超时时间,再试试

builder.setConnectTimeout(5 * 1000); // 建立三次握手的超时时间
builder.setSocketTimeout(40 * 1000); // 没收到服务器数据的超时时间

可以发现超过40s还没返回,就会抛SocketTimeoutException

java.net.SocketTimeoutException: Read timed out
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    at java.net.SocketInputStream.read(SocketInputStream.java:170)

connectTimeout是建立连接的时间,如果服务器没挂,进程挂了,则3s就会报Connection refused: connect,否则就要20s,所以最好也设置下

RestTemplate超时时间

接下去我们用RestTemplate重复以上实验,发现会有同样的问题,这是就要设置超时时间了

    @Test
    public void testRestTemplate() {
        RestTemplate restTemplate = restTemplate();
        String url = "http://localhost:8081/httpTest/testSleep?time=30";
        ResponseEntity responseEntity = restTemplate.getForEntity(url, String.class);
        System.out.println(responseEntity.getBody());
    }

    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(clientHttpRequestFactory());
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }

    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
        try {
            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new org.apache.http.ssl.TrustStrategy() {
                public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                    return true;
                }
            }).build();
            httpClientBuilder.setSSLContext(sslContext);
            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslConnectionSocketFactory).build();// 注册http和https请求
            // 开始设置连接池
            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            poolingHttpClientConnectionManager.setMaxTotal(500); // 最大连接数500
            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); // 同路由并发数100
            httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
            HttpClient httpClient = httpClientBuilder.build();
            HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // httpClient连接配置
            clientHttpRequestFactory.setConnectTimeout(20 * 1000);              // 连接超时
            clientHttpRequestFactory.setReadTimeout(30 * 1000);                 // 数据读取超时时间
            clientHttpRequestFactory.setConnectionRequestTimeout(20 * 1000);    // 连接不够用的等待时间
            return clientHttpRequestFactory;
        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
            e.printStackTrace();
        }
        return null;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值