1

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();
    }
}
obl0702
  • 123
  • 11
  • Does your application use certificate based client authentication? – ok2c Apr 22 '19 at 10:15
  • Hi ok2c, I think ya. I will do the following: 1. put keystore and truststore into KeyManager and TrustManager 2. put KeyManager and TrustManager into SSLContext 3. create org.apache.http.conn.ssl.SSLConnectionSocketFactory by putting SSLContext and protocol is TLSv1.2 4. create PoolingHttpClientConnectionManager by putting the SSLConnectionSocketFactory – obl0702 Apr 22 '19 at 14:30
  • That will not solve your immediate problem but at least explains why HttpClient does not re-use persistent connections that do not share the same session / execution context – ok2c Apr 22 '19 at 15:31
  • Hi ok2c, thanks for the feedback. I unable to use the httpclient under the same execution context because it is called from another class. Implementation Class A -> webServiceClient -> HttpClientPool. Or do you have a sample of HttpClientPool to use under same execution context? – obl0702 Apr 23 '19 at 02:01

1 Answers1

2

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();    
    
ok2c
  • 26,450
  • 5
  • 63
  • 71
  • Hi ok2c, I have tried both method and work well. I will use the 1st option as it can handle multiple user identities. I am really appreciate your feedback. It really help me alot. – obl0702 Apr 23 '19 at 09:56