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