View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.client5.http.impl.async;
29  
30  import java.io.Closeable;
31  import java.io.IOException;
32  import java.net.InetSocketAddress;
33  import java.security.AccessController;
34  import java.security.PrivilegedAction;
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.LinkedList;
38  import java.util.List;
39  import java.util.concurrent.ThreadFactory;
40  
41  import org.apache.hc.client5.http.AuthenticationStrategy;
42  import org.apache.hc.client5.http.DnsResolver;
43  import org.apache.hc.client5.http.HttpRequestRetryStrategy;
44  import org.apache.hc.client5.http.SchemePortResolver;
45  import org.apache.hc.client5.http.async.AsyncExecChainHandler;
46  import org.apache.hc.client5.http.auth.AuthSchemeFactory;
47  import org.apache.hc.client5.http.auth.StandardAuthScheme;
48  import org.apache.hc.client5.http.auth.CredentialsProvider;
49  import org.apache.hc.client5.http.config.RequestConfig;
50  import org.apache.hc.client5.http.cookie.BasicCookieStore;
51  import org.apache.hc.client5.http.cookie.CookieSpecFactory;
52  import org.apache.hc.client5.http.cookie.CookieStore;
53  import org.apache.hc.client5.http.impl.ChainElement;
54  import org.apache.hc.client5.http.impl.CookieSpecSupport;
55  import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
56  import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
57  import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
58  import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
59  import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
60  import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
61  import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
62  import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory;
63  import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory;
64  import org.apache.hc.client5.http.impl.auth.SPNegoSchemeFactory;
65  import org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider;
66  import org.apache.hc.client5.http.impl.nio.MultihomeConnectionInitiator;
67  import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
68  import org.apache.hc.client5.http.protocol.RedirectStrategy;
69  import org.apache.hc.client5.http.protocol.RequestAddCookies;
70  import org.apache.hc.client5.http.protocol.RequestAuthCache;
71  import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
72  import org.apache.hc.client5.http.protocol.RequestExpectContinue;
73  import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
74  import org.apache.hc.client5.http.routing.HttpRoutePlanner;
75  import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
76  import org.apache.hc.core5.annotation.Internal;
77  import org.apache.hc.core5.concurrent.DefaultThreadFactory;
78  import org.apache.hc.core5.function.Callback;
79  import org.apache.hc.core5.function.Resolver;
80  import org.apache.hc.core5.http.Header;
81  import org.apache.hc.core5.http.HttpException;
82  import org.apache.hc.core5.http.HttpHost;
83  import org.apache.hc.core5.http.HttpRequest;
84  import org.apache.hc.core5.http.HttpRequestInterceptor;
85  import org.apache.hc.core5.http.HttpResponseInterceptor;
86  import org.apache.hc.core5.http.config.CharCodingConfig;
87  import org.apache.hc.core5.http.config.Lookup;
88  import org.apache.hc.core5.http.config.NamedElementChain;
89  import org.apache.hc.core5.http.config.RegistryBuilder;
90  import org.apache.hc.core5.http.nio.AsyncPushConsumer;
91  import org.apache.hc.core5.http.nio.HandlerFactory;
92  import org.apache.hc.core5.http.nio.command.ShutdownCommand;
93  import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
94  import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
95  import org.apache.hc.core5.http.protocol.HttpContext;
96  import org.apache.hc.core5.http.protocol.HttpProcessor;
97  import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
98  import org.apache.hc.core5.http.protocol.RequestTargetHost;
99  import org.apache.hc.core5.http.protocol.RequestUserAgent;
100 import org.apache.hc.core5.http2.config.H2Config;
101 import org.apache.hc.core5.http2.nio.pool.H2ConnPool;
102 import org.apache.hc.core5.http2.protocol.H2RequestConnControl;
103 import org.apache.hc.core5.http2.protocol.H2RequestContent;
104 import org.apache.hc.core5.http2.protocol.H2RequestTargetHost;
105 import org.apache.hc.core5.io.CloseMode;
106 import org.apache.hc.core5.reactor.Command;
107 import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
108 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
109 import org.apache.hc.core5.reactor.IOReactorConfig;
110 import org.apache.hc.core5.reactor.IOSession;
111 import org.apache.hc.core5.util.Args;
112 import org.apache.hc.core5.util.TimeValue;
113 import org.apache.hc.core5.util.VersionInfo;
114 
115 /**
116  * Builder for HTTP/2 only {@link CloseableHttpAsyncClient} instances.
117  * <p>
118  * Concurrent message exchanges with the same connection route executed
119  * with these {@link CloseableHttpAsyncClient} instances will get
120  * automatically multiplexed over a single physical HTTP/2 connection.
121  * </p>
122  * <p>
123  * When a particular component is not explicitly set this class will
124  * use its default implementation.
125  * <p>
126  *
127  * @since 5.0
128  */
129 public class H2AsyncClientBuilder {
130 
131     private static class RequestInterceptorEntry {
132 
133         enum Position { FIRST, LAST }
134 
135         final RequestInterceptorEntry.Position position;
136         final HttpRequestInterceptor interceptor;
137 
138         private RequestInterceptorEntry(final RequestInterceptorEntry.Position position, final HttpRequestInterceptor interceptor) {
139             this.position = position;
140             this.interceptor = interceptor;
141         }
142     }
143 
144     private static class ResponseInterceptorEntry {
145 
146         enum Position { FIRST, LAST }
147 
148         final ResponseInterceptorEntry.Position position;
149         final HttpResponseInterceptor interceptor;
150 
151         private ResponseInterceptorEntry(final ResponseInterceptorEntry.Position position, final HttpResponseInterceptor interceptor) {
152             this.position = position;
153             this.interceptor = interceptor;
154         }
155     }
156 
157     private static class ExecInterceptorEntry {
158 
159         enum Position { BEFORE, AFTER, REPLACE, FIRST, LAST }
160 
161         final ExecInterceptorEntry.Position position;
162         final String name;
163         final AsyncExecChainHandler interceptor;
164         final String existing;
165 
166         private ExecInterceptorEntry(
167                 final ExecInterceptorEntry.Position position,
168                 final String name,
169                 final AsyncExecChainHandler interceptor,
170                 final String existing) {
171             this.position = position;
172             this.name = name;
173             this.interceptor = interceptor;
174             this.existing = existing;
175         }
176 
177     }
178 
179     private IOReactorConfig ioReactorConfig;
180     private H2Config h2Config;
181     private CharCodingConfig charCodingConfig;
182     private SchemePortResolver schemePortResolver;
183     private AuthenticationStrategy targetAuthStrategy;
184     private AuthenticationStrategy proxyAuthStrategy;
185 
186     private LinkedList<RequestInterceptorEntry> requestInterceptors;
187     private LinkedList<ResponseInterceptorEntry> responseInterceptors;
188     private LinkedList<ExecInterceptorEntry> execInterceptors;
189 
190     private HttpRoutePlanner routePlanner;
191     private RedirectStrategy redirectStrategy;
192     private HttpRequestRetryStrategy retryStrategy;
193 
194     private Lookup<AuthSchemeFactory> authSchemeRegistry;
195     private Lookup<CookieSpecFactory> cookieSpecRegistry;
196     private CookieStore cookieStore;
197     private CredentialsProvider credentialsProvider;
198 
199     private String userAgent;
200     private Collection<? extends Header> defaultHeaders;
201     private RequestConfig defaultRequestConfig;
202     private boolean evictIdleConnections;
203     private TimeValue maxIdleTime;
204 
205     private boolean systemProperties;
206     private boolean automaticRetriesDisabled;
207     private boolean redirectHandlingDisabled;
208     private boolean cookieManagementDisabled;
209     private boolean authCachingDisabled;
210 
211     private DnsResolver dnsResolver;
212     private TlsStrategy tlsStrategy;
213 
214     private ThreadFactory threadFactory;
215 
216     private List<Closeable> closeables;
217 
218     public static H2AsyncClientBuilder create() {
219         return new H2AsyncClientBuilder();
220     }
221 
222     protected H2AsyncClientBuilder() {
223         super();
224     }
225 
226     /**
227      * Sets {@link H2Config} configuration.
228      */
229     public final H2AsyncClientBuilder setH2Config(final H2Config h2Config) {
230         this.h2Config = h2Config;
231         return this;
232     }
233 
234     /**
235      * Sets {@link IOReactorConfig} configuration.
236      */
237     public final H2AsyncClientBuilder setIOReactorConfig(final IOReactorConfig ioReactorConfig) {
238         this.ioReactorConfig = ioReactorConfig;
239         return this;
240     }
241 
242     /**
243      * Sets {@link CharCodingConfig} configuration.
244      */
245     public final H2AsyncClientBuilder setCharCodingConfig(final CharCodingConfig charCodingConfig) {
246         this.charCodingConfig = charCodingConfig;
247         return this;
248     }
249 
250     /**
251      * Assigns {@link AuthenticationStrategy} instance for target
252      * host authentication.
253      */
254     public final H2AsyncClientBuilder setTargetAuthenticationStrategy(
255             final AuthenticationStrategy targetAuthStrategy) {
256         this.targetAuthStrategy = targetAuthStrategy;
257         return this;
258     }
259 
260     /**
261      * Assigns {@link AuthenticationStrategy} instance for proxy
262      * authentication.
263      */
264     public final H2AsyncClientBuilder setProxyAuthenticationStrategy(
265             final AuthenticationStrategy proxyAuthStrategy) {
266         this.proxyAuthStrategy = proxyAuthStrategy;
267         return this;
268     }
269 
270     /**
271      * Adds this protocol interceptor to the head of the protocol processing list.
272      */
273     public final H2AsyncClientBuilder addResponseInterceptorFirst(final HttpResponseInterceptor interceptor) {
274         Args.notNull(interceptor, "Interceptor");
275         if (responseInterceptors == null) {
276             responseInterceptors = new LinkedList<>();
277         }
278         responseInterceptors.add(new ResponseInterceptorEntry(ResponseInterceptorEntry.Position.FIRST, interceptor));
279         return this;
280     }
281 
282     /**
283      * Adds this protocol interceptor to the tail of the protocol processing list.
284      */
285     public final H2AsyncClientBuilder addResponseInterceptorLast(final HttpResponseInterceptor interceptor) {
286         Args.notNull(interceptor, "Interceptor");
287         if (responseInterceptors == null) {
288             responseInterceptors = new LinkedList<>();
289         }
290         responseInterceptors.add(new ResponseInterceptorEntry(ResponseInterceptorEntry.Position.LAST, interceptor));
291         return this;
292     }
293 
294     /**
295      * Adds this execution interceptor before an existing interceptor.
296      */
297     public final H2AsyncClientBuilder addExecInterceptorBefore(final String existing, final String name, final AsyncExecChainHandler interceptor) {
298         Args.notBlank(existing, "Existing");
299         Args.notBlank(name, "Name");
300         Args.notNull(interceptor, "Interceptor");
301         if (execInterceptors == null) {
302             execInterceptors = new LinkedList<>();
303         }
304         execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.BEFORE, name, interceptor, existing));
305         return this;
306     }
307 
308     /**
309      * Adds this execution interceptor after interceptor with the given name.
310      */
311     public final H2AsyncClientBuilder addExecInterceptorAfter(final String existing, final String name, final AsyncExecChainHandler interceptor) {
312         Args.notBlank(existing, "Existing");
313         Args.notBlank(name, "Name");
314         Args.notNull(interceptor, "Interceptor");
315         if (execInterceptors == null) {
316             execInterceptors = new LinkedList<>();
317         }
318         execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.AFTER, name, interceptor, existing));
319         return this;
320     }
321 
322     /**
323      * Replace an existing interceptor with the given name with new interceptor.
324      */
325     public final H2AsyncClientBuilder replaceExecInterceptor(final String existing, final AsyncExecChainHandler interceptor) {
326         Args.notBlank(existing, "Existing");
327         Args.notNull(interceptor, "Interceptor");
328         if (execInterceptors == null) {
329             execInterceptors = new LinkedList<>();
330         }
331         execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.REPLACE, existing, interceptor, existing));
332         return this;
333     }
334 
335     /**
336      * Add an interceptor to the head of the processing list.
337      */
338     public final H2AsyncClientBuilder addExecInterceptorFirst(final String name, final AsyncExecChainHandler interceptor) {
339         Args.notNull(name, "Name");
340         Args.notNull(interceptor, "Interceptor");
341         if (execInterceptors == null) {
342             execInterceptors = new LinkedList<>();
343         }
344         execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.FIRST, name, interceptor, null));
345         return this;
346     }
347 
348     /**
349      * Add an interceptor to the tail of the processing list.
350      */
351     public final H2AsyncClientBuilder addExecInterceptorLast(final String name, final AsyncExecChainHandler interceptor) {
352         Args.notNull(name, "Name");
353         Args.notNull(interceptor, "Interceptor");
354         if (execInterceptors == null) {
355             execInterceptors = new LinkedList<>();
356         }
357         execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.LAST, name, interceptor, null));
358         return this;
359     }
360 
361     /**
362      * Adds this protocol interceptor to the head of the protocol processing list.
363      */
364     public final H2AsyncClientBuilder addRequestInterceptorFirst(final HttpRequestInterceptor interceptor) {
365         Args.notNull(interceptor, "Interceptor");
366         if (requestInterceptors == null) {
367             requestInterceptors = new LinkedList<>();
368         }
369         requestInterceptors.add(new RequestInterceptorEntry(RequestInterceptorEntry.Position.FIRST, interceptor));
370         return this;
371     }
372 
373     /**
374      * Adds this protocol interceptor to the tail of the protocol processing list.
375      */
376     public final H2AsyncClientBuilder addRequestInterceptorLast(final HttpRequestInterceptor interceptor) {
377         Args.notNull(interceptor, "Interceptor");
378         if (requestInterceptors == null) {
379             requestInterceptors = new LinkedList<>();
380         }
381         requestInterceptors.add(new RequestInterceptorEntry(RequestInterceptorEntry.Position.LAST, interceptor));
382         return this;
383     }
384 
385     /**
386      * Assigns {@link HttpRequestRetryStrategy} instance.
387      * <p>
388      * Please note this value can be overridden by the {@link #disableAutomaticRetries()}
389      * method.
390      */
391     public final H2AsyncClientBuilder setRetryStrategy(final HttpRequestRetryStrategy retryStrategy) {
392         this.retryStrategy = retryStrategy;
393         return this;
394     }
395 
396     /**
397      * Assigns {@link RedirectStrategy} instance.
398      * <p>
399      * Please note this value can be overridden by the {@link #disableRedirectHandling()}
400      * method.
401      * </p>
402      */
403     public H2AsyncClientBuilder setRedirectStrategy(final RedirectStrategy redirectStrategy) {
404         this.redirectStrategy = redirectStrategy;
405         return this;
406     }
407 
408     /**
409      * Assigns {@link SchemePortResolver} instance.
410      */
411     public final H2AsyncClientBuilder setSchemePortResolver(final SchemePortResolver schemePortResolver) {
412         this.schemePortResolver = schemePortResolver;
413         return this;
414     }
415 
416     /**
417      * Assigns {@link DnsResolver} instance.
418      */
419     public final H2AsyncClientBuilder setDnsResolver(final DnsResolver dnsResolver) {
420         this.dnsResolver = dnsResolver;
421         return this;
422     }
423 
424     /**
425      * Assigns {@link TlsStrategy} instance.
426      */
427     public final H2AsyncClientBuilder setTlsStrategy(final TlsStrategy tlsStrategy) {
428         this.tlsStrategy = tlsStrategy;
429         return this;
430     }
431 
432     /**
433      * Assigns {@link ThreadFactory} instance.
434      */
435     public final H2AsyncClientBuilder setThreadFactory(final ThreadFactory threadFactory) {
436         this.threadFactory = threadFactory;
437         return this;
438     }
439 
440     /**
441      * Assigns {@code User-Agent} value.
442      */
443     public final H2AsyncClientBuilder setUserAgent(final String userAgent) {
444         this.userAgent = userAgent;
445         return this;
446     }
447 
448     /**
449      * Assigns default request header values.
450      */
451     public final H2AsyncClientBuilder setDefaultHeaders(final Collection<? extends Header> defaultHeaders) {
452         this.defaultHeaders = defaultHeaders;
453         return this;
454     }
455 
456     /**
457      * Assigns {@link HttpRoutePlanner} instance.
458      */
459     public final H2AsyncClientBuilder setRoutePlanner(final HttpRoutePlanner routePlanner) {
460         this.routePlanner = routePlanner;
461         return this;
462     }
463 
464     /**
465      * Assigns default {@link CredentialsProvider} instance which will be used
466      * for request execution if not explicitly set in the client execution
467      * context.
468      */
469     public final H2AsyncClientBuilder setDefaultCredentialsProvider(final CredentialsProvider credentialsProvider) {
470         this.credentialsProvider = credentialsProvider;
471         return this;
472     }
473 
474     /**
475      * Assigns default {@link org.apache.hc.client5.http.auth.AuthScheme} registry which will
476      * be used for request execution if not explicitly set in the client execution
477      * context.
478      */
479     public final H2AsyncClientBuilder setDefaultAuthSchemeRegistry(final Lookup<AuthSchemeFactory> authSchemeRegistry) {
480         this.authSchemeRegistry = authSchemeRegistry;
481         return this;
482     }
483 
484     /**
485      * Assigns default {@link org.apache.hc.client5.http.cookie.CookieSpec} registry
486      * which will be used for request execution if not explicitly set in the client
487      * execution context.
488      */
489     public final H2AsyncClientBuilder setDefaultCookieSpecRegistry(final Lookup<CookieSpecFactory> cookieSpecRegistry) {
490         this.cookieSpecRegistry = cookieSpecRegistry;
491         return this;
492     }
493 
494     /**
495      * Assigns default {@link CookieStore} instance which will be used for
496      * request execution if not explicitly set in the client execution context.
497      */
498     public final H2AsyncClientBuilder setDefaultCookieStore(final CookieStore cookieStore) {
499         this.cookieStore = cookieStore;
500         return this;
501     }
502 
503     /**
504      * Assigns default {@link RequestConfig} instance which will be used
505      * for request execution if not explicitly set in the client execution
506      * context.
507      */
508     public final H2AsyncClientBuilder setDefaultRequestConfig(final RequestConfig config) {
509         this.defaultRequestConfig = config;
510         return this;
511     }
512 
513     /**
514      * Use system properties when creating and configuring default
515      * implementations.
516      */
517     public final H2AsyncClientBuilder useSystemProperties() {
518         this.systemProperties = true;
519         return this;
520     }
521 
522     /**
523      * Disables automatic redirect handling.
524      */
525     public final H2AsyncClientBuilder disableRedirectHandling() {
526         redirectHandlingDisabled = true;
527         return this;
528     }
529 
530     /**
531      * Disables automatic request recovery and re-execution.
532      */
533     public final H2AsyncClientBuilder disableAutomaticRetries() {
534         automaticRetriesDisabled = true;
535         return this;
536     }
537 
538     /**
539      * Disables state (cookie) management.
540      */
541     public final H2AsyncClientBuilder disableCookieManagement() {
542         this.cookieManagementDisabled = true;
543         return this;
544     }
545 
546     /**
547      * Disables authentication scheme caching.
548      */
549     public final H2AsyncClientBuilder disableAuthCaching() {
550         this.authCachingDisabled = true;
551         return this;
552     }
553 
554     /**
555      * Makes this instance of HttpClient proactively evict idle connections from the
556      * connection pool using a background thread.
557      * <p>
558      * One MUST explicitly close HttpClient with {@link CloseableHttpAsyncClient#close()}
559      * in order to stop and release the background thread.
560      * <p>
561      * Please note this method has no effect if the instance of HttpClient is configured to
562      * use a shared connection manager.
563      *
564      * @param maxIdleTime maximum time persistent connections can stay idle while kept alive
565      * in the connection pool. Connections whose inactivity period exceeds this value will
566      * get closed and evicted from the pool.
567      */
568     public final H2AsyncClientBuilder evictIdleConnections(final TimeValue maxIdleTime) {
569         this.evictIdleConnections = true;
570         this.maxIdleTime = maxIdleTime;
571         return this;
572     }
573 
574     /**
575      * Request exec chain customization and extension.
576      * <p>
577      * For internal use.
578      */
579     @Internal
580     protected void customizeExecChain(final NamedElementChain<AsyncExecChainHandler> execChainDefinition) {
581     }
582 
583     /**
584      * Adds to the list of {@link Closeable} resources to be managed by the client.
585      * <p>
586      * For internal use.
587      */
588     @Internal
589     protected void addCloseable(final Closeable closeable) {
590         if (closeable == null) {
591             return;
592         }
593         if (closeables == null) {
594             closeables = new ArrayList<>();
595         }
596         closeables.add(closeable);
597     }
598 
599     public CloseableHttpAsyncClient build() {
600         final NamedElementChain<AsyncExecChainHandler> execChainDefinition = new NamedElementChain<>();
601         execChainDefinition.addLast(
602                 new H2AsyncMainClientExec(),
603                 ChainElement.MAIN_TRANSPORT.name());
604 
605         AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy;
606         if (targetAuthStrategyCopy == null) {
607             targetAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
608         }
609         AuthenticationStrategy proxyAuthStrategyCopy = this.proxyAuthStrategy;
610         if (proxyAuthStrategyCopy == null) {
611             proxyAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
612         }
613 
614         String userAgentCopy = this.userAgent;
615         if (userAgentCopy == null) {
616             if (systemProperties) {
617                 userAgentCopy = getProperty("http.agent", null);
618             }
619             if (userAgentCopy == null) {
620                 userAgentCopy = VersionInfo.getSoftwareInfo("Apache-HttpAsyncClient",
621                         "org.apache.hc.client5", getClass());
622             }
623         }
624 
625         execChainDefinition.addFirst(
626                 new AsyncConnectExec(
627                         new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
628                         proxyAuthStrategyCopy),
629                 ChainElement.CONNECT.name());
630 
631         final HttpProcessorBuilder b = HttpProcessorBuilder.create();
632         if (requestInterceptors != null) {
633             for (final RequestInterceptorEntry entry: requestInterceptors) {
634                 if (entry.position == RequestInterceptorEntry.Position.FIRST) {
635                     b.addFirst(entry.interceptor);
636                 }
637             }
638         }
639         if (responseInterceptors != null) {
640             for (final ResponseInterceptorEntry entry: responseInterceptors) {
641                 if (entry.position == ResponseInterceptorEntry.Position.FIRST) {
642                     b.addFirst(entry.interceptor);
643                 }
644             }
645         }
646         b.addAll(
647                 new RequestDefaultHeaders(defaultHeaders),
648                 new RequestUserAgent(userAgentCopy),
649                 new RequestExpectContinue());
650         if (!cookieManagementDisabled) {
651             b.add(new RequestAddCookies());
652         }
653         if (!authCachingDisabled) {
654             b.add(new RequestAuthCache());
655         }
656         if (!cookieManagementDisabled) {
657             b.add(new ResponseProcessCookies());
658         }
659         if (requestInterceptors != null) {
660             for (final RequestInterceptorEntry entry: requestInterceptors) {
661                 if (entry.position == RequestInterceptorEntry.Position.LAST) {
662                     b.addLast(entry.interceptor);
663                 }
664             }
665         }
666         if (responseInterceptors != null) {
667             for (final ResponseInterceptorEntry entry: responseInterceptors) {
668                 if (entry.position == ResponseInterceptorEntry.Position.LAST) {
669                     b.addLast(entry.interceptor);
670                 }
671             }
672         }
673 
674         final HttpProcessor httpProcessor = b.build();
675         execChainDefinition.addFirst(
676                 new AsyncProtocolExec(httpProcessor, targetAuthStrategyCopy, proxyAuthStrategyCopy),
677                 ChainElement.PROTOCOL.name());
678 
679         // Add request retry executor, if not disabled
680         if (!automaticRetriesDisabled) {
681             HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;
682             if (retryStrategyCopy == null) {
683                 retryStrategyCopy = DefaultHttpRequestRetryStrategy.INSTANCE;
684             }
685             execChainDefinition.addFirst(
686                     new AsyncHttpRequestRetryExec(retryStrategyCopy),
687                     ChainElement.RETRY.name());
688         }
689 
690         HttpRoutePlanner routePlannerCopy = this.routePlanner;
691         if (routePlannerCopy == null) {
692             SchemePortResolver schemePortResolverCopy = this.schemePortResolver;
693             if (schemePortResolverCopy == null) {
694                 schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE;
695             }
696             routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
697         }
698 
699         // Add redirect executor, if not disabled
700         if (!redirectHandlingDisabled) {
701             RedirectStrategy redirectStrategyCopy = this.redirectStrategy;
702             if (redirectStrategyCopy == null) {
703                 redirectStrategyCopy = DefaultRedirectStrategy.INSTANCE;
704             }
705             execChainDefinition.addFirst(
706                     new AsyncRedirectExec(routePlannerCopy, redirectStrategyCopy),
707                     ChainElement.REDIRECT.name());
708         }
709 
710         final AsyncPushConsumerRegistrymerRegistry.html#AsyncPushConsumerRegistry">AsyncPushConsumerRegistry pushConsumerRegistry = new AsyncPushConsumerRegistry();
711         final IOEventHandlerFactory ioEventHandlerFactory = new H2AsyncClientEventHandlerFactory(
712                 new DefaultHttpProcessor(new H2RequestContent(), new H2RequestTargetHost(), new H2RequestConnControl()),
713                 new HandlerFactory<AsyncPushConsumer>() {
714 
715                     @Override
716                     public AsyncPushConsumer create(final HttpRequest request, final HttpContext context) throws HttpException {
717                         return pushConsumerRegistry.get(request);
718                     }
719 
720                 },
721                 h2Config != null ? h2Config : H2Config.DEFAULT,
722                 charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT);
723         final DefaultConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(
724                 ioEventHandlerFactory,
725                 ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT,
726                 threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-dispatch", true),
727                 LoggingIOSessionDecorator.INSTANCE,
728                 LoggingExceptionCallback.INSTANCE,
729                 null,
730                 new Callback<IOSession>() {
731 
732                     @Override
733                     public void execute(final IOSession ioSession) {
734                         ioSession.enqueue(new ShutdownCommand(CloseMode.GRACEFUL), Command.Priority.IMMEDIATE);
735                     }
736 
737                 });
738 
739         if (execInterceptors != null) {
740             for (final ExecInterceptorEntry entry: execInterceptors) {
741                 switch (entry.position) {
742                     case AFTER:
743                         execChainDefinition.addAfter(entry.existing, entry.interceptor, entry.name);
744                         break;
745                     case BEFORE:
746                         execChainDefinition.addBefore(entry.existing, entry.interceptor, entry.name);
747                         break;
748                     case REPLACE:
749                         execChainDefinition.replace(entry.existing, entry.interceptor);
750                         break;
751                     case FIRST:
752                         execChainDefinition.addFirst(entry.interceptor, entry.name);
753                         break;
754                     case LAST:
755                         // Don't add last, after H2AsyncMainClientExec, as that does not delegate to the chain
756                         // Instead, add the interceptor just before it, making it effectively the last interceptor
757                         execChainDefinition.addBefore(ChainElement.MAIN_TRANSPORT.name(), entry.interceptor, entry.name);
758                         break;
759                 }
760             }
761         }
762 
763         customizeExecChain(execChainDefinition);
764 
765         NamedElementChain<AsyncExecChainHandler>.Node current = execChainDefinition.getLast();
766         AsyncExecChainElement execChain = null;
767         while (current != null) {
768             execChain = new AsyncExecChainElement(current.getValue(), execChain);
769             current = current.getPrevious();
770         }
771 
772         Lookup<AuthSchemeFactory> authSchemeRegistryCopy = this.authSchemeRegistry;
773         if (authSchemeRegistryCopy == null) {
774             authSchemeRegistryCopy = RegistryBuilder.<AuthSchemeFactory>create()
775                     .register(StandardAuthScheme.BASIC, BasicSchemeFactory.INSTANCE)
776                     .register(StandardAuthScheme.DIGEST, DigestSchemeFactory.INSTANCE)
777                     .register(StandardAuthScheme.NTLM, NTLMSchemeFactory.INSTANCE)
778                     .register(StandardAuthScheme.SPNEGO, SPNegoSchemeFactory.DEFAULT)
779                     .register(StandardAuthScheme.KERBEROS, KerberosSchemeFactory.DEFAULT)
780                     .build();
781         }
782         Lookup<CookieSpecFactory> cookieSpecRegistryCopy = this.cookieSpecRegistry;
783         if (cookieSpecRegistryCopy == null) {
784             cookieSpecRegistryCopy = CookieSpecSupport.createDefault();
785         }
786 
787         CookieStore cookieStoreCopy = this.cookieStore;
788         if (cookieStoreCopy == null) {
789             cookieStoreCopy = new BasicCookieStore();
790         }
791 
792         CredentialsProvider credentialsProviderCopy = this.credentialsProvider;
793         if (credentialsProviderCopy == null) {
794             if (systemProperties) {
795                 credentialsProviderCopy = new SystemDefaultCredentialsProvider();
796             } else {
797                 credentialsProviderCopy = new BasicCredentialsProvider();
798             }
799         }
800 
801         TlsStrategy tlsStrategyCopy = this.tlsStrategy;
802         if (tlsStrategyCopy == null) {
803             if (systemProperties) {
804                 tlsStrategyCopy = DefaultClientTlsStrategy.getSystemDefault();
805             } else {
806                 tlsStrategyCopy = DefaultClientTlsStrategy.getDefault();
807             }
808         }
809 
810         final MultihomeConnectionInitiatornInitiator.html#MultihomeConnectionInitiator">MultihomeConnectionInitiator connectionInitiator = new MultihomeConnectionInitiator(ioReactor, dnsResolver);
811         final H2ConnPool connPool = new H2ConnPool(connectionInitiator, new Resolver<HttpHost, InetSocketAddress>() {
812 
813             @Override
814             public InetSocketAddress resolve(final HttpHost host) {
815                 return null;
816             }
817 
818         }, tlsStrategyCopy);
819 
820         List<Closeable> closeablesCopy = closeables != null ? new ArrayList<>(closeables) : null;
821         if (closeablesCopy == null) {
822             closeablesCopy = new ArrayList<>(1);
823         }
824         if (evictIdleConnections) {
825             final IdleConnectionEvictornEvictor.html#IdleConnectionEvictor">IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(connPool,
826                     maxIdleTime != null ? maxIdleTime : TimeValue.ofSeconds(30L));
827             closeablesCopy.add(new Closeable() {
828 
829                 @Override
830                 public void close() throws IOException {
831                     connectionEvictor.shutdown();
832                 }
833 
834             });
835             connectionEvictor.start();
836         }
837         closeablesCopy.add(connPool);
838 
839         return new InternalH2AsyncClient(
840                 ioReactor,
841                 execChain,
842                 pushConsumerRegistry,
843                 threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-main", true),
844                 connPool,
845                 routePlannerCopy,
846                 cookieSpecRegistryCopy,
847                 authSchemeRegistryCopy,
848                 cookieStoreCopy,
849                 credentialsProviderCopy,
850                 defaultRequestConfig,
851                 closeablesCopy);
852     }
853 
854     private static String getProperty(final String key, final String defaultValue) {
855         return AccessController.doPrivileged(new PrivilegedAction<String>() {
856             @Override
857             public String run() {
858                 return System.getProperty(key, defaultValue);
859             }
860         });
861     }
862 
863     static class IdleConnectionEvictor implements Closeable {
864 
865         private final Thread thread;
866 
867         public IdleConnectionEvictor(final H2ConnPool connPool, final TimeValue maxIdleTime) {
868             this.thread = new DefaultThreadFactory("idle-connection-evictor", true).newThread(new Runnable() {
869                 @Override
870                 public void run() {
871                     try {
872                         while (!Thread.currentThread().isInterrupted()) {
873                             maxIdleTime.sleep();
874                             connPool.closeIdle(maxIdleTime);
875                         }
876                     } catch (final InterruptedException ex) {
877                         Thread.currentThread().interrupt();
878                     } catch (final Exception ex) {
879                     }
880 
881                 }
882             });
883         }
884 
885         public void start() {
886             thread.start();
887         }
888 
889         public void shutdown() {
890             thread.interrupt();
891         }
892 
893         @Override
894         public void close() throws IOException {
895             shutdown();
896         }
897 
898     }
899 
900 }