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