From 83e19359c762bd5652dfa8e2a66d7e5a0c3f2184 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Thu, 5 Nov 2020 11:39:02 +0100 Subject: add scheduled eviction policy to clean-up expired or old http connections from pool --- .../eaaf/core/impl/http/HttpClientFactory.java | 168 +++++++++++++-------- 1 file changed, 104 insertions(+), 64 deletions(-) (limited to 'eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java') diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java index 647c0636..07522b56 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java @@ -4,6 +4,8 @@ import java.security.KeyStore; import java.security.Provider; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; @@ -23,6 +25,7 @@ import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.config.SocketConfig; +import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.LayeredConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; @@ -33,10 +36,12 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.BasicHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContexts; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; @@ -65,10 +70,10 @@ public class HttpClientFactory implements IHttpClientFactory { public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION = "client.http.connection.timeout.connection"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = - "client.http.connection.timeout.request"; - public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT = + "client.http.connection.timeout.request"; + public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT = "client.http.connection.retry.count"; - public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST = + public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST = "client.http.connection.retry.post"; public static final String PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL = "client.http.ssl.hostnameverifier.trustall"; @@ -97,9 +102,14 @@ public class HttpClientFactory implements IHttpClientFactory { public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE = "100"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT = "3"; public static final String DEFAUTL_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST = String.valueOf(false); + + public static final int DEFAULT_CLEANUP_RUNNER_TIME = 30000; + public static final int DEFAULT_CLEANUP_IDLE_TIME = 60; + private String defaultConfigurationId = null; - private final Map availableBuilders = new HashMap<>(); + private final Map> + availableBuilders = new HashMap<>(); /* * (non-Javadoc) @@ -114,7 +124,7 @@ public class HttpClientFactory implements IHttpClientFactory { @Override public CloseableHttpClient getHttpClient(final boolean followRedirects) { - return availableBuilders.get(defaultConfigurationId).setRedirectStrategy( + return availableBuilders.get(defaultConfigurationId).getFirst().setRedirectStrategy( buildRedirectStrategy(followRedirects)).build(); } @@ -124,30 +134,31 @@ public class HttpClientFactory implements IHttpClientFactory { log.trace("Build http client for: {}", config.getFriendlyName()); HttpClientBuilder builder = null; if (availableBuilders.containsKey(config.getUuid())) { - builder = availableBuilders.get(config.getUuid()); + builder = availableBuilders.get(config.getUuid()).getFirst(); } else { log.debug("Initialize new http-client builder for: {}", config.getFriendlyName()); - //validate configuration object + // validate configuration object config.validate(); builder = HttpClients.custom(); - - //inject request configuration + + // inject request configuration builder.setDefaultRequestConfig(buildDefaultRequestConfig()); injectInternalRetryHandler(builder, config); - - //inject basic authentication infos + + // inject basic authentication infos injectBasicAuthenticationIfRequired(builder, config); - //inject authentication if required + // inject authentication if required final LayeredConnectionSocketFactory sslConnectionFactory = getSslContext(config); // set pool connection if required - injectDefaultConnectionPoolIfRequired(builder, sslConnectionFactory); + HttpClientConnectionManager connectionManager + = injectConnectionManager(builder, sslConnectionFactory); - availableBuilders.put(config.getUuid(), builder); + availableBuilders.put(config.getUuid(), Pair.newInstance(builder, connectionManager)); } @@ -156,27 +167,45 @@ public class HttpClientFactory implements IHttpClientFactory { } - private void injectInternalRetryHandler(HttpClientBuilder builder, HttpClientConfiguration config) { + /** + * Worker that closes expired connections or connections that in idle + * for more than DEFAULT_CLEANUP_IDLE_TIME seconds. + * + */ + @Scheduled(fixedDelay = DEFAULT_CLEANUP_RUNNER_TIME) + private void httpConnectionPoolCleaner() { + log.trace("Starting http connection-pool eviction policy ... "); + for (final Entry> el + : availableBuilders.entrySet()) { + log.trace("Checking connections of http-client: {}", el.getKey()); + el.getValue().getSecond().closeExpiredConnections(); + el.getValue().getSecond().closeIdleConnections(DEFAULT_CLEANUP_IDLE_TIME, TimeUnit.SECONDS); + + } + + } + + private void injectInternalRetryHandler(HttpClientBuilder builder, HttpClientConfiguration config) { if (config.getHttpErrorRetryCount() > 0) { - log.info("Set HTTP error-retry to {} for http-client: {}", + log.info("Set HTTP error-retry to {} for http-client: {}", config.getHttpErrorRetryCount(), config.getFriendlyName()); builder.setRetryHandler(new EaafHttpRequestRetryHandler( - config.getHttpErrorRetryCount(), - config.isHttpErrorRetryPost())); - + config.getHttpErrorRetryCount(), + config.isHttpErrorRetryPost())); + if (config.getServiceUnavailStrategy() != null) { log.debug("HttpClient configuration: {} set custom ServiceUnavailableRetryStrategy: {}", config.getFriendlyName(), config.getServiceUnavailStrategy().getClass().getName()); builder.setServiceUnavailableRetryStrategy(config.getServiceUnavailStrategy()); - + } - + } else { log.info("Disable HTTP error-retry for http-client: {}", config.getFriendlyName()); builder.disableAutomaticRetries(); - + } - + } @PostConstruct @@ -190,8 +219,8 @@ public class HttpClientFactory implements IHttpClientFactory { // set default request configuration defaultHttpClientBuilder.setDefaultRequestConfig(buildDefaultRequestConfig()); injectInternalRetryHandler(defaultHttpClientBuilder, defaultHttpClientConfig); - - //inject http basic authentication + + // inject http basic authentication injectBasicAuthenticationIfRequired(defaultHttpClientBuilder, defaultHttpClientConfig); // inject authentication if required @@ -199,11 +228,13 @@ public class HttpClientFactory implements IHttpClientFactory { getSslContext(defaultHttpClientConfig); // set pool connection if required - injectDefaultConnectionPoolIfRequired(defaultHttpClientBuilder, sslConnectionFactory); + HttpClientConnectionManager connectionManager + = injectConnectionManager(defaultHttpClientBuilder, sslConnectionFactory); - //set default http client builder + // set default http client builder defaultConfigurationId = defaultHttpClientConfig.getUuid(); - availableBuilders.put(defaultConfigurationId, defaultHttpClientBuilder); + availableBuilders.put(defaultConfigurationId, + Pair.newInstance(defaultHttpClientBuilder, connectionManager)); } @@ -239,13 +270,12 @@ public class HttpClientFactory implements IHttpClientFactory { PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)); config.setHttpErrorRetryCount(Integer.parseInt(basicConfig.getBasicConfiguration( - PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT, + PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT))); config.setHttpErrorRetryPost(Boolean.parseBoolean(basicConfig.getBasicConfiguration( - PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST, + PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST, DEFAUTL_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST))); - - + // validate configuration object config.validate(); @@ -280,7 +310,8 @@ public class HttpClientFactory implements IHttpClientFactory { SSLContext sslContext = null; if (httpClientConfig.getAuthMode().equals(HttpClientConfiguration.ClientAuthMode.SSL)) { log.debug("Open keyStore with type: {}", httpClientConfig.getKeyStoreConfig().getKeyStoreType()); - final Pair keyStore = keyStoreFactory.buildNewKeyStore(httpClientConfig.getKeyStoreConfig()); + final Pair keyStore = keyStoreFactory.buildNewKeyStore(httpClientConfig + .getKeyStoreConfig()); log.trace("Injecting SSL client-authentication into http client ... "); sslContext = HttpUtils.buildSslContextWithSslClientAuthentication(keyStore, @@ -290,7 +321,7 @@ public class HttpClientFactory implements IHttpClientFactory { } else { log.trace("Initializing default SSL Context ... "); sslContext = SSLContexts.createDefault(); - + } // set hostname verifier @@ -308,48 +339,37 @@ public class HttpClientFactory implements IHttpClientFactory { } - private void injectDefaultConnectionPoolIfRequired( + @Nonnull + private HttpClientConnectionManager injectConnectionManager( HttpClientBuilder builder, final LayeredConnectionSocketFactory sslConnectionFactory) { if (basicConfig.getBasicConfigurationBoolean(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE, true)) { - PoolingHttpClientConnectionManager pool; - - // set socketFactoryRegistry if SSLConnectionFactory is Set - if (sslConnectionFactory != null) { - final Registry socketFactoryRegistry = - RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslConnectionFactory).build(); - log.trace("Inject SSLSocketFactory into pooled connection"); - pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry); - - } else { - pool = new PoolingHttpClientConnectionManager(); - - } - - pool.setDefaultMaxPerRoute(Integer.parseInt( + PoolingHttpClientConnectionManager connectionPool + = new PoolingHttpClientConnectionManager(getDefaultRegistry(sslConnectionFactory)); + connectionPool.setDefaultMaxPerRoute(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE))); - pool.setMaxTotal(Integer.parseInt( + connectionPool.setMaxTotal(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL))); - - pool.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(Integer.parseInt( + connectionPool.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) * 1000).build()); + builder.setConnectionManager(connectionPool); + log.debug("Initalize http-client pool with, maxTotal: {} maxPerRoute: {}", + connectionPool.getMaxTotal(), connectionPool.getDefaultMaxPerRoute()); + return connectionPool; + + } else { + log.debug("Building http-client without Connection-Pool ... "); + final BasicHttpClientConnectionManager basicPool = new BasicHttpClientConnectionManager( + getDefaultRegistry(sslConnectionFactory)); + builder.setConnectionManager(basicPool); + return basicPool; - builder.setConnectionManager(pool); - log.debug("Initalize http-client pool with, maxTotal: {} maxPerRoute: {}", pool.getMaxTotal(), - pool.getDefaultMaxPerRoute()); - - } else if (sslConnectionFactory != null) { - log.trace("Inject SSLSocketFactory without connection pool"); - builder.setSSLSocketFactory(sslConnectionFactory); - } - + } private RequestConfig buildDefaultRequestConfig() { @@ -392,5 +412,25 @@ public class HttpClientFactory implements IHttpClientFactory { return redirectStrategy; } + + private static Registry getDefaultRegistry( + final LayeredConnectionSocketFactory sslConnectionFactory) { + final RegistryBuilder builder = + RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()); + + if (sslConnectionFactory != null) { + log.trace("Inject own SSLSocketFactory into pooled connection"); + builder.register("https", sslConnectionFactory); + + } else { + log.trace("Inject default SSLSocketFactory into pooled connection"); + builder.register("https", SSLConnectionSocketFactory.getSocketFactory()); + + } + + return builder.build(); + + } } -- cgit v1.2.3