blob: 77252dd5ac748c30987bd23fdd446630c9825f13 [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.classic;
import java.io.Closeable;
import java.io.IOException;
import java.net.ProxySelector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.hc.client5.http.AuthenticationStrategy;
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.UserTokenHandler;
import org.apache.hc.client5.http.auth.AuthSchemeFactory;
import org.apache.hc.client5.http.auth.StandardAuthScheme;
import org.apache.hc.client5.http.auth.CredentialsProvider;
import org.apache.hc.client5.http.classic.BackoffManager;
import org.apache.hc.client5.http.classic.ConnectionBackoffStrategy;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.CookieSpecFactory;
import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.entity.InputStreamFactory;
import org.apache.hc.client5.http.impl.ChainElement;
import org.apache.hc.client5.http.impl.CookieSpecSupport;
import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.client5.http.impl.DefaultUserTokenHandler;
import org.apache.hc.client5.http.impl.IdleConnectionEvictor;
import org.apache.hc.client5.http.impl.NoopUserTokenHandler;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory;
import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory;
import org.apache.hc.client5.http.impl.auth.SPNegoSchemeFactory;
import org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.RedirectStrategy;
import org.apache.hc.client5.http.protocol.RequestAddCookies;
import org.apache.hc.client5.http.protocol.RequestAuthCache;
import org.apache.hc.client5.http.protocol.RequestClientConnControl;
import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
import org.apache.hc.client5.http.protocol.RequestExpectContinue;
import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.http.ConnectionReuseStrategy;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpResponseInterceptor;
import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.NamedElementChain;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.protocol.HttpProcessor;
import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
import org.apache.hc.core5.http.protocol.RequestContent;
import org.apache.hc.core5.http.protocol.RequestTargetHost;
import org.apache.hc.core5.http.protocol.RequestUserAgent;
import org.apache.hc.core5.pool.ConnPoolControl;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.apache.hc.core5.util.VersionInfo;
/**
* Builder for {@link CloseableHttpClient} instances.
* <p>
* When a particular component is not explicitly set this class will
* use its default implementation. System properties will be taken
* into account when configuring the default implementations when
* {@link #useSystemProperties()} method is called prior to calling
* {@link #build()}.
* </p>
* <ul>
* <li>http.proxyHost</li>
* <li>http.proxyPort</li>
* <li>https.proxyHost</li>
* <li>https.proxyPort</li>
* <li>http.nonProxyHosts</li>
* <li>https.proxyUser</li>
* <li>http.proxyUser</li>
* <li>https.proxyPassword</li>
* <li>http.proxyPassword</li>
* <li>http.keepAlive</li>
* <li>http.agent</li>
* </ul>
* <p>
* Please note that some settings used by this class can be mutually
* exclusive and may not apply when building {@link CloseableHttpClient}
* instances.
* </p>
*
* @since 4.3
*/
public class HttpClientBuilder {
private static class RequestInterceptorEntry {
enum Position { FIRST, LAST }
final RequestInterceptorEntry.Position position;
final HttpRequestInterceptor interceptor;
private RequestInterceptorEntry(final RequestInterceptorEntry.Position position, final HttpRequestInterceptor interceptor) {
this.position = position;
this.interceptor = interceptor;
}
}
private static class ResponseInterceptorEntry {
enum Position { FIRST, LAST }
final ResponseInterceptorEntry.Position position;
final HttpResponseInterceptor interceptor;
private ResponseInterceptorEntry(final ResponseInterceptorEntry.Position position, final HttpResponseInterceptor interceptor) {
this.position = position;
this.interceptor = interceptor;
}
}
private static class ExecInterceptorEntry {
enum Position { BEFORE, AFTER, REPLACE, FIRST, LAST }
final ExecInterceptorEntry.Position position;
final String name;
final ExecChainHandler interceptor;
final String existing;
private ExecInterceptorEntry(
final ExecInterceptorEntry.Position position,
final String name,
final ExecChainHandler interceptor,
final String existing) {
this.position = position;
this.name = name;
this.interceptor = interceptor;
this.existing = existing;
}
}
private HttpRequestExecutor requestExec;
private HttpClientConnectionManager connManager;
private boolean connManagerShared;
private SchemePortResolver schemePortResolver;
private ConnectionReuseStrategy reuseStrategy;
private ConnectionKeepAliveStrategy keepAliveStrategy;
private AuthenticationStrategy targetAuthStrategy;
private AuthenticationStrategy proxyAuthStrategy;
private UserTokenHandler userTokenHandler;
private LinkedList<RequestInterceptorEntry> requestInterceptors;
private LinkedList<ResponseInterceptorEntry> responseInterceptors;
private LinkedList<ExecInterceptorEntry> execInterceptors;
private HttpRequestRetryStrategy retryStrategy;
private HttpRoutePlanner routePlanner;
private RedirectStrategy redirectStrategy;
private ConnectionBackoffStrategy connectionBackoffStrategy;
private BackoffManager backoffManager;
private Lookup<AuthSchemeFactory> authSchemeRegistry;
private Lookup<CookieSpecFactory> cookieSpecRegistry;
private LinkedHashMap<String, InputStreamFactory> contentDecoderMap;
private CookieStore cookieStore;
private CredentialsProvider credentialsProvider;
private String userAgent;
private HttpHost proxy;
private Collection<? extends Header> defaultHeaders;
private RequestConfig defaultRequestConfig;
private boolean evictExpiredConnections;
private boolean evictIdleConnections;
private TimeValue maxIdleTime;
private boolean systemProperties;
private boolean redirectHandlingDisabled;
private boolean automaticRetriesDisabled;
private boolean contentCompressionDisabled;
private boolean cookieManagementDisabled;
private boolean authCachingDisabled;
private boolean connectionStateDisabled;
private boolean defaultUserAgentDisabled;
private List<Closeable> closeables;
public static HttpClientBuilder create() {
return new HttpClientBuilder();
}
protected HttpClientBuilder() {
super();
}
/**
* Assigns {@link HttpRequestExecutor} instance.
*/
public final HttpClientBuilder setRequestExecutor(final HttpRequestExecutor requestExec) {
this.requestExec = requestExec;
return this;
}
/**
* Assigns {@link HttpClientConnectionManager} instance.
*/
public final HttpClientBuilder setConnectionManager(
final HttpClientConnectionManager connManager) {
this.connManager = connManager;
return this;
}
/**
* Defines the connection manager is to be shared by multiple
* client instances.
* <p>
* If the connection manager is shared its life-cycle is expected
* to be managed by the caller and it will not be shut down
* if the client is closed.
* </p>
*
* @param shared defines whether or not the connection manager can be shared
* by multiple clients.
*
* @since 4.4
*/
public final HttpClientBuilder setConnectionManagerShared(
final boolean shared) {
this.connManagerShared = shared;
return this;
}
/**
* Assigns {@link ConnectionReuseStrategy} instance.
*/
public final HttpClientBuilder setConnectionReuseStrategy(
final ConnectionReuseStrategy reuseStrategy) {
this.reuseStrategy = reuseStrategy;
return this;
}
/**
* Assigns {@link ConnectionKeepAliveStrategy} instance.
*/
public final HttpClientBuilder setKeepAliveStrategy(
final ConnectionKeepAliveStrategy keepAliveStrategy) {
this.keepAliveStrategy = keepAliveStrategy;
return this;
}
/**
* Assigns {@link AuthenticationStrategy} instance for target
* host authentication.
*/
public final HttpClientBuilder setTargetAuthenticationStrategy(
final AuthenticationStrategy targetAuthStrategy) {
this.targetAuthStrategy = targetAuthStrategy;
return this;
}
/**
* Assigns {@link AuthenticationStrategy} instance for proxy
* authentication.
*/
public final HttpClientBuilder setProxyAuthenticationStrategy(
final AuthenticationStrategy proxyAuthStrategy) {
this.proxyAuthStrategy = proxyAuthStrategy;
return this;
}
/**
* Assigns {@link UserTokenHandler} instance.
* <p>
* Please note this value can be overridden by the {@link #disableConnectionState()}
* method.
* </p>
*/
public final HttpClientBuilder setUserTokenHandler(final UserTokenHandler userTokenHandler) {
this.userTokenHandler = userTokenHandler;
return this;
}
/**
* Disables connection state tracking.
*/
public final HttpClientBuilder disableConnectionState() {
connectionStateDisabled = true;
return this;
}
/**
* Assigns {@link SchemePortResolver} instance.
*/
public final HttpClientBuilder setSchemePortResolver(
final SchemePortResolver schemePortResolver) {
this.schemePortResolver = schemePortResolver;
return this;
}
/**
* Assigns {@code User-Agent} value.
*/
public final HttpClientBuilder setUserAgent(final String userAgent) {
this.userAgent = userAgent;
return this;
}
/**
* Assigns default request header values.
*/
public final HttpClientBuilder setDefaultHeaders(final Collection<? extends Header> defaultHeaders) {
this.defaultHeaders = defaultHeaders;
return this;
}
/**
* Adds this protocol interceptor to the head of the protocol processing list.
*/
public final HttpClientBuilder addResponseInterceptorFirst(final HttpResponseInterceptor interceptor) {
Args.notNull(interceptor, "Interceptor");
if (responseInterceptors == null) {
responseInterceptors = new LinkedList<>();
}
responseInterceptors.add(new ResponseInterceptorEntry(ResponseInterceptorEntry.Position.FIRST, interceptor));
return this;
}
/**
* Adds this protocol interceptor to the tail of the protocol processing list.
*/
public final HttpClientBuilder addResponseInterceptorLast(final HttpResponseInterceptor interceptor) {
Args.notNull(interceptor, "Interceptor");
if (responseInterceptors == null) {
responseInterceptors = new LinkedList<>();
}
responseInterceptors.add(new ResponseInterceptorEntry(ResponseInterceptorEntry.Position.LAST, interceptor));
return this;
}
/**
* Adds this protocol interceptor to the head of the protocol processing list.
*/
public final HttpClientBuilder addRequestInterceptorFirst(final HttpRequestInterceptor interceptor) {
Args.notNull(interceptor, "Interceptor");
if (requestInterceptors == null) {
requestInterceptors = new LinkedList<>();
}
requestInterceptors.add(new RequestInterceptorEntry(RequestInterceptorEntry.Position.FIRST, interceptor));
return this;
}
/**
* Adds this protocol interceptor to the tail of the protocol processing list.
*/
public final HttpClientBuilder addRequestInterceptorLast(final HttpRequestInterceptor interceptor) {
Args.notNull(interceptor, "Interceptor");
if (requestInterceptors == null) {
requestInterceptors = new LinkedList<>();
}
requestInterceptors.add(new RequestInterceptorEntry(RequestInterceptorEntry.Position.LAST, interceptor));
return this;
}
/**
* Adds this execution interceptor before an existing interceptor.
*/
public final HttpClientBuilder addExecInterceptorBefore(final String existing, final String name, final ExecChainHandler interceptor) {
Args.notBlank(existing, "Existing");
Args.notBlank(name, "Name");
Args.notNull(interceptor, "Interceptor");
if (execInterceptors == null) {
execInterceptors = new LinkedList<>();
}
execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.BEFORE, name, interceptor, existing));
return this;
}
/**
* Adds this execution interceptor after interceptor with the given name.
*/
public final HttpClientBuilder addExecInterceptorAfter(final String existing, final String name, final ExecChainHandler interceptor) {
Args.notBlank(existing, "Existing");
Args.notBlank(name, "Name");
Args.notNull(interceptor, "Interceptor");
if (execInterceptors == null) {
execInterceptors = new LinkedList<>();
}
execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.AFTER, name, interceptor, existing));
return this;
}
/**
* Replace an existing interceptor with the given name with new interceptor.
*/
public final HttpClientBuilder replaceExecInterceptor(final String existing, final ExecChainHandler interceptor) {
Args.notBlank(existing, "Existing");
Args.notNull(interceptor, "Interceptor");
if (execInterceptors == null) {
execInterceptors = new LinkedList<>();
}
execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.REPLACE, existing, interceptor, existing));
return this;
}
/**
* Add an interceptor to the head of the processing list.
*/
public final HttpClientBuilder addExecInterceptorFirst(final String name, final ExecChainHandler interceptor) {
Args.notNull(name, "Name");
Args.notNull(interceptor, "Interceptor");
if (execInterceptors == null) {
execInterceptors = new LinkedList<>();
}
execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.FIRST, name, interceptor, null));
return this;
}
/**
* Add an interceptor to the tail of the processing list.
*/
public final HttpClientBuilder addExecInterceptorLast(final String name, final ExecChainHandler interceptor) {
Args.notNull(name, "Name");
Args.notNull(interceptor, "Interceptor");
if (execInterceptors == null) {
execInterceptors = new LinkedList<>();
}
execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Position.LAST, name, interceptor, null));
return this;
}
/**
* Disables state (cookie) management.
*/
public final HttpClientBuilder disableCookieManagement() {
this.cookieManagementDisabled = true;
return this;
}
/**
* Disables automatic content decompression.
*/
public final HttpClientBuilder disableContentCompression() {
contentCompressionDisabled = true;
return this;
}
/**
* Disables authentication scheme caching.
*/
public final HttpClientBuilder disableAuthCaching() {
this.authCachingDisabled = true;
return this;
}
/**
* Assigns {@link HttpRequestRetryStrategy} instance.
* <p>
* Please note this value can be overridden by the {@link #disableAutomaticRetries()}
* method.
*/
public final HttpClientBuilder setRetryStrategy(final HttpRequestRetryStrategy retryStrategy) {
this.retryStrategy = retryStrategy;
return this;
}
/**
* Disables automatic request recovery and re-execution.
*/
public final HttpClientBuilder disableAutomaticRetries() {
automaticRetriesDisabled = true;
return this;
}
/**
* Assigns default proxy value.
* <p>
* Please note this value can be overridden by the {@link #setRoutePlanner(
* org.apache.hc.client5.http.routing.HttpRoutePlanner)} method.
*/
public final HttpClientBuilder setProxy(final HttpHost proxy) {
this.proxy = proxy;
return this;
}
/**
* Assigns {@link HttpRoutePlanner} instance.
*/
public final HttpClientBuilder setRoutePlanner(final HttpRoutePlanner routePlanner) {
this.routePlanner = routePlanner;
return this;
}
/**
* Assigns {@link RedirectStrategy} instance.
* <p>
* Please note this value can be overridden by the {@link #disableRedirectHandling()}
* method.
* </p>
` */
public final HttpClientBuilder setRedirectStrategy(final RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
return this;
}
/**
* Disables automatic redirect handling.
*/
public final HttpClientBuilder disableRedirectHandling() {
redirectHandlingDisabled = true;
return this;
}
/**
* Assigns {@link ConnectionBackoffStrategy} instance.
*/
public final HttpClientBuilder setConnectionBackoffStrategy(
final ConnectionBackoffStrategy connectionBackoffStrategy) {
this.connectionBackoffStrategy = connectionBackoffStrategy;
return this;
}
/**
* Assigns {@link BackoffManager} instance.
*/
public final HttpClientBuilder setBackoffManager(final BackoffManager backoffManager) {
this.backoffManager = backoffManager;
return this;
}
/**
* Assigns default {@link CookieStore} instance which will be used for
* request execution if not explicitly set in the client execution context.
*/
public final HttpClientBuilder setDefaultCookieStore(final CookieStore cookieStore) {
this.cookieStore = cookieStore;
return this;
}
/**
* Assigns default {@link CredentialsProvider} instance which will be used
* for request execution if not explicitly set in the client execution
* context.
*/
public final HttpClientBuilder setDefaultCredentialsProvider(
final CredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
return this;
}
/**
* Assigns default {@link org.apache.hc.client5.http.auth.AuthScheme} registry which will
* be used for request execution if not explicitly set in the client execution
* context.
*/
public final HttpClientBuilder setDefaultAuthSchemeRegistry(
final Lookup<AuthSchemeFactory> authSchemeRegistry) {
this.authSchemeRegistry = authSchemeRegistry;
return this;
}
/**
* Assigns default {@link org.apache.hc.client5.http.cookie.CookieSpec} registry which will
* be used for request execution if not explicitly set in the client execution
* context.
*
* @see CookieSpecSupport
*
*/
public final HttpClientBuilder setDefaultCookieSpecRegistry(
final Lookup<CookieSpecFactory> cookieSpecRegistry) {
this.cookieSpecRegistry = cookieSpecRegistry;
return this;
}
/**
* Assigns a map of {@link org.apache.hc.client5.http.entity.InputStreamFactory}s
* to be used for automatic content decompression.
*/
public final HttpClientBuilder setContentDecoderRegistry(
final LinkedHashMap<String, InputStreamFactory> contentDecoderMap) {
this.contentDecoderMap = contentDecoderMap;
return this;
}
/**
* Assigns default {@link RequestConfig} instance which will be used
* for request execution if not explicitly set in the client execution
* context.
*/
public final HttpClientBuilder setDefaultRequestConfig(final RequestConfig config) {
this.defaultRequestConfig = config;
return this;
}
/**
* Use system properties when creating and configuring default
* implementations.
*/
public final HttpClientBuilder useSystemProperties() {
this.systemProperties = true;
return this;
}
/**
* Makes this instance of HttpClient proactively evict expired connections from the
* connection pool using a background thread.
* <p>
* One MUST explicitly close HttpClient with {@link CloseableHttpClient#close()} in order
* to stop and release the background thread.
* <p>
* Please note this method has no effect if the instance of HttpClient is configured to
* use a shared connection manager.
*
* @see #setConnectionManagerShared(boolean)
* @see ConnPoolControl#closeExpired()
*
* @since 4.4
*/
public final HttpClientBuilder evictExpiredConnections() {
evictExpiredConnections = true;
return this;
}
/**
* Makes this instance of HttpClient proactively evict idle connections from the
* connection pool using a background thread.
* <p>
* One MUST explicitly close HttpClient with {@link CloseableHttpClient#close()} in order
* to stop and release the background thread.
* <p>
* Please note this method has no effect if the instance of HttpClient is configured to
* use a shared connection manager.
*
* @see #setConnectionManagerShared(boolean)
* @see ConnPoolControl#closeIdle(TimeValue)
*
* @param maxIdleTime maximum time persistent connections can stay idle while kept alive
* in the connection pool. Connections whose inactivity period exceeds this value will
* get closed and evicted from the pool.
*
* @since 4.4
*/
public final HttpClientBuilder evictIdleConnections(final TimeValue maxIdleTime) {
this.evictIdleConnections = true;
this.maxIdleTime = maxIdleTime;
return this;
}
/**
* Disables the default user agent set by this builder if none has been provided by the user.
*
* @since 4.5.7
*/
public final HttpClientBuilder disableDefaultUserAgent() {
this.defaultUserAgentDisabled = true;
return this;
}
/**
* Request exec chain customization and extension.
* <p>
* For internal use.
*/
@Internal
protected void customizeExecChain(final NamedElementChain<ExecChainHandler> execChainDefinition) {
}
/**
* Adds to the list of {@link Closeable} resources to be managed by the client.
* <p>
* For internal use.
*/
@Internal
protected void addCloseable(final Closeable closeable) {
if (closeable == null) {
return;
}
if (closeables == null) {
closeables = new ArrayList<>();
}
closeables.add(closeable);
}
public CloseableHttpClient build() {
// Create main request executor
// We copy the instance fields to avoid changing them, and rename to avoid accidental use of the wrong version
HttpRequestExecutor requestExecCopy = this.requestExec;
if (requestExecCopy == null) {
requestExecCopy = new HttpRequestExecutor();
}
HttpClientConnectionManager connManagerCopy = this.connManager;
if (connManagerCopy == null) {
connManagerCopy = PoolingHttpClientConnectionManagerBuilder.create().build();
}
ConnectionReuseStrategy reuseStrategyCopy = this.reuseStrategy;
if (reuseStrategyCopy == null) {
if (systemProperties) {
final String s = System.getProperty("http.keepAlive", "true");
if ("true".equalsIgnoreCase(s)) {
reuseStrategyCopy = DefaultConnectionReuseStrategy.INSTANCE;
} else {
reuseStrategyCopy = new ConnectionReuseStrategy() {
@Override
public boolean keepAlive(
final HttpRequest request, final HttpResponse response, final HttpContext context) {
return false;
}
};
}
} else {
reuseStrategyCopy = DefaultConnectionReuseStrategy.INSTANCE;
}
}
ConnectionKeepAliveStrategy keepAliveStrategyCopy = this.keepAliveStrategy;
if (keepAliveStrategyCopy == null) {
keepAliveStrategyCopy = DefaultConnectionKeepAliveStrategy.INSTANCE;
}
AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy;
if (targetAuthStrategyCopy == null) {
targetAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
}
AuthenticationStrategy proxyAuthStrategyCopy = this.proxyAuthStrategy;
if (proxyAuthStrategyCopy == null) {
proxyAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
}
UserTokenHandler userTokenHandlerCopy = this.userTokenHandler;
if (userTokenHandlerCopy == null) {
if (!connectionStateDisabled) {
userTokenHandlerCopy = DefaultUserTokenHandler.INSTANCE;
} else {
userTokenHandlerCopy = NoopUserTokenHandler.INSTANCE;
}
}
String userAgentCopy = this.userAgent;
if (userAgentCopy == null) {
if (systemProperties) {
userAgentCopy = System.getProperty("http.agent");
}
if (userAgentCopy == null && !defaultUserAgentDisabled) {
userAgentCopy = VersionInfo.getSoftwareInfo("Apache-HttpClient",
"org.apache.hc.client5", getClass());
}
}
final NamedElementChain<ExecChainHandler> execChainDefinition = new NamedElementChain<>();
execChainDefinition.addLast(
new MainClientExec(connManagerCopy, reuseStrategyCopy, keepAliveStrategyCopy, userTokenHandlerCopy),
ChainElement.MAIN_TRANSPORT.name());
execChainDefinition.addFirst(
new ConnectExec(
reuseStrategyCopy,
new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
proxyAuthStrategyCopy),
ChainElement.CONNECT.name());
final HttpProcessorBuilder b = HttpProcessorBuilder.create();
if (requestInterceptors != null) {
for (final RequestInterceptorEntry entry: requestInterceptors) {
if (entry.position == RequestInterceptorEntry.Position.FIRST) {
b.addFirst(entry.interceptor);
}
}
}
if (responseInterceptors != null) {
for (final ResponseInterceptorEntry entry: responseInterceptors) {
if (entry.position == ResponseInterceptorEntry.Position.FIRST) {
b.addFirst(entry.interceptor);
}
}
}
b.addAll(
new RequestDefaultHeaders(defaultHeaders),
new RequestContent(),
new RequestTargetHost(),
new RequestClientConnControl(),
new RequestUserAgent(userAgentCopy),
new RequestExpectContinue());
if (!cookieManagementDisabled) {
b.add(new RequestAddCookies());
}
if (!authCachingDisabled) {
b.add(new RequestAuthCache());
}
if (!cookieManagementDisabled) {
b.add(new ResponseProcessCookies());
}
if (requestInterceptors != null) {
for (final RequestInterceptorEntry entry: requestInterceptors) {
if (entry.position == RequestInterceptorEntry.Position.LAST) {
b.addLast(entry.interceptor);
}
}
}
if (responseInterceptors != null) {
for (final ResponseInterceptorEntry entry: responseInterceptors) {
if (entry.position == ResponseInterceptorEntry.Position.LAST) {
b.addLast(entry.interceptor);
}
}
}
final HttpProcessor httpProcessor = b.build();
execChainDefinition.addFirst(
new ProtocolExec(httpProcessor, targetAuthStrategyCopy, proxyAuthStrategyCopy),
ChainElement.PROTOCOL.name());
// Add request retry executor, if not disabled
if (!automaticRetriesDisabled) {
HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;
if (retryStrategyCopy == null) {
retryStrategyCopy = DefaultHttpRequestRetryStrategy.INSTANCE;
}
execChainDefinition.addFirst(
new HttpRequestRetryExec(retryStrategyCopy),
ChainElement.RETRY.name());
}
HttpRoutePlanner routePlannerCopy = this.routePlanner;
if (routePlannerCopy == null) {
SchemePortResolver schemePortResolverCopy = this.schemePortResolver;
if (schemePortResolverCopy == null) {
schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE;
}
if (proxy != null) {
routePlannerCopy = new DefaultProxyRoutePlanner(proxy, schemePortResolverCopy);
} else if (systemProperties) {
routePlannerCopy = new SystemDefaultRoutePlanner(
schemePortResolverCopy, ProxySelector.getDefault());
} else {
routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
}
}
if (!contentCompressionDisabled) {
if (contentDecoderMap != null) {
final List<String> encodings = new ArrayList<>(contentDecoderMap.keySet());
final RegistryBuilder<InputStreamFactory> b2 = RegistryBuilder.create();
for (final Map.Entry<String, InputStreamFactory> entry: contentDecoderMap.entrySet()) {
b2.register(entry.getKey(), entry.getValue());
}
final Registry<InputStreamFactory> decoderRegistry = b2.build();
execChainDefinition.addFirst(
new ContentCompressionExec(encodings, decoderRegistry, true),
ChainElement.COMPRESS.name());
} else {
execChainDefinition.addFirst(new ContentCompressionExec(true), ChainElement.COMPRESS.name());
}
}
// Add redirect executor, if not disabled
if (!redirectHandlingDisabled) {
RedirectStrategy redirectStrategyCopy = this.redirectStrategy;
if (redirectStrategyCopy == null) {
redirectStrategyCopy = DefaultRedirectStrategy.INSTANCE;
}
execChainDefinition.addFirst(
new RedirectExec(routePlannerCopy, redirectStrategyCopy),
ChainElement.REDIRECT.name());
}
// Optionally, add connection back-off executor
if (this.backoffManager != null && this.connectionBackoffStrategy != null) {
execChainDefinition.addFirst(new BackoffStrategyExec(this.connectionBackoffStrategy, this.backoffManager),
ChainElement.BACK_OFF.name());
}
if (execInterceptors != null) {
for (final ExecInterceptorEntry entry: execInterceptors) {
switch (entry.position) {
case AFTER:
execChainDefinition.addAfter(entry.existing, entry.interceptor, entry.name);
break;
case BEFORE:
execChainDefinition.addBefore(entry.existing, entry.interceptor, entry.name);
break;
case REPLACE:
execChainDefinition.replace(entry.existing, entry.interceptor);
break;
case FIRST:
execChainDefinition.addFirst(entry.interceptor, entry.name);
break;
case LAST:
// Don't add last, after MainClientExec, as that does not delegate to the chain
// Instead, add the interceptor just before it, making it effectively the last interceptor
execChainDefinition.addBefore(ChainElement.MAIN_TRANSPORT.name(), entry.interceptor, entry.name);
break;
}
}
}
customizeExecChain(execChainDefinition);
NamedElementChain<ExecChainHandler>.Node current = execChainDefinition.getLast();
ExecChainElement execChain = null;
while (current != null) {
execChain = new ExecChainElement(current.getValue(), execChain);
current = current.getPrevious();
}
Lookup<AuthSchemeFactory> authSchemeRegistryCopy = this.authSchemeRegistry;
if (authSchemeRegistryCopy == null) {
authSchemeRegistryCopy = RegistryBuilder.<AuthSchemeFactory>create()
.register(StandardAuthScheme.BASIC, BasicSchemeFactory.INSTANCE)
.register(StandardAuthScheme.DIGEST, DigestSchemeFactory.INSTANCE)
.register(StandardAuthScheme.NTLM, NTLMSchemeFactory.INSTANCE)
.register(StandardAuthScheme.SPNEGO, SPNegoSchemeFactory.DEFAULT)
.register(StandardAuthScheme.KERBEROS, KerberosSchemeFactory.DEFAULT)
.build();
}
Lookup<CookieSpecFactory> cookieSpecRegistryCopy = this.cookieSpecRegistry;
if (cookieSpecRegistryCopy == null) {
cookieSpecRegistryCopy = CookieSpecSupport.createDefault();
}
CookieStore defaultCookieStore = this.cookieStore;
if (defaultCookieStore == null) {
defaultCookieStore = new BasicCookieStore();
}
CredentialsProvider defaultCredentialsProvider = this.credentialsProvider;
if (defaultCredentialsProvider == null) {
if (systemProperties) {
defaultCredentialsProvider = new SystemDefaultCredentialsProvider();
} else {
defaultCredentialsProvider = new BasicCredentialsProvider();
}
}
List<Closeable> closeablesCopy = closeables != null ? new ArrayList<>(closeables) : null;
if (!this.connManagerShared) {
if (closeablesCopy == null) {
closeablesCopy = new ArrayList<>(1);
}
if (evictExpiredConnections || evictIdleConnections) {
if (connManagerCopy instanceof ConnPoolControl) {
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor((ConnPoolControl<?>) connManagerCopy,
maxIdleTime, maxIdleTime);
closeablesCopy.add(new Closeable() {
@Override
public void close() throws IOException {
connectionEvictor.shutdown();
try {
connectionEvictor.awaitTermination(Timeout.ofSeconds(1));
} catch (final InterruptedException interrupted) {
Thread.currentThread().interrupt();
}
}
});
connectionEvictor.start();
}
}
closeablesCopy.add(connManagerCopy);
}
return new InternalHttpClient(
connManagerCopy,
requestExecCopy,
execChain,
routePlannerCopy,
cookieSpecRegistryCopy,
authSchemeRegistryCopy,
defaultCookieStore,
defaultCredentialsProvider,
defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT,
closeablesCopy);
}
}