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