【问题标题】:Apache HttpClient - keepalive is ignored when using SSL or TLSv1.2Apache HttpClient - 使用 SSL 或 TLSv1.2 时忽略 keepalive
【发布时间】:2019-04-22 02:31:10
【问题描述】:

我将 Apache HttpClient 4.5 用于我的肥皂网络服务。

目前,我遇到了一个问题,当使用 TLSv1.2 时,httpclient 中的保持活动被忽略。但是,如果使用 HTTP,keep alive 是有效的。

你们有什么想法吗?

我的代码如下所示:

主类: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();
    }
}

【问题讨论】:

  • 您的应用程序是否使用基于证书的客户端身份验证?
  • 嗨 ok2c,我想是的。我将执行以下操作: 1. 将 keystore 和 truststore 放入 KeyManager 和 TrustManager 2. 将 KeyManager 和 TrustManager 放入 SSLContext 3. 通过放入 SSLContext 和协议为 TLSv1.2 创建 org.apache.http.conn.ssl.SSLConnectionSocketFactory 4. 创建PoolingHttpClientConnectionManager 通过放置 SSLConnectionSocketFactory
  • 这不会解决你眼前的问题,但至少解释了为什么 HttpClient 不重用不共享相同会话/执行上下文的持久连接
  • 您好 ok2c,感谢您的反馈。我无法在相同的执行上下文下使用 httpclient,因为它是从另一个类调用的。实现类 A -> webServiceClient -> HttpClientPool。或者你有一个 HttpClientPool 的样本可以在相同的执行环境下使用?

标签: java web-services apache-httpclient-4.x tls1.2 tcp-keepalive


【解决方案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();    
    

【讨论】:

  • 嗨 ok2c,我已经尝试了这两种方法并且效果很好。我将使用第一个选项,因为它可以处理多个用户身份。我非常感谢您的反馈。这对我帮助很大。
猜你喜欢
  • 1970-01-01
  • 2013-10-31
  • 2016-05-03
  • 2019-03-31
  • 2017-12-17
  • 2011-02-11
  • 1970-01-01
  • 2021-11-21
相关资源
最近更新 更多