package com.kendelong.util.http; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLContext; import org.apache.http.client.config.RequestConfig; import org.apache.http.config.ConnectionConfig; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.config.SocketConfig; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.pool.PoolStats; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.scheduling.annotation.Scheduled; /** * This Strategy allows for the configuration of pooled http connections. All the properties * in this class will be configurable from a jmx interface * */ public class PooledHttpClientStrategy implements IHttpClientStrategy,InitializingBean, DisposableBean { private final AtomicReference<PoolingHttpClientConnectionManager> connectionManager = new AtomicReference<PoolingHttpClientConnectionManager>(); private final AtomicReference<CloseableHttpClient> httpClient = new AtomicReference<CloseableHttpClient>(); private volatile int maxConnectionsPerHost = 6; private volatile int maxTotalConnections = 30; // Connection timeout is how long the client will wait for a connection to be created private volatile int connectionTimeoutInMs = 5000; // Socket timeout is how long the socket can be idle before the client aborts private volatile int socketTimeoutInMs = 5000; private volatile boolean ignoreCookies = false; // retrieveConnectionTimeout is how long in ms the client will wait for the connection manager to deliver a connection private volatile int retrieveConnectionTimeoutInMs = 500; private volatile boolean staleConnectionCheck = true; // enables a scheduled cleaning sweep of our connections private volatile boolean connectionCleaningEnabled = true; // timeout value used for scheduled cleansing of unused connections (measured in seconds) private volatile int idleConnectionTimeoutInSeconds = 30; private final Logger logger = LoggerFactory.getLogger(this.getClass()); private boolean allowAllSsl = false; @Override public CloseableHttpClient getHttpClient() { return httpClient.get(); } public void reset() { // ************* Create connection manager ********************** PoolingHttpClientConnectionManager cm; if(allowAllSsl) { Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create() .register("https", getLenientSslSocketFactory()) .build(); cm = new PoolingHttpClientConnectionManager(r); } else { cm = new PoolingHttpClientConnectionManager(); } cm.setMaxTotal(maxTotalConnections); cm.setDefaultMaxPerRoute(maxConnectionsPerHost); ConnectionConfig connectionConfig = ConnectionConfig.custom() .setCharset(Charset.forName("UTF-8")) .build(); cm.setDefaultConnectionConfig(connectionConfig); SocketConfig socketConfig = SocketConfig.custom() .setTcpNoDelay(true) .setSoTimeout(socketTimeoutInMs) .setSoLinger(socketTimeoutInMs + 500) .build(); cm.setDefaultSocketConfig(socketConfig); setConnectionManager(cm); // **************** Create Client ***************** // these are reusable: https://hc.apache.org/httpcomponents-client-4.3.x/tutorial/html/connmgmt.html#d5e380 Section 2.4 // this ref is better than the official docs // http://www.baeldung.com/httpclient-timeout RequestConfig config = RequestConfig.custom() .setConnectTimeout(connectionTimeoutInMs) .setConnectionRequestTimeout(retrieveConnectionTimeoutInMs) .setSocketTimeout(socketTimeoutInMs) .setStaleConnectionCheckEnabled(staleConnectionCheck) .build(); CloseableHttpClient client = HttpClientBuilder.create() .setDefaultRequestConfig(config) .setConnectionManager(getConnectionManager()) .build(); httpClient.set(client); // HttpProtocolParams.setUserAgent(params, "Apache httpclient-4.1.3 ops@me.com"); // ignore cookies // gzip } /** * This method will be invoked after spring instantiates the object. * If the properties are configured in spring xml this method will enable honoring those properties * */ @Override public void afterPropertiesSet() throws Exception { reset(); } /** * Clean the connections. For this method to be invoked, the Spring context needs a couple of beans: * * <pre> {@code <task:scheduler id="threadPoolTaskScheduler" pool-size="1" /> <task:annotation-driven scheduler="threadPoolTaskScheduler" /> } </pre> Or just put @EnableScheduling on one of the @Config classes * * This will activate the timer that actually calls this method. * * See http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html for an explanation (section 2.10) */ @Scheduled(fixedDelay=30000) public void cleanConnections() { if (isConnectionCleaningEnabled()) { logger.trace("cleaning http connections"); this.connectionManager.get().closeExpiredConnections(); this.connectionManager.get().closeIdleConnections(getIdleConnectionTimeoutInSeconds(), TimeUnit.SECONDS); } } private SSLConnectionSocketFactory getLenientSslSocketFactory() { SSLContext sslContext = SSLContexts.createSystemDefault(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); return sslsf; } private PoolingHttpClientConnectionManager getConnectionManager() { return connectionManager.get(); } private void setConnectionManager(PoolingHttpClientConnectionManager newConMgr) { PoolingHttpClientConnectionManager oldConMgr = connectionManager.getAndSet(newConMgr); if(oldConMgr != null) { oldConMgr.shutdown(); } } public int getMaxConnectionsPerHost() { return maxConnectionsPerHost; } public void setMaxConnectionsPerHost(int defaultMaxConnectionsPerHost) { this.maxConnectionsPerHost = defaultMaxConnectionsPerHost; } public int getMaxTotalConnections() { return maxTotalConnections; } public void setMaxTotalConnections(int maxTotalConnections) { this.maxTotalConnections = maxTotalConnections; } public int getConnectionTimeoutInMs() { return connectionTimeoutInMs; } public void setConnectionTimeoutInMs(int connectionTimeout) { this.connectionTimeoutInMs = connectionTimeout; } public int getSocketTimeoutInMs() { return socketTimeoutInMs; } public void setSocketTimeoutInMs(int socketTimeout) { this.socketTimeoutInMs = socketTimeout; } public boolean isIgnoreCookies() { return ignoreCookies; } public void setIgnoreCookies(boolean ignoreCookies) { this.ignoreCookies = ignoreCookies; } public int getRetrieveConnectionTimeoutInMs() { return retrieveConnectionTimeoutInMs; } public void setRetrieveConnectionTimeoutInMs(int retrieveConnectionTimeout) { this.retrieveConnectionTimeoutInMs = retrieveConnectionTimeout; } public boolean isStaleConnectionCheck() { return staleConnectionCheck; } public void setStaleConnectionCheck(boolean staleConnectionCheck) { this.staleConnectionCheck = staleConnectionCheck; } public boolean isConnectionCleaningEnabled() { return connectionCleaningEnabled; } public void setConnectionCleaningEnabled(boolean connectionCleaningEnabled) { this.connectionCleaningEnabled = connectionCleaningEnabled; } public int getIdleConnectionTimeoutInSeconds() { return idleConnectionTimeoutInSeconds; } public void setIdleConnectionTimeoutInSeconds(int idleConnectionTimeout) { this.idleConnectionTimeoutInSeconds = idleConnectionTimeout; } public boolean isAllowAllSsl() { return allowAllSsl; } public void setAllowAllSsl(boolean allowAllSsl) { this.allowAllSsl = allowAllSsl; } public PoolStats getPoolStats() { return getConnectionManager().getTotalStats(); } @Override public void destroy() { try { getHttpClient().close(); } catch(IOException e) { // seriously? getConnectionManager().close(); } } }