001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2015 ForgeRock AS. 015 */ 016 017package org.forgerock.http.apache.async; 018 019import static java.util.concurrent.TimeUnit.MILLISECONDS; 020import static org.forgerock.http.handler.HttpClientHandler.OPTION_CONNECT_TIMEOUT; 021import static org.forgerock.http.handler.HttpClientHandler.OPTION_HOSTNAME_VERIFIER; 022import static org.forgerock.http.handler.HttpClientHandler.OPTION_KEY_MANAGERS; 023import static org.forgerock.http.handler.HttpClientHandler.OPTION_MAX_CONNECTIONS; 024import static org.forgerock.http.handler.HttpClientHandler.OPTION_REUSE_CONNECTIONS; 025import static org.forgerock.http.handler.HttpClientHandler.OPTION_SO_TIMEOUT; 026import static org.forgerock.http.handler.HttpClientHandler.OPTION_SSLCONTEXT_ALGORITHM; 027import static org.forgerock.http.handler.HttpClientHandler.OPTION_SSL_CIPHER_SUITES; 028import static org.forgerock.http.handler.HttpClientHandler.OPTION_SSL_ENABLED_PROTOCOLS; 029import static org.forgerock.http.handler.HttpClientHandler.OPTION_TEMPORARY_STORAGE; 030import static org.forgerock.http.handler.HttpClientHandler.OPTION_TRUST_MANAGERS; 031import static org.forgerock.http.util.Lists.asArrayOrNull; 032 033import java.security.GeneralSecurityException; 034import java.util.List; 035 036import javax.net.ssl.HostnameVerifier; 037import javax.net.ssl.SSLContext; 038 039import org.apache.http.HttpRequest; 040import org.apache.http.HttpResponse; 041import org.apache.http.ProtocolException; 042import org.apache.http.client.RedirectStrategy; 043import org.apache.http.client.methods.HttpUriRequest; 044import org.apache.http.config.Registry; 045import org.apache.http.config.RegistryBuilder; 046import org.apache.http.conn.ssl.DefaultHostnameVerifier; 047import org.apache.http.conn.ssl.NoopHostnameVerifier; 048import org.apache.http.impl.NoConnectionReuseStrategy; 049import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; 050import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; 051import org.apache.http.impl.nio.client.HttpAsyncClients; 052import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; 053import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; 054import org.apache.http.impl.nio.reactor.IOReactorConfig; 055import org.apache.http.nio.conn.NoopIOSessionStrategy; 056import org.apache.http.nio.conn.SchemeIOSessionStrategy; 057import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; 058import org.apache.http.nio.reactor.ConnectingIOReactor; 059import org.apache.http.nio.reactor.IOReactorException; 060import org.apache.http.protocol.HttpContext; 061import org.forgerock.http.HttpApplicationException; 062import org.forgerock.http.apache.NoAuthenticationStrategy; 063import org.forgerock.http.io.Buffer; 064import org.forgerock.http.spi.HttpClient; 065import org.forgerock.http.spi.HttpClientProvider; 066import org.forgerock.util.Factory; 067import org.forgerock.util.Option; 068import org.forgerock.util.Options; 069import org.forgerock.util.time.Duration; 070 071/** 072 * Creates and configures a {@link HttpClient} instance built around Apache HTTP Async Client component. 073 * 074 * @see <a href="https://hc.apache.org/httpcomponents-asyncclient-dev/index.html">Apache HTTP Async Client</a> 075 */ 076public class AsyncHttpClientProvider implements HttpClientProvider { 077 078 /** 079 * Specify the number of worker threads. If not set, the async client implementation manages this setting itself 080 * (by default this is number of CPU + 1). 081 */ 082 public static final Option<Integer> OPTION_WORKER_THREADS = Option.of(Integer.class, null); 083 084 /** 085 * A redirect strategy that never performs a redirect. 086 */ 087 private static final RedirectStrategy DISABLE_REDIRECT = new RedirectStrategy() { 088 @Override 089 public boolean isRedirected(final HttpRequest request, final HttpResponse response, 090 final HttpContext context) throws ProtocolException { 091 return false; 092 } 093 094 @Override 095 public HttpUriRequest getRedirect(final HttpRequest request, final HttpResponse response, 096 final HttpContext context) throws ProtocolException { 097 return null; 098 } 099 }; 100 101 @Override 102 public HttpClient newHttpClient(final Options options) throws HttpApplicationException { 103 104 final Factory<Buffer> storage = options.get(OPTION_TEMPORARY_STORAGE); 105 106 // SSL 107 final SSLContext sslContext; 108 try { 109 sslContext = SSLContext.getInstance(options.get(OPTION_SSLCONTEXT_ALGORITHM)); 110 sslContext.init(options.get(OPTION_KEY_MANAGERS), 111 options.get(OPTION_TRUST_MANAGERS), null); 112 } catch (final GeneralSecurityException e) { 113 throw new HttpApplicationException("Can't create SSL Context", e); 114 } 115 116 HostnameVerifier verifier = new DefaultHostnameVerifier(); 117 switch (options.get(OPTION_HOSTNAME_VERIFIER)) { 118 case ALLOW_ALL: 119 verifier = NoopHostnameVerifier.INSTANCE; 120 break; 121 } 122 123 List<String> protocols = options.get(OPTION_SSL_ENABLED_PROTOCOLS); 124 List<String> ciphers = options.get(OPTION_SSL_CIPHER_SUITES); 125 126 // Create a registry of custom connection session strategies for supported protocol schemes 127 Registry<SchemeIOSessionStrategy> registry = 128 RegistryBuilder.<SchemeIOSessionStrategy>create() 129 .register("http", NoopIOSessionStrategy.INSTANCE) 130 .register("https", new SSLIOSessionStrategy(sslContext, asArrayOrNull(protocols), 131 asArrayOrNull(ciphers), verifier)) 132 .build(); 133 134 // Timeouts 135 final Duration soTimeout = options.get(OPTION_SO_TIMEOUT); 136 final Duration connectTimeout = options.get(OPTION_CONNECT_TIMEOUT); 137 // FIXME GSA Can we support requestConnectTimeout ? 138 139 // Create I/O reactor configuration 140 IOReactorConfig.Builder reactorBuilder = IOReactorConfig.custom(); 141 142 if (!connectTimeout.isUnlimited()) { 143 reactorBuilder.setConnectTimeout((int) connectTimeout.to(MILLISECONDS)); 144 } 145 if (!soTimeout.isUnlimited()) { 146 reactorBuilder.setSoTimeout((int) soTimeout.to(MILLISECONDS)); 147 } 148 Integer threadCount = options.get(OPTION_WORKER_THREADS); 149 if (threadCount != null) { 150 reactorBuilder.setIoThreadCount(threadCount); 151 } 152 IOReactorConfig ioReactorConfig = reactorBuilder.build(); 153 154 // Create a custom I/O reactor 155 ConnectingIOReactor reactor; 156 try { 157 reactor = new DefaultConnectingIOReactor(ioReactorConfig); 158 } catch (IOReactorException e) { 159 throw new HttpApplicationException("Cannot create I/O Reactor", e); 160 } 161 162 // Create a connection manager with custom configuration. 163 PoolingNHttpClientConnectionManager manager = new PoolingNHttpClientConnectionManager(reactor, registry); 164 165 // Connection pooling 166 final int maxConnections = options.get(OPTION_MAX_CONNECTIONS); 167 manager.setMaxTotal(maxConnections); 168 manager.setDefaultMaxPerRoute(maxConnections); 169 170 // FIXME GSA Couldn't find how to configure retries in async http client 171 //if (!options.get(OPTION_RETRY_REQUESTS)) { 172 // builder.disableAutomaticRetries(); 173 //} 174 175 // Create a client with the given custom dependencies and configuration. 176 HttpAsyncClientBuilder builder = HttpAsyncClients.custom(); 177 178 if (!options.get(OPTION_REUSE_CONNECTIONS)) { 179 builder.setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE); 180 } 181 182 // TODO Uncomment when we'll have a user-agent Option 183 // builder.setUserAgent("CHF/1.0"); 184 185 CloseableHttpAsyncClient client = builder.setConnectionManager(manager) 186 .disableCookieManagement() 187 .setRedirectStrategy(DISABLE_REDIRECT) 188 .setTargetAuthenticationStrategy(NoAuthenticationStrategy.INSTANCE) 189 .setProxyAuthenticationStrategy(NoAuthenticationStrategy.INSTANCE) 190 .build(); 191 client.start(); 192 return new AsyncHttpClient(client, storage); 193 } 194}