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