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