From aac274bbb3d458798303380ea5d9767f6a169afa Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 10 Feb 2016 18:07:11 +0100 Subject: [PATCH] KEYCLOAK-2463 Fix performance issue with just 2 default connections per route in Apache HTTP client --- .../en/en-US/modules/server-installation.xml | 20 +- .../httpclient/DefaultHttpClientFactory.java | 6 +- .../httpclient/HttpClientBuilder.java | 21 +- .../services/util/HttpClientBuilder.java | 331 ------------------ 4 files changed, 41 insertions(+), 337 deletions(-) delete mode 100755 services/src/main/java/org/keycloak/services/util/HttpClientBuilder.java diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml index edf3f36cdb..10b0ec4840 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml @@ -396,7 +396,7 @@ bin/add-user.[sh|bat] -r master -u -p connection-pool-size - How many connections can be in the pool. + How many connections can be in the pool (200 by default). @@ -404,7 +404,23 @@ bin/add-user.[sh|bat] -r master -u -p max-pooled-per-route - How many connections can be pooled per host. + How many connections can be pooled per host (100 by default). + + + + + connection-ttl-millis + + + Maximum connection time to live in milliseconds + + + + + max-connection-idle-time-millis + + + Maximum time the connection might stay idle in the connection pool. Will start background cleaner thread if set (by default it's not set) diff --git a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java index 471b32b6ef..430da72397 100755 --- a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java +++ b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java @@ -119,8 +119,10 @@ public class DefaultHttpClientFactory implements HttpClientFactory { if (httpClient == null) { long socketTimeout = config.getLong("socket-timeout-millis", -1L); long establishConnectionTimeout = config.getLong("establish-connection-timeout-millis", -1L); - int maxPooledPerRoute = config.getInt("max-pooled-per-route", 0); + int maxPooledPerRoute = config.getInt("max-pooled-per-route", 100); int connectionPoolSize = config.getInt("connection-pool-size", 200); + long connectionTTL = config.getLong("connection-ttl-millis", -1L); + long maxConnectionIdleTime = config.getLong("max-connection-idle-time-millis", -1L); boolean disableCookies = config.getBoolean("disable-cookies", true); String clientKeystore = config.get("client-keystore"); String clientKeystorePassword = config.get("client-keystore-password"); @@ -139,6 +141,8 @@ public class DefaultHttpClientFactory implements HttpClientFactory { .establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS) .maxPooledPerRoute(maxPooledPerRoute) .connectionPoolSize(connectionPoolSize) + .connectionTTL(connectionTTL, TimeUnit.MILLISECONDS) + .maxConnectionIdleTime(maxConnectionIdleTime, TimeUnit.MILLISECONDS) .disableCookies(disableCookies); if (disableTrustManager) { diff --git a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java index 8c5dd4bcf2..ba727bd2d8 100755 --- a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java +++ b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java @@ -92,10 +92,12 @@ public class HttpClientBuilder { protected boolean disableTrustManager; protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD; protected SSLContext sslContext; - protected int connectionPoolSize = 100; - protected int maxPooledPerRoute = 0; + protected int connectionPoolSize = 200; + protected int maxPooledPerRoute = 100; protected long connectionTTL = -1; protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS; + protected long maxConnectionIdleTime = -1; + protected TimeUnit maxConnectionIdleTimeUnit = TimeUnit.MILLISECONDS; protected HostnameVerifier verifier = null; protected long socketTimeout = -1; protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS; @@ -138,6 +140,12 @@ public class HttpClientBuilder { return this; } + public HttpClientBuilder maxConnectionIdleTime(long maxConnectionIdleTime, TimeUnit unit) { + this.maxConnectionIdleTime = maxConnectionIdleTime; + this.maxConnectionIdleTimeUnit = unit; + return this; + } + public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) { this.maxPooledPerRoute = maxPooledPerRoute; return this; @@ -272,7 +280,14 @@ public class HttpClientBuilder { .setDefaultRequestConfig(requestConfig) .setSSLSocketFactory(sslsf) .setMaxConnTotal(connectionPoolSize) - .setMaxConnPerRoute(maxPooledPerRoute); + .setMaxConnPerRoute(maxPooledPerRoute) + .setConnectionTimeToLive(connectionTTL, connectionTTLUnit); + + if (maxConnectionIdleTime > 0) { + // Will start background cleaner thread + builder.evictIdleConnections(maxConnectionIdleTime, maxConnectionIdleTimeUnit); + } + if (disableCookies) builder.disableCookieManagement(); return builder.build(); } catch (Exception e) { diff --git a/services/src/main/java/org/keycloak/services/util/HttpClientBuilder.java b/services/src/main/java/org/keycloak/services/util/HttpClientBuilder.java deleted file mode 100755 index 83aba480ff..0000000000 --- a/services/src/main/java/org/keycloak/services/util/HttpClientBuilder.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.keycloak.services.util; - -import org.apache.http.client.HttpClient; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.AllowAllHostnameVerifier; -import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.conn.ssl.StrictHostnameVerifier; -import org.apache.http.conn.ssl.X509HostnameVerifier; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.SingleClientConnManager; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.keycloak.representations.adapters.config.AdapterConfig; -import org.keycloak.common.util.EnvUtil; -import org.keycloak.common.util.KeystoreUtil; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import java.io.IOException; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.concurrent.TimeUnit; - -/** - * Abstraction for creating HttpClients. Allows SSL configuration. - * - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class HttpClientBuilder { - public static enum HostnameVerificationPolicy { - /** - * Hostname verification is not done on the server's certificate - */ - ANY, - /** - * Allows wildcards in subdomain names i.e. *.foo.com - */ - WILDCARD, - /** - * CN must match hostname connecting to - */ - STRICT - } - - - /** - * @author Bill Burke - * @version $Revision: 1 $ - */ - private static class PassthroughTrustManager implements X509TrustManager { - public void checkClientTrusted(X509Certificate[] chain, - String authType) throws CertificateException { - } - - public void checkServerTrusted(X509Certificate[] chain, - String authType) throws CertificateException { - } - - public X509Certificate[] getAcceptedIssuers() { - return null; - } - } - - protected KeyStore truststore; - protected KeyStore clientKeyStore; - protected String clientPrivateKeyPassword; - protected boolean disableTrustManager; - protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD; - protected SSLContext sslContext; - protected int connectionPoolSize = 100; - protected int maxPooledPerRoute = 0; - protected long connectionTTL = -1; - protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS; - protected HostnameVerifier verifier = null; - protected long socketTimeout = -1; - protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS; - protected long establishConnectionTimeout = -1; - protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS; - - - /** - * Socket inactivity timeout - * - * @param timeout - * @param unit - * @return - */ - public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) - { - this.socketTimeout = timeout; - this.socketTimeoutUnits = unit; - return this; - } - - /** - * When trying to make an initial socket connection, what is the timeout? - * - * @param timeout - * @param unit - * @return - */ - public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) - { - this.establishConnectionTimeout = timeout; - this.establishConnectionTimeoutUnits = unit; - return this; - } - - public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) { - this.connectionTTL = ttl; - this.connectionTTLUnit = unit; - return this; - } - - public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) { - this.maxPooledPerRoute = maxPooledPerRoute; - return this; - } - - public HttpClientBuilder connectionPoolSize(int connectionPoolSize) { - this.connectionPoolSize = connectionPoolSize; - return this; - } - - /** - * Disable trust management and hostname verification. NOTE this is a security - * hole, so only set this option if you cannot or do not want to verify the identity of the - * host you are communicating with. - */ - public HttpClientBuilder disableTrustManager() { - this.disableTrustManager = true; - return this; - } - - /** - * SSL policy used to verify hostnames - * - * @param policy - * @return - */ - public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) { - this.policy = policy; - return this; - } - - - public HttpClientBuilder sslContext(SSLContext sslContext) { - this.sslContext = sslContext; - return this; - } - - public HttpClientBuilder trustStore(KeyStore truststore) { - this.truststore = truststore; - return this; - } - - public HttpClientBuilder keyStore(KeyStore keyStore, String password) { - this.clientKeyStore = keyStore; - this.clientPrivateKeyPassword = password; - return this; - } - - public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) { - this.clientKeyStore = keyStore; - this.clientPrivateKeyPassword = new String(password); - return this; - } - - - static class VerifierWrapper implements X509HostnameVerifier { - protected HostnameVerifier verifier; - - VerifierWrapper(HostnameVerifier verifier) { - this.verifier = verifier; - } - - @Override - public void verify(String host, SSLSocket ssl) throws IOException { - if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure"); - } - - @Override - public void verify(String host, X509Certificate cert) throws SSLException { - throw new SSLException("This verification path not implemented"); - } - - @Override - public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { - throw new SSLException("This verification path not implemented"); - } - - @Override - public boolean verify(String s, SSLSession sslSession) { - return verifier.verify(s, sslSession); - } - } - - public HttpClient build() { - X509HostnameVerifier verifier = null; - if (this.verifier != null) verifier = new VerifierWrapper(this.verifier); - else { - switch (policy) { - case ANY: - verifier = new AllowAllHostnameVerifier(); - break; - case WILDCARD: - verifier = new BrowserCompatHostnameVerifier(); - break; - case STRICT: - verifier = new StrictHostnameVerifier(); - break; - } - } - try { - SSLSocketFactory sslsf = null; - SSLContext theContext = sslContext; - if (disableTrustManager) { - theContext = SSLContext.getInstance("SSL"); - theContext.init(null, new TrustManager[]{new PassthroughTrustManager()}, - new SecureRandom()); - verifier = new AllowAllHostnameVerifier(); - sslsf = new SSLSocketFactory(theContext, verifier); - } else if (theContext != null) { - sslsf = new SSLSocketFactory(theContext, verifier); - } else if (clientKeyStore != null || truststore != null) { - sslsf = new SSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier); - } else { - final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS); - tlsContext.init(null, null, null); - sslsf = new SSLSocketFactory(tlsContext, verifier); - } - SchemeRegistry registry = new SchemeRegistry(); - registry.register( - new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); - Scheme httpsScheme = new Scheme("https", 443, sslsf); - registry.register(httpsScheme); - ClientConnectionManager cm = null; - if (connectionPoolSize > 0) { - ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit); - tcm.setMaxTotal(connectionPoolSize); - if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize; - tcm.setDefaultMaxPerRoute(maxPooledPerRoute); - cm = tcm; - - } else { - cm = new SingleClientConnManager(registry); - } - BasicHttpParams params = new BasicHttpParams(); - if (socketTimeout > -1) - { - HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout)); - - } - if (establishConnectionTimeout > -1) - { - HttpConnectionParams.setConnectionTimeout(params, (int)establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout)); - } - return new DefaultHttpClient(cm, params); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public HttpClient build(AdapterConfig adapterConfig) { - String truststorePath = adapterConfig.getTruststore(); - if (truststorePath != null) { - truststorePath = EnvUtil.replace(truststorePath); - String truststorePassword = adapterConfig.getTruststorePassword(); - try { - this.truststore = KeystoreUtil.loadKeyStore(truststorePath, truststorePassword); - } catch (Exception e) { - throw new RuntimeException("Failed to load truststore", e); - } - } - String clientKeystore = adapterConfig.getClientKeystore(); - if (clientKeystore != null) { - clientKeystore = EnvUtil.replace(clientKeystore); - String clientKeystorePassword = adapterConfig.getClientKeystorePassword(); - try { - KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword); - keyStore(clientCertKeystore, clientKeystorePassword); - } catch (Exception e) { - throw new RuntimeException("Failed to load keystore", e); - } - } - int size = 10; - if (adapterConfig.getConnectionPoolSize() > 0) - size = adapterConfig.getConnectionPoolSize(); - HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD; - if (adapterConfig.isAllowAnyHostname()) - policy = HttpClientBuilder.HostnameVerificationPolicy.ANY; - connectionPoolSize(size); - hostnameVerification(policy); - if (adapterConfig.isDisableTrustManager()) { - disableTrustManager(); - } else { - trustStore(truststore); - } - return build(); - } -} \ No newline at end of file