Apache HttpClient — keepalive игнорируется при использовании SSL или TLSv1.2

#java #web-services #apache-httpclient-4.x #tls1.2 #tcp-keepalive

#java #веб-сервисы #apache-httpclient-4.x #tls1.2 #tcp-keepalive

Вопрос:

Я использую Apache HttpClient 4.5 для своего веб-сервиса soap.

В настоящее время я столкнулся с проблемой, из-за которой функция keep alive в httpclient игнорируется при использовании TLSv1.2. Однако функция поддержания работоспособности работает при использовании HTTP.

У вас, ребята, есть какая-то идея по этому поводу?

Мой код показан как показано ниже:

Основной класс: 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;
    }

}
  

Класс триггера: 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();
    }
}
  

Комментарии:

1. Использует ли ваше приложение аутентификацию клиента на основе сертификата?

2. Привет, ok2c, я думаю, да. Я сделаю следующее: 1. поместите хранилище ключей и truststore в KeyManager и TrustManager 2. поместите KeyManager и TrustManager в SSLContext 3. создайте org.apache.http.conn.ssl.SSLConnectionSocketFactory, указав SSLContext, а протокол — TLSv1.2 4. создайте PoolingHttpClientConnectionManager, указав SSLConnectionSocketFactory

3. Это не решит вашу непосредственную проблему, но, по крайней мере, объясняет, почему HttpClient не использует повторно постоянные соединения, которые не используют один и тот же контекст сеанса / выполнения

4. Привет, ok2c, спасибо за отзыв. Я не могу использовать httpclient в том же контексте выполнения, потому что он вызывается из другого класса. Класс реализации A -> webServiceClient -> HttpClientPool. Или у вас есть образец HttpClientPool для использования в том же контексте выполнения?

Ответ №1:

Здесь у вас есть два варианта:

  1. Передайте токен пользователя (который в вашем случае должен быть CN сертификата пользователя) в #sendSOAPMessage в качестве параметра.

     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. Если вы уверены, что ваше приложение не должно поддерживать несколько удостоверений пользователя, отключите отслеживание состояния соединения.

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

Комментарии:

1. Привет, ok2c, я попробовал оба метода и хорошо работаю. Я буду использовать 1-й вариант, поскольку он может обрабатывать несколько удостоверений пользователя. Я действительно ценю ваш отзыв. Это действительно мне очень помогает.