Search code examples
javaweb-servicesapache-httpclient-4.xtls1.2tcp-keepalive

Apache HttpClient - keepalive is ignored when using SSL or TLSv1.2


I am using Apache HttpClient 4.5 for my soap webservice.

Currently, I have encountered an issue where the keep alive in httpclient is ignored when TLSv1.2 is used. However, the keep alive is working if using HTTP.

Do you guys have some idea on it?

My code is shown as below:

Main Class: HttpClientPool.java

public class HttpClientPool {

    private static PoolingHttpClientConnectionManager manager = null;
    private static CloseableHttpClient httpClient = null;
    private static final Logger logger = Logger.getLogger(HttpClientPool.class);

    public static synchronized CloseableHttpClient getHttpClient(){

        if(httpClient==null){

            //Some function to get SSLConnectionSocketFactory in Singleton
            SSLConnectionSocketFactory sslConnSocFac = getSSLConnectionSocketFactory();

            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("https", sslConnSocFac)
                    .build();

            HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connectionFactory = new ManagedHttpClientConnectionFactory(
                    DefaultHttpRequestWriterFactory.INSTANCE, DefaultHttpResponseParserFactory.INSTANCE);

            DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;

            manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, connectionFactory, dnsResolver);

            SocketConfig deaultSocketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
            manager.setDefaultSocketConfig(deaultSocketConfig);
            manager.setMaxTotal(300);
            manager.setDefaultMaxPerRoute(200);
            manager.setValidateAfterInactivity(50*1000);

            RequestConfig defaultRequestConfig = RequestConfig.custom()
                    .setConnectTimeout(20*1000)
                    .setSocketTimeout(50*1000)
                    .setConnectionRequestTimeout(20000)
                    .build();

            ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
                public long getKeepAliveDuration(HttpResponse httpResponse, org.apache.http.protocol.HttpContext context) {
                    return 1000 * 1000;
                }
            };

            httpClient = HttpClients.custom()
                    .setConnectionManager(manager)
                    .setConnectionManagerShared(false)
                    .evictIdleConnections(60l, TimeUnit.SECONDS)
                    .evictExpiredConnections()
                    .setConnectionTimeToLive(60, TimeUnit.SECONDS)
                    .setDefaultRequestConfig(defaultRequestConfig)
                    .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)
                    .setKeepAliveStrategy(myStrategy)
                    .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false))
                    .build();

            Runtime.getRuntime().addShutdownHook(new Thread(){
                @Override
                public void run(){
                    try {
                        httpClient1.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        return httpClient;
    }

    private static SSLConnectionSocketFactory getSSLConnectionSocketFactory() {
        //some working
        return sslConnectionSocketFactory;
    }

}

Trigger Class: webServiceClient.java

public class webServiceClient{

    HttpClientPool httpClientPool;

    private static final Logger logger = Logger.getLogger(HttpClientPool.class);

    public sendSOAPMessage(String url, String soapAction){
        HttpPost post = new HttpPost(url);
        HttpEntity entity = new ByteArrayEntity(xml.getBytes("UTF-8"));

        post.setEntity(entity);
        post.setHeader("Content-type", "application/soap+xml; charset=UTF-8");
        post.setHeader("SOAPAction", soapAction);
        post.setHeader("Connection", "Keep-Alive");
        post.setHeader("Keep-Alive", "header");

        CloseableHttpResponse response = httpClientPool.getHttpClient().execute(post);
        String result = EntityUtils.toString(response.getEntity());
        logger.info("Response: " + result);

        EntityUtils.consume(response.getEntity());
        response.close();
    }
}

Solution

  • You have two options here:

    1. Pass user token (which in your case should be the CN of the user certificate) to the #sendSOAPMessage as a parameter.

      public class webServiceClient{
      
          HttpClientPool httpClientPool;
      
          private static final Logger logger = Logger.getLogger(HttpClientPool.class);
      
          public sendSOAPMessage(String url, String soapAction, String userToken){
              HttpPost post = new HttpPost(url);
              HttpEntity entity = new ByteArrayEntity(xml.getBytes("UTF-8"));
      
              post.setEntity(entity);
              post.setHeader("Content-type", "application/soap+xml; charset=UTF-8");
              post.setHeader("SOAPAction", soapAction);
              post.setHeader("Connection", "Keep-Alive");
              post.setHeader("Keep-Alive", "header");
      
              HttpClientContext clientContext = HttpClientContext.create();
              clientContext.setUserToken(userToken);
              try (CloseableHttpResponse response = httpClientPool.getHttpClient().execute(post, clientContext)) {
                  String result = EntityUtils.toString(response.getEntity());
                  logger.info("Response: " + result);
      
                  EntityUtils.consume(response.getEntity());
              }
          }
      }
      
    2. If you are certain your application does not have to support multiple user identities disable connection state tracking.

      httpClient = HttpClients.custom()
          .disableConnectionState()
          .build();