SpringBoot : SpringBoot 2.x Integrated HttpClient

Posted by sem_db on Fri, 04 Mar 2022 15:26:36 +0100

catalogue

  1. Connection pool settings.
  2. Get the settings of connection timeout, establish connection timeout and maintain connection timeout.
  3. Long connection policy settings.
  4. Connection eviction policy settings.
  5. Setting of retry mechanism.
  6. Setting of personalization request parameters.
  7. Appendix.

order

HttpClient can be used to provide an efficient, up-to-date and feature rich client programming toolkit supporting HTTP protocol, and it supports the latest version and suggestions of HTTP protocol.

To send a request and receive a response using HttpClient:

  1. Create CloseableHttpClient object;
  2. Create a request method instance and specify the request URL. Example: if you want to send a Get request, create an HttpGet object; If you want to send a POST request, create an HttpPost object;
  3. If you need to send parameters, call setentity (httpentity) method to set parameters;
  4. Call setHeader(String name,String value) method of HttpGet/HttpPost object to set header information, or call setHeader(Header[] headers) to set a set of header parameters;
  5. Call execute (httpurirequest request request) of the CloseableHttpClient object to send a request. This method returns a CloseableHttpResponse;
  6. Call the getEntity() method of HttpResponse to get the HttpEntity object, which wraps the response content of the server. The program can obtain the response content of the server through the object; Call getAllHeaders(), getHeaders(String name) and other methods of CloseableHttpResponse to obtain the response header of the server;
  7. Release the connection. The connection must be released whether or not the execution method is successful

1. Introduce Maven dependency

 

 <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
 </dependency>

1. HttpClient connection pool analysis

PoolingHttpClientConnectionManager is a connection pool of HttpClientConnection, which can provide concurrent request services for multiple threads. It mainly distributes connections and reclaims connections. For the same remote request, the idle long connection provided by the connection pool will be used preferentially.

Source location: org apache. http. impl. conn.PoolingHttpClientConnectionManager

Default construction method:

 

    /**
     * @since 4.4
     */
    public PoolingHttpClientConnectionManager(
        final HttpClientConnectionOperator httpClientConnectionOperator,
        final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
        final long timeToLive, final TimeUnit timeUnit) {
        super();
        this.configData = new ConfigData();
        //The default configuration of connection pool is 2 for defaultMaxPerRoute and 20 for maxTotal
        this.pool = new CPool(new InternalConnectionFactory(
                this.configData, connFactory), 2, 20, timeToLive, timeUnit);
        //Officials recommend using this to check the availability of permanent links, rather than checking each request 
        this.pool.setValidateAfterInactivity(2000);
        this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator");
        this.isShutDown = new AtomicBoolean(false);
    }
  • maxTotal: the maximum number of connections in the connection pool.
  • Defaultmaxpreroute: the maximum number of connections per route (remote) request.
  • setValidateAfterInactivity: check how long the connection is idle (in milliseconds).

Adjust connection pool parameters displayed:

 

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
// Increase max connections for localhost:80 to 50
HttpHost localhost = new HttpHost("locahost", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);

CloseableHttpClient httpClient = HttpClients.custom()
        .setConnectionManager(cm)
        .build();

Parameter settings, code writing and risks that must be known when using httpclient

1.1 difference between maxtotal and DefaultMaxPerRoute

Parameter configuration: MaxTotal=100, DefaultMaxPerRoute=5

Server side sleep for 2 seconds. This is a screenshot of the response information of the client.

Screenshot of concurrent response png

It can be seen that only five requests call the remote server concurrently, and after receiving the response. Five requests call the server again.

  1. MaxtTotal is the size of the whole pool;
  2. DefaultMaxPerRoute is a subdivision of MaxTotal according to the connected host; For example:
    MaxtTotal=400 DefaultMaxPerRoute=200
    And I only connect to http://sishuok.com When, the concurrency to this host is only 200 at most; Not 400;
    And I'm connected to http://sishuok.com and http://qq.com When, the concurrency to each host is only 200 at most; That is, the total is 400 (but not more than 400); So the setting that works is DefaultMaxPerRoute.

2. SpringBoot integrates HttpClient

2.1 timeout setting

There are three timeout settings in httpClient: timeout for obtaining connection, timeout for establishing connection and timeout for reading data.

 

 //Set up network Configurator
    @Bean
    public RequestConfig requestConfig(){

        return RequestConfig.custom().setConnectionRequestTimeout(2000)  //Timeout for getting connections from link pool
                .setConnectTimeout(2000)    //Timeout time of connecting with the server, timeout time of creating socket connection
                .setSocketTimeout(2000)   //Timeout of socket reading data and timeout of obtaining data from server
                .build();
    }

1. Get available connection timeout ConnectionRequestTimeout from connection pool

Try to get a connection from the connection pool when you want to use a connection in HttpClient. If you haven't got an available connection after waiting for a certain time (for example, there are no idle connections in the connection pool), you will throw a get connection timeout exception.

 

org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool

The number of concurrent requested connections exceeds the DefaultMaxPerRoute setting. If there is still no available connection within the ConnectionRequestTimeout time, the above exception will be thrown. The way to solve the above exception is to appropriately increase the sizes of DefaultMaxPerRoute and MaxTotal.

2. Connection target timeout connectionTimeout

It refers to the connection timeout of connecting to the target url, that is, the maximum time from the customer service end sending the request to establishing a connection with the target url. If a connection has not been established within this time range, a connectionTimeOut exception is thrown.
For example, when testing, change the url to a nonexistent url:“ http://test.com ”, after the timeout of 3000ms, the system reports an exception: org.apache.commons.httpclient.ConnectTimeoutException:The host did not accept the connection within timeout of 3000 ms

3. Waiting for response timeout (data reading timeout) socketTimeout

After connecting to the previous url, obtain the return waiting time of the response, that is, the maximum time to wait for the response to be put back after establishing a connection with the target url. If no response is returned within the specified time, SocketTimeout will be thrown.
The connection url during the test is a url opened locally by me, http://localhost:8080/firstTest.htm?method=test , in my test url, when accessing this link, the thread sleep s for a period of time to simulate the response timeout returned.

2.2 KeepAliveStrategy strategy

Keep alive ——By using the keep alive mechanism, the number of tcp connection establishment can be reduced, which is also thought to reduce TIME_WAIT state connection to improve performance and throughput of HTTP server (fewer tcp connections mean fewer system kernel calls, socket accept() and close() calls). However, long-time tcp connection is easy to lead to invalid occupation of system resources. Improperly configured keep alive may cause greater losses than reusing the connection. Therefore, it is very important to set the keep alive timeout time correctly.

Keep alive: timeout=5, max=100.

It means: the expiration time is 5 seconds, max is up to 100 requests, and the connection is forcibly disconnected. That is, for each new request within the timeout time, max will automatically decrease by 1 until it is 0, and the connection will be forcibly disconnected.

It should be noted that the use of keep alive should be determined according to the business situation. If a few fixed clients visit the server for a long time and high frequency, it is very appropriate to enable keep client!

The default keepClient policy in HttpClient:

org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy

By default, the timeout parameter in keep alive in response is read. If it is not read, it is set to - 1, which represents infinity. However, there is a problem with this setting. Because the real HTTP server is configured to lose the connection after a specific inactivity cycle to save system resources, it often does not notify the client.

Default keep alive policy

 

@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {

    public static final DefaultConnectionKeepAliveStrategy INSTANCE = new DefaultConnectionKeepAliveStrategy();

    @Override
    public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
        Args.notNull(response, "HTTP response");
        final HeaderElementIterator it = new BasicHeaderElementIterator(
                response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            final HeaderElement he = it.nextElement();
            final String param = he.getName();
            final String value = he.getValue();
            if (value != null && param.equalsIgnoreCase("timeout")) {
                try {
                    return Long.parseLong(value) * 1000;
                } catch(final NumberFormatException ignore) {
                }
            }
        }
        return -1;
    }
}

Solution: you can customize the keep alive policy. If you don't read it, set the save connection to 60s.

 

    @Bean
    public HttpClientBuilder httpClientBuilder(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        //Set up connection pool
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
        //Set timeout
        httpClientBuilder.setDefaultRequestConfig(requestConfig());
        //Define that the connection manager will be shared by multiple client instances. If the connection manager is shared, its life cycle should be managed by the caller. If the client is closed, it will not be closed.
        httpClientBuilder.setConnectionManagerShared(true);
        //Set KeepAlive
        ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {

            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                // Honor 'keep-alive' header
                HeaderElementIterator it = new BasicHeaderElementIterator(
                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    HeaderElement he = it.nextElement();
                    String param = he.getName();
                    String value = he.getValue();
                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        try {
                            return Long.parseLong(value) * 1000;
                        } catch(NumberFormatException ignore) {
                        }
                    }
                }
                HttpHost target = (HttpHost) context.getAttribute(
                        HttpClientContext.HTTP_TARGET_HOST);
                if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
                    // Keep alive for 5 seconds only
                    return 5 * 1000;
                } else {
                    // otherwise keep alive for 30 seconds
                    return 30 * 1000;
                }
            }

        };
        httpClientBuilder.setKeepAliveStrategy(myStrategy);

        return httpClientBuilder;
    }

2.3 connection eviction policy

When a connection is released to the connection pool, it can remain active without monitoring the status of the socket and any I/O events. If the connection is closed on the server side, the client connection cannot detect the changes in the connection state and close the socket on the local side to make an appropriate response.

HttpClient tries to solve this problem by testing whether the connection is valid, but it is closed on the server side, and the failed connection check is not 100% reliable. The only solution: create a monitoring thread to recycle connections that are considered expired due to prolonged inactivity.

 

public class IdleConnectionMonitorThread extends Thread {
    
    private final HttpClientConnectionManager connMgr;
    private volatile boolean shutdown;
    
    public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
        super();
        this.connMgr = connMgr;
    }

    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    wait(5000);
                    // Close expired connections
                    connMgr.closeExpiredConnections();
                    // Optionally, close connections
                    // that have been idle longer than 30 sec
                    connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                }
            }
        } catch (InterruptedException ex) {
            // terminate
        }
    }
    
    public void shutdown() {
        shutdown = true;
        synchronized (this) {
            notifyAll();
        }
    }
    
}

The monitoring thread can periodically call the ClientConnectionManager#closeExpiredConnections() method to close all expired connections and withdraw the closed connections from the connection pool. It can also optionally call the ClientConnectionManager#closeIdleConnections() method to close all connections that have been idle for more than a given period of time. httpclient parameter configuration

 

    @Bean
    public HttpClientBuilder httpClientBuilder(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        //Set connection pool
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
        //Set timeout
        httpClientBuilder.setDefaultRequestConfig(requestConfig());
        //Define that the connection manager will be shared by multiple client instances. If the connection manager is shared, its life cycle should be managed by the caller. If the client is closed, it will not be closed.
        httpClientBuilder.setConnectionManagerShared(true);
       //Start the thread and empty the failed connection once in 5 seconds
        new IdleConnectionMonitorThread(poolingHttpClientConnectionManager).start();
        return httpClientBuilder;
    }

2.4 retry mechanism of httpclient

This parameter is recommended to be closed if there are a large number of concurrent requests. If the number of items is less than, this is the default.

HttpClient uses connection pool PoolingHttpClientConnectionManager

Set retry policy: org apache. http. impl. client. DefaultHttpRequestRetryHandler

Source code of retry mechanism: org apache. http. impl. execchain. RetryExec#execute

The retttryhandler policy is not used by default, regardless of the setting of htttryhandler.

Construction method of default policy:

 

public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) {
        this(retryCount, requestSentRetryEnabled, Arrays.asList(
                InterruptedIOException.class,
                UnknownHostException.class,
                ConnectException.class,
                SSLException.class));
    }
  1. retryCount: number of retries;
  2. requestSentRetryEnabled: if a request is retried successfully, whether it will be retried again;
  3. InterruptedIOException, UnknownHostException, ConnectException and sslexexception. In case of these 4 exceptions (and subclass exceptions), do not retry;

Verification method of default retry policy: org apache. http. impl. client. DefaultHttpRequestRetryHandler # retryRequest

 

    @Override
    public boolean retryRequest(
            final IOException exception,
            final int executionCount,
            final HttpContext context) {
        Args.notNull(exception, "Exception parameter");
        Args.notNull(context, "HTTP context");
        if (executionCount > this.retryCount) {
            // Do not retry if over max retry count
            return false;
        }
        if (this.nonRetriableClasses.contains(exception.getClass())) {
            return false;
        }
        for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) {
            if (rejectException.isInstance(exception)) {
                return false;
            }
        }
        final HttpClientContext clientContext = HttpClientContext.adapt(context);
        final HttpRequest request = clientContext.getRequest();
        //If the same request has been terminated in the asynchronous task, it will not be retried
        if(requestIsAborted(request)){
            return false;
        }
        //Determine whether the request is idempotent
        if (handleAsIdempotent(request)) {
            // Retry if the request is considered idempotent
            return true;
        }
        //If the request is not sent successfully, or if it is allowed to be sent successfully, it can be sent again
        if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) {
            // Retry if the request has not been sent fully or
            // if it's OK to retry methods that have been sent
            return true;
        }
        // otherwise do not retry
        return false;
    }

About the default retry policy:

  1. If it is retried more than 3 times, it will not be retried;
  2. If the retry is a special exception and its subclasses, it will not be retried (see below);
  3. The same request is not requested if the asynchronous task is terminated;
  4. Idempotent methods can be retried, such as Get;
  5. If the request is not sent successfully, it can be retried;

How to judge whether the request is sent successfully?

Source code: org apache. http. protocol. Httpcorecontext # isrequestsent according to http request_ Send parameter to judge whether the transmission is successful.

The underlying communication of RetryExec uses MainClientExec, and the underlying communication of MainClientExec calls httprequesteexecutor doSendRequest().

So http request_ The send parameter is set through httprequesteexecutor Set by dosendrequest() method.

Exception not retried

Research on HttpClient retry strategy

  1. InterruptedIOException, thread interrupt exception
  2. UnknownHostException, corresponding host not found
  3. ConnectException, found host, but failed to establish connection.
  4. SSLException, https authentication exception

In addition, we often mention two types of timeout, connection timeout and read timeout:

  • java.net.SocketTimeoutException: Read timed out
  • java.net.SocketTimeoutException: connect timed out
    Both timeouts are sockettimeoutexceptions, which inherit from InterruptedIOException. They belong to the first thread interrupt exception above and will not be retried.

Idempotent request not retried

In the default retry class: handleAsIdempotent(request) will check whether the request is idempotent. Default implementation:

 

public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler {
    protected boolean handleAsIdempotent(final HttpRequest request) {
        return !(request instanceof HttpEntityEnclosingRequest);
    }
}

Judge whether the request belongs to HttpEntityEnclosingRequest class.

Subclass png

 

This will cause the handleAsIdempotent method to return false if it is a post request, that is, it will not retry.

How to prohibit retry

In HttpClinetBuilder, when RetryExec executor is selected in its Build() method, the retry policy is enabled by default.
Therefore, we can manually disable it when building an httpClient instance.

 

httpClientBuilder.disableAutomaticRetries();

How to customize retry policy

Custom retry policy

Just need to implement org apache. http. client. Httprequestretryhandler interface, just re the methods inside.

The source code of the retry strategy is at org apache. http. impl. execchain. Retryexec#execute implementation.

 

httpClientBuilder.setRetryHandler(new MyHttpRequestRetryHandler());

2.5 setting personalized request parameters

Because in the configuration file, we have configured the default socket timeout (the maximum time to establish a connection, that is, the response timeout), but in the actual business, different requests have different response timeout. How to set different timeout times for different businesses?

We know that in fact, the CloseableHttpClient we injected is an abstract class. In fact, it will be org apache. http. impl. client. Internalhttpclient type is injected in, so we use org apache. http. client. methods. When httprequestbase (Note: the common parent of httpPost/httpGet) sends a request, the RequestConfig parameter can be set separately.

RequestConfig.Builder custom = RequestConfig.copy(configClient.getConfig()); Get requestconfig Builder object to set personalization parameters.

 

    private static String doHttp(HttpRequestBase request, int socketTimeout) throws IOException {
        //Set timeout
        if (socketTimeout > 0) {
            //Get the original configuration
            //Actual injection type org apache. http. impl. client. InternalHttpClient
            Configurable configClient = (Configurable) httpClient;
            RequestConfig.Builder custom = RequestConfig.copy(configClient.getConfig());
            //Set personalized configuration
            RequestConfig config = custom.setSocketTimeout(socketTimeout).build();
            request.setConfig(config);
        }
        ResponseHandler<String> handler = new BasicResponseHandler();
        String response = httpClient.execute(request, handler);
        return response;
    }
}

2.6 HttpClient response data processing

EntityUtils.consume will release all the resources held by httpEntity, which actually means releasing any underlying flow and putting the connection object back into the pool (in the case of multithreading when connecting to the pool), or releasing the connection manager to process the next request.

Source code: org apache. http. impl. client. CloseableHttpClient # execute
If you get a custom response entity, you can implement org apache. http. client. Responsehandler interface.

How to process the response:

 

    @Test
    public void test1() throws IOException, InterruptedException {
        HttpPost httpPost = new HttpPost("http://www.baidu.com");
        httpPost.setConfig(requestConfig);
        Map<String, String> innerReq = new HashMap<>();
        innerReq.put("XX", "data1");
        innerReq.put("YY", "data2");
        String innerReqJson = JSONObject.toJSONString(innerReq);
        StringEntity entity = new StringEntity(innerReqJson, "UTF-8");
        httpPost.addHeader("content-type", "application/json;charset=UTF-8");
        httpPost.setEntity(entity);
        //Request execution
        CloseableHttpResponse execute = closeableHttpClient.execute(httpPost);
        //Set return data
        String res = EntityUtils.toString(execute.getEntity(), "UTF-8");
        //close resource
        EntityUtils.consume(execute.getEntity());
        log.info(res);
    }

close resource

Why do I use entityutils consume(httpEntity);? (Why did the author use EntityUtils.consume(httpEntity);?)

 

EntityUtils.consume(execute.getEntity());

(New) use ResponseHandler to process response data

Whether the request is executed successfully or causes an exception, HttpClient will automatically ensure that the connection is released back to the connection manager.

 

    @Test
    public void test() throws IOException, InterruptedException {
        HttpPost httpPost = new HttpPost("http://www.baidu.com");
        httpPost.setConfig(requestConfig);
        Map<String, String> innerReq = new HashMap<>();
        innerReq.put("XX", "data1");
        innerReq.put("YY", "data2");
        String innerReqJson = JSONObject.toJSONString(innerReq);
        StringEntity entity = new StringEntity(innerReqJson, "UTF-8");
        httpPost.addHeader("content-type", "application/json;charset=UTF-8");
        httpPost.setEntity(entity);
        //Custom ResponseHandler
        ResponseHandler<ResponseVo> handler = new ResponseHandler<ResponseVo>() {
            @Override
            public ResponseVo handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
                final StatusLine statusLine = response.getStatusLine();
                final HttpEntity entity = response.getEntity();
                if (statusLine.getStatusCode() >= 300) {
                    EntityUtils.consume(entity);
                    throw new HttpResponseException(statusLine.getStatusCode(),
                            statusLine.getReasonPhrase());
                }

                if (entity == null) {
                    throw new ClientProtocolException("Abnormal!");
                }
                String res = EntityUtils.toString(entity);
                ResponseVo responseVo = JSON.parseObject(res, ResponseVo.class);
                return responseVo;
            }
        };
        //Whether the request is executed successfully or causes an exception, HttpClient will automatically ensure that the connection is released back to the connection manager.
        ResponseHandler<String> responseHandler = new BasicResponseHandler();
//        String execute1 = closeableHttpClient.execute(httpPost, responseHandler);
        ResponseVo execute = closeableHttpClient.execute(httpPost, handler);
        log.info(JSON.toJSONString(execute));
    }

2.7 request tools

Receive POST request:

 

    public static String doPost(String url, Object paramsObj, int socketTimeout) throws IOException {
        HttpPost post = new HttpPost(url);
        StringEntity entity = new StringEntity(JSONObject.toJSONString(paramsObj), "UTF-8");
        post.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        post.setEntity(entity);
        return doHttp(post, socketTimeout);
    }

Receive GET request:

 

    public static String doGet(String url, Map<String, String> params, int socketTimeout) throws IOException, URISyntaxException {
        URIBuilder uriBuilder = new URIBuilder(url);
        uriBuilder.setCharset(Consts.UTF_8).build();
        if (params != null) {
            params.forEach(uriBuilder::addParameter);
        }
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        //Set request header
        httpGet.addHeader(HttpHeaders.CONTENT_TYPE, "text/html;charset=UTF-8");

        return doHttp(httpGet, socketTimeout);
    }

Public processing class:

 

private static String doHttp(HttpRequestBase request, int socketTimeout) throws IOException {
        //Set timeout
        if (socketTimeout > 0) {
            //Get the original configuration
            //Actual injection type org apache. http. impl. client. InternalHttpClient
            Configurable configClient = (Configurable) httpClient;
            RequestConfig.Builder custom = RequestConfig.copy(configClient.getConfig());
            //Set personalized configuration
            RequestConfig config = custom.setSocketTimeout(socketTimeout).build();
            request.setConfig(config);
        }
        ResponseHandler<String> handler = new BasicResponseHandler();
        long startPoint = System.currentTimeMillis();
        String response = httpClient.execute(request, handler);
        log.info("Request time[{}], Interface return information[{}]", System.currentTimeMillis() - startPoint, response);
        return response;
    }

Two ways to pass parameters in http post method

Appendix:

Appendix code reference, spring boot integration HttpClient

httpClient configuration:

 

@Configuration
public class HttpClientConfig {

    @Autowired
    private HttpClientProperties httpClientProperties;


    /**
     * Display and modify the httpClient connection pool parameters. Note: if the settings are not displayed, there should be a default configuration!
     *
     * @return
     */
    @Bean
    public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
        //The created object has been set: the processing Socket link factory object corresponding to Protocol Http and Https.
        PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager();
        httpClientConnectionManager.setDefaultMaxPerRoute(httpClientProperties.getDefaultMaxPerRoute());
        httpClientConnectionManager.setMaxTotal(httpClientProperties.getMaxTotal());
        httpClientConnectionManager.setValidateAfterInactivity(httpClientProperties.getValidateAfterInactivity());
        return httpClientConnectionManager;
    }


    //Set up network Configurator
    @Bean
    public RequestConfig requestConfig(){

        return RequestConfig.custom().setConnectionRequestTimeout(httpClientProperties.getConnectionRequestTimeout())  //Timeout for getting connections from link pool
                .setConnectTimeout(httpClientProperties.getConnectTimeout())    //Timeout time of connecting with the server, timeout time of creating socket connection
                .setSocketTimeout(httpClientProperties.getSocketTimeout())   //Timeout of socket reading data and timeout of obtaining data from server
//                . setsockettimeout (1) / / timeout of socket reading data and timeout of obtaining data from the server
//                . setExpectContinueEnabled(true) / / set whether to enable the client. Before sending the Request Message, judge whether the server is willing to accept the message body sent by the client
                .build();
    }

    /**
     * Instantiate the connection pool and set the connection pool manager
     *
     * @param poolingHttpClientConnectionManager
     * @return
     */
    @Bean
    public HttpClientBuilder httpClientBuilder(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        //Set up connection pool
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
        //Set timeout
        httpClientBuilder.setDefaultRequestConfig(requestConfig());
        //Define that the connection manager will be shared by multiple client instances. If the connection manager is shared, its life cycle should be managed by the caller. If the client is closed, it will not be closed.
        httpClientBuilder.setConnectionManagerShared(true);
        //Set keep alive
        ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {

            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                // Honor 'keep-alive' header
                HeaderElementIterator it = new BasicHeaderElementIterator(
                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    HeaderElement he = it.nextElement();
                    String param = he.getName();
                    String value = he.getValue();
                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        try {
                            return Long.parseLong(value) * 1000;
                        } catch(NumberFormatException ignore) {
                        }
                    }
                }
                HttpHost target = (HttpHost) context.getAttribute(
                        HttpClientContext.HTTP_TARGET_HOST);
                if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
                    // Keep alive for 5 seconds only
                    return 5 * 1000;
                } else {
                    // otherwise keep alive for 30 seconds
                    return 30 * 1000;
                }
            }

        };
        httpClientBuilder.setKeepAliveStrategy(myStrategy);
//        httpClientBuilder.setRetryHandler(new MyHttpRequestRetryHandler());
//        httpClientBuilder.disableAutomaticRetries();
        new IdleConnectionMonitorThread(poolingHttpClientConnectionManager).start();//Start the thread and empty the failed connection once in 5 seconds
        return httpClientBuilder;
    }


    @Bean
    public CloseableHttpClient getCloseableHttpClient(HttpClientBuilder httpClientBuilder) {
        return httpClientBuilder.build();
    }

}

Scheduled cleanup thread

 

@Slf4j
public  class IdleConnectionMonitorThread extends Thread {

    private final HttpClientConnectionManager connMgr;
    private volatile boolean shutdown;

    public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
        super();
        this.connMgr = connMgr;
    }

    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    wait(5000);
                    log.info("[Scheduled clearing of expired connections starts...]");
                    // Close timed out connections
                    connMgr.closeExpiredConnections();
                    // Close connections with idle time greater than 30s
                    connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                }
            }
        } catch (InterruptedException ex) {
            // terminate
        }
    }

    public void shutdown() {
        shutdown = true;
        synchronized (this) {
            notifyAll();
        }
    }
}

 

spring: 
  http-pool:
    # Maximum connections in connection pool
    max-total: 3000
    # Maximum number of connections per round request
    default-max-per-route: 20
    # How long (in milliseconds) is idle to verify the validity of the connection
    validate-after-inactivity: 2000
    # Maximum timeout for establishing a connection (MS)
    connect-timeout: 20000 
    # Gets the maximum timeout (in milliseconds) for a connection
    connection-request-timeout: 20000
    # The maximum time (in milliseconds) to remain connected to the server
    socket-timeout: 20000  

 

@ConfigurationProperties(prefix = "spring.http-pool")
public class HttpClientProperties {
    //Default configuration
    private int defaultMaxPerRoute = 2;
    private int maxTotal = 20;
    private int validateAfterInactivity = 2000;
    private int connectTimeout = 2000;
    private int connectionRequestTimeout = 20000;
    private int socketTimeout = 20000;

}

Tools:

 

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.BeanUtils;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @program: springboot
 * @description: httpClient Communication tools
 * @author: xueruiye
 * @create: 2019-08-13 17:18
 * <p>
 * Note: set the tool class of httpClient. Provides static methods for get and post access.
 * get Request content type = = text / HTML; charset=UTF-8
 * post Request content type = Application / JSON; charset=UTF-8
 * You can flexibly set socket timeout (socket connection time, i.e. timeout, unit: Ms!)
 */
@Slf4j
public class HttpClientUtils {

    private static CloseableHttpClient httpClient = SpringContextUtil.getBean("customCloseableHttpClient", CloseableHttpClient.class);


    /**
     * get Request content type = = text / HTML; charset=UTF-8
     *
     * @param url       url address
     * @param paramsObj params Object object composed of parameters
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    public static <T> String doGet(String url, Object paramsObj) throws IOException, URISyntaxException {
        Map<String, String> params = JSON.parseObject(JSON.toJSONString(paramsObj), Map.class);
        return doGet(url, params, -1);
    }

    public static <T> String doGet(String url, Object paramsObj, int socketTimeout) throws IOException, URISyntaxException {
        Map<String, String> params = JSON.parseObject(JSON.toJSONString(paramsObj), Map.class);
        return doGet(url, params, socketTimeout);
    }


    /**
     * post The call uses the timeout configured in the configuration file
     *
     * @param url          Request address
     * @param paramsObj    Request entity
     * @param responseType Request content example: new typereference < list < account > > () {}
     * @param <T>
     * @return
     * @throws IOException
     */
    public static <T> T doPost(String url, Object paramsObj, TypeReference<T> responseType) throws IOException {
        return doPost(url, paramsObj, responseType, -1);
    }

    public static String doPost(String url, Object paramsObj) throws IOException {
        return doPost(url, paramsObj, -1);
    }

    /**
     * post Request content type = Application / JSON; charset=UTF-8
     *
     * @param url           url address
     * @param paramsObj     Request parameter field
     * @param responseType  Response object type
     * @param socketTimeout Timeout
     * @param <T>
     * @return Content corresponding to response entity
     * @throws IOException
     */
    public static <T> T doPost(String url, Object paramsObj, TypeReference<T> responseType, int socketTimeout) throws IOException {
        String responseContent = doPost(url, paramsObj, socketTimeout);
        if (StringUtils.isBlank(responseContent)) {
            return null;
        }

        T response = JSONObject.parseObject(responseContent, responseType);

        return response;
    }


    /**
     * @param url
     * @param paramsObj
     * @param socketTimeout
     * @return
     * @throws IOException
     */
    public static String doPost(String url, Object paramsObj, int socketTimeout) throws IOException {
        HttpPost post = new HttpPost(url);
        //If a String type object is submitted, no String type conversion is required
        String paramsStr = paramsObj instanceof String ? (String) paramsObj : JSONObject.toJSONString(paramsObj);
        StringEntity entity = new StringEntity(paramsStr, "UTF-8");
        post.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        post.setEntity(entity);
        return doHttp(post, socketTimeout);
    }

    /**
     * get Request content type = = text / HTML; charset=UTF-8
     *
     * @param url    url address
     * @param params params Map object composed of parameters
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    public static String doGet(String url, Map<String, String> params) throws IOException, URISyntaxException {
        return doGet(url, params, -1);
    }


    public static String doGet(String url, Map<String, String> params, int socketTimeout) throws IOException, URISyntaxException {
        URIBuilder uriBuilder = new URIBuilder(url);
        uriBuilder.setCharset(Consts.UTF_8).build();
        if (params != null) {
//            Set<String> keys = params.keySet();
//            for (String key : keys) {
//                uriBuilder.addParameter(key, params.get(key));
//            }
            params.forEach(uriBuilder::addParameter);
        }
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        //Set request header
        httpGet.addHeader(HttpHeaders.CONTENT_TYPE, "text/html;charset=UTF-8");

        return doHttp(httpGet, socketTimeout);
    }


    /**
     * Actually call remote methods
     *
     * @param request       httpGet/httpPost Common parent of
     * @param socketTimeout Timeout
     * @return
     * @throws IOException
     */
    private static String doHttp(HttpRequestBase request, int socketTimeout) throws IOException {
        //Set timeout
        if (socketTimeout > 0) {
            //Get the original configuration
            //Actual injection type org apache. http. impl. client. InternalHttpClient
            Configurable configClient = (Configurable) httpClient;
            RequestConfig.Builder custom = RequestConfig.copy(configClient.getConfig());
            //Set personalized configuration
            RequestConfig config = custom.setSocketTimeout(socketTimeout).build();
            request.setConfig(config);
        }
        ResponseHandler<String> handler = new BasicResponseHandler();
        long startPoint = System.currentTimeMillis();
        String response = httpClient.execute(request, handler);
        log.info("Request time[{}], Interface return information[{}]", System.currentTimeMillis() - startPoint, response);
        return response;
    }
}

Article reference

1. Official documents

Class PoolingHttpClientConnectionManager official website API document

Class RequestConfig official website API document

Class HttpClientBuilder official API document

Official API document of apache connection pool

PoolingHttpClientConnectionManager for httpclient source code analysis

2. Related blogs

Using PoolingHttpClientConnectionManager to solve friend

http and https examples of post requests in HttpClient

Http request connection pool - HttpClient connection pool

Detailed explanation of three timeouts in HttpClient

HttpClient.DefaultRequestHeaders.ExpectContinue. What is the purpose of expectcontinue and under what conditions is it set to true or false.

Understand expect: 100 continue in HTTP protocol

java. Lang. IllegalStateException: solution to connection pool shutdown

httpclient parameter configuration

Optimized use of httpClient in high concurrency scenarios

Analysis of retry mechanism



Author: xiaopang learns programming
Link: https://www.jianshu.com/p/e77e9e126f89
Source: Jianshu
The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source.

Topics: Spring Boot