package org.openqa.selenium.remote.internal; import java.lang.reflect.Field; import java.net.ProxySelector; import java.net.URL; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.conn.routing.HttpRoutePlanner; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.conn.DefaultSchemePortResolver; import org.apache.http.impl.conn.SystemDefaultRoutePlanner; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; import org.apache.http.nio.reactor.ConnectingIOReactor; import org.apache.http.nio.reactor.IOReactorException; import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.http.HttpClient.Factory; import static com.google.common.base.Preconditions.*; import static java.util.concurrent.TimeUnit.*; /** * Replacement of {@link org.openqa.selenium.remote.internal.ApacheHttpClient.Factory} to return {@link ApacheHttpAsyncClient} */ public class ApacheHttpAsyncClientFactory implements Factory { // {@link org.openqa.selenium.remote.internal.HttpClientFactory}'s defaults private final int TIMEOUT_THREE_HOURS = (int) SECONDS.toMillis(60 * 60 * 3); private final int TIMEOUT_TWO_MINUTES = (int) SECONDS.toMillis(60 * 2); /** * Replace default http client factory in HttpCommandExcecutor. * * @throws Exception throw if cannot access HttpCommandExecutor#defaultClientFactory field. */ public static void replaceDefaultClientFactory() throws Exception { Class<?> factoryClass = Class.forName("org.openqa.selenium.remote.HttpCommandExecutor"); Field defaultClientFactory = factoryClass.getDeclaredField("defaultClientFactory"); defaultClientFactory.setAccessible(true); synchronized (factoryClass) { Factory factory = (Factory) defaultClientFactory.get(null); if (factory == null || !(factory instanceof ApacheHttpAsyncClientFactory)) defaultClientFactory.set(null, new ApacheHttpAsyncClientFactory()); } } @Override public HttpClient createClient(URL url) { int connectionTimeout = TIMEOUT_TWO_MINUTES; int socketTimeout = TIMEOUT_THREE_HOURS; checkNotNull(url, "null URL"); if (connectionTimeout <= 0) { throw new IllegalArgumentException("connection timeout must be > 0"); } if (socketTimeout <= 0) { throw new IllegalArgumentException("socket timeout must be > 0"); } // same as HttpClientFactory#createRequestConfig(int, int) @SuppressWarnings("deprecation") RequestConfig requestConfig = RequestConfig.custom() .setStaleConnectionCheckEnabled(true) .setConnectTimeout(connectionTimeout) .setSocketTimeout(socketTimeout) .build(); // same as HttpClientFactory#createRoutePlanner() HttpRoutePlanner routePlanner = new SystemDefaultRoutePlanner( new DefaultSchemePortResolver(), ProxySelector.getDefault()); ConnectingIOReactor ioReactor; try { ioReactor = new DefaultConnectingIOReactor(); } catch (IOReactorException e) { throw new RuntimeException(e); } PoolingNHttpClientConnectionManager cm = new PoolingNHttpClientConnectionManager(ioReactor); // these values are obtained from HttpClientFactory#getClientConnectionManager() cm.setMaxTotal(2000); cm.setDefaultMaxPerRoute(2000); HttpAsyncClientBuilder builder = HttpAsyncClientBuilder.create() .setConnectionManager(cm) .setDefaultRequestConfig(requestConfig) .setRoutePlanner(routePlanner); String userInfo = url.getUserInfo(); if (userInfo != null) { String user; String password; int sep = userInfo.indexOf(':'); if (sep >= 0) { user = userInfo.substring(0, sep); password = userInfo.substring(sep + 1); } else { user = userInfo; password = null; } UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(user, password); CredentialsProvider provider = new BasicCredentialsProvider(); provider.setCredentials(AuthScope.ANY, credentials); builder.setDefaultCredentialsProvider(provider); } return new ApacheHttpAsyncClient(builder.build(), url); } }