View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2015-2016 ForgeRock AS.
15   */
16  
17  package org.forgerock.http.apache.async;
18  
19  import static java.util.concurrent.TimeUnit.MILLISECONDS;
20  import static org.forgerock.http.handler.HttpClientHandler.OPTION_CONNECT_TIMEOUT;
21  import static org.forgerock.http.handler.HttpClientHandler.OPTION_HOSTNAME_VERIFIER;
22  import static org.forgerock.http.handler.HttpClientHandler.OPTION_KEY_MANAGERS;
23  import static org.forgerock.http.handler.HttpClientHandler.OPTION_MAX_CONNECTIONS;
24  import static org.forgerock.http.handler.HttpClientHandler.OPTION_REUSE_CONNECTIONS;
25  import static org.forgerock.http.handler.HttpClientHandler.OPTION_SO_TIMEOUT;
26  import static org.forgerock.http.handler.HttpClientHandler.OPTION_SSLCONTEXT_ALGORITHM;
27  import static org.forgerock.http.handler.HttpClientHandler.OPTION_SSL_CIPHER_SUITES;
28  import static org.forgerock.http.handler.HttpClientHandler.OPTION_SSL_ENABLED_PROTOCOLS;
29  import static org.forgerock.http.handler.HttpClientHandler.OPTION_TEMPORARY_STORAGE;
30  import static org.forgerock.http.handler.HttpClientHandler.OPTION_TRUST_MANAGERS;
31  import static org.forgerock.http.util.Lists.asArrayOrNull;
32  
33  import java.security.GeneralSecurityException;
34  import java.util.List;
35  
36  import javax.net.ssl.HostnameVerifier;
37  import javax.net.ssl.SSLContext;
38  
39  import org.apache.http.HttpRequest;
40  import org.apache.http.HttpResponse;
41  import org.apache.http.ProtocolException;
42  import org.apache.http.client.RedirectStrategy;
43  import org.apache.http.client.methods.HttpUriRequest;
44  import org.apache.http.config.Registry;
45  import org.apache.http.config.RegistryBuilder;
46  import org.apache.http.conn.ssl.DefaultHostnameVerifier;
47  import org.apache.http.conn.ssl.NoopHostnameVerifier;
48  import org.apache.http.impl.NoConnectionReuseStrategy;
49  import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
50  import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
51  import org.apache.http.impl.nio.client.HttpAsyncClients;
52  import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
53  import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
54  import org.apache.http.impl.nio.reactor.IOReactorConfig;
55  import org.apache.http.nio.conn.NoopIOSessionStrategy;
56  import org.apache.http.nio.conn.SchemeIOSessionStrategy;
57  import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
58  import org.apache.http.nio.reactor.ConnectingIOReactor;
59  import org.apache.http.nio.reactor.IOReactorException;
60  import org.apache.http.protocol.HttpContext;
61  import org.forgerock.http.HttpApplicationException;
62  import org.forgerock.http.apache.NoAuthenticationStrategy;
63  import org.forgerock.http.io.Buffer;
64  import org.forgerock.http.spi.HttpClient;
65  import org.forgerock.http.spi.HttpClientProvider;
66  import org.forgerock.util.Factory;
67  import org.forgerock.util.Option;
68  import org.forgerock.util.Options;
69  import org.forgerock.util.time.Duration;
70  
71  /**
72   * Creates and configures a {@link HttpClient} instance built around Apache HTTP Async Client component.
73   *
74   * @see <a href="https://hc.apache.org/httpcomponents-asyncclient-dev/index.html">Apache HTTP Async Client</a>
75   */
76  public class AsyncHttpClientProvider implements HttpClientProvider {
77  
78      /**
79       * Specify the number of worker threads. If not set, the async client implementation manages this setting itself
80       * (by default this is number of CPU + 1).
81       */
82      public static final Option<Integer> OPTION_WORKER_THREADS = Option.of(Integer.class, null);
83  
84      /**
85       * A redirect strategy that never performs a redirect.
86       */
87      private static final RedirectStrategy DISABLE_REDIRECT = new RedirectStrategy() {
88          @Override
89          public boolean isRedirected(final HttpRequest request, final HttpResponse response,
90                  final HttpContext context) throws ProtocolException {
91              return false;
92          }
93  
94          @Override
95          public HttpUriRequest getRedirect(final HttpRequest request, final HttpResponse response,
96                  final HttpContext context) throws ProtocolException {
97              return null;
98          }
99      };
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, ioReactorConfig.getIoThreadCount());
193     }
194 }