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