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