1. 程式人生 > >Java HttpClient中的三種超時設定區別

Java HttpClient中的三種超時設定區別

最近專案中在使用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.
     * <p>
     * A timeout value of zero is interpreted as an infinite timeout.
     * A negative value is interpreted as undefined (system default).
     * </p>
     * <p>
     * Default: {@code -1}
     * </p>
     */
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. * <p> * A timeout value of zero is interpreted as an infinite timeout. * A negative value is interpreted as undefined (system default). * </p> * <p> * Default: {@code -1} * </p> */
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). * <p> * A timeout value of zero is interpreted as an infinite timeout. * A negative value is interpreted as undefined (system default). * </p> * <p> * Default: {@code -1} * </p> */
public int getSocketTimeout() { return socketTimeout; }

正確解讀一下

API中不能看出正式的含義是什麼,經過demo之後,終於知道了各自含義

1. connectTimeOut:指建立連線的超時時間,比較容易理解

2. connectionRequestTimeOut:指從連線池獲取到連線的超時時間,如果是非連線池的話,該引數暫時沒有發現有什麼用處

3. socketTimeOut:指客戶端和服務進行資料互動的時間,是指兩者之間如果兩個資料包之間的時間大於該時間則認為超時,而不是整個互動的整體時間,比如如果設定1秒超時,如果每隔0.8秒傳輸一次資料,傳輸10次,總共8秒,這樣是不超時的。而如果任意兩個資料包之間的時間超過了1秒,則超時。

程式碼

首先是為這次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)