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