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