blob: 56f2265d25f0ebcc22018da17ca82bc00ff010bd [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.async;
import java.io.Closeable;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import org.apache.hc.client5.http.AuthenticationStrategy;
import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.async.AsyncExecChainHandler;
import org.apache.hc.client5.http.auth.AuthSchemeFactory;
import org.apache.hc.client5.http.auth.CredentialsProvider;
import org.apache.hc.client5.http.auth.StandardAuthScheme;
import org.apache.hc.client5.http.config.ConnectionConfig;
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.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.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
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.BearerSchemeFactory;
import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
import org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider;
import org.apache.hc.client5.http.impl.nio.MultihomeConnectionInitiator;
import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
import org.apache.hc.client5.http.protocol.RedirectStrategy;
import org.apache.hc.client5.http.protocol.RequestAddCookies;
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.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.concurrent.DefaultThreadFactory;
import org.apache.hc.core5.function.Callback;
import org.apache.hc.core5.function.Decorator;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.HttpResponseInterceptor;
import org.apache.hc.core5.http.config.CharCodingConfig;
import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.NamedElementChain;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.nio.command.ShutdownCommand;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
import org.apache.hc.core5.http.protocol.HttpProcessor;
import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
import org.apache.hc.core5.http.protocol.RequestTargetHost;
import org.apache.hc.core5.http.protocol.RequestUserAgent;
import org.apache.hc.core5.http2.config.H2Config;
import org.apache.hc.core5.http2.protocol.H2RequestConnControl;
import org.apache.hc.core5.http2.protocol.H2RequestContent;
import org.apache.hc.core5.http2.protocol.H2RequestTargetHost;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.reactor.Command;
import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
import org.apache.hc.core5.reactor.IOEventHandlerFactory;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.reactor.IOSession;
import org.apache.hc.core5.reactor.IOSessionListener;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.VersionInfo;
/**
* Builder for HTTP/2 only {@link CloseableHttpAsyncClient} instances.
* <p>
* Concurrent message exchanges with the same connection route executed
* with these {@link CloseableHttpAsyncClient} instances will get
* automatically multiplexed over a single physical HTTP/2 connection.
* </p>
* <p>
* When a particular component is not explicitly set this class will
* use its default implementation.
* <p>
*
* @since 5.0
*/
public class H2AsyncClientBuilder {
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 AsyncExecChainHandler interceptor;
final String existing;
private ExecInterceptorEntry(
final ExecInterceptorEntry.Position position,
final String name,
final AsyncExecChainHandler interceptor,
final String existing) {
this.position = position;
this.name = name;
this.interceptor = interceptor;
this.existing = existing;
}
}
private IOReactorConfig ioReactorConfig;
private IOSessionListener ioSessionListener;
private H2Config h2Config;
private CharCodingConfig charCodingConfig;
private SchemePortResolver schemePortResolver;
private AuthenticationStrategy targetAuthStrategy;
private AuthenticationStrategy proxyAuthStrategy;
private LinkedList<RequestInterceptorEntry> requestInterceptors;
private LinkedList<ResponseInterceptorEntry> responseInterceptors;
private LinkedList<ExecInterceptorEntry> execInterceptors;
private HttpRoutePlanner routePlanner;
private RedirectStrategy redirectStrategy;
private HttpRequestRetryStrategy retryStrategy;
private Lookup<AuthSchemeFactory> authSchemeRegistry;
private Lookup<CookieSpecFactory> cookieSpecRegistry;
private CookieStore cookieStore;
private CredentialsProvider credentialsProvider;
private String userAgent;
private Collection<? extends Header> defaultHeaders;
private RequestConfig defaultRequestConfig;
private Resolver<HttpHost, ConnectionConfig> connectionConfigResolver;
private boolean evictIdleConnections;
private TimeValue maxIdleTime;
private boolean systemProperties;
private boolean automaticRetriesDisabled;
private boolean redirectHandlingDisabled;
private boolean cookieManagementDisabled;
private boolean authCachingDisabled;
private DnsResolver dnsResolver;
private TlsStrategy tlsStrategy;
private ThreadFactory threadFactory;
private List<Closeable> closeables;
private Callback<Exception> ioReactorExceptionCallback;
private Decorator<IOSession> ioSessionDecorator;
public static H2AsyncClientBuilder create() {
return new H2AsyncClientBuilder();
}
protected H2AsyncClientBuilder() {
super();
}
/**
* Sets {@link H2Config} configuration.
*/
public final H2AsyncClientBuilder setH2Config(final H2Config h2Config) {
this.h2Config = h2Config;
return this;
}
/**
* Sets {@link IOReactorConfig} configuration.
*/
public final H2AsyncClientBuilder setIOReactorConfig(final IOReactorConfig ioReactorConfig) {
this.ioReactorConfig = ioReactorConfig;
return this;
}
/**
* Sets {@link IOSessionListener} listener.
*
* @since 5.2
*/
public final H2AsyncClientBuilder setIOSessionListener(final IOSessionListener ioSessionListener) {
this.ioSessionListener = ioSessionListener;
return this;
}
/**
* Sets {@link CharCodingConfig} configuration.
*/
public final H2AsyncClientBuilder setCharCodingConfig(final CharCodingConfig charCodingConfig) {
this.charCodingConfig = charCodingConfig;
return this;
}
/**
* Assigns {@link AuthenticationStrategy} instance for target
* host authentication.
*/
public final H2AsyncClientBuilder setTargetAuthenticationStrategy(
final AuthenticationStrategy targetAuthStrategy) {
this.targetAuthStrategy = targetAuthStrategy;
return this;
}
/**
* Assigns {@link AuthenticationStrategy} instance for proxy
* authentication.
*/
public final H2AsyncClientBuilder setProxyAuthenticationStrategy(
final AuthenticationStrategy proxyAuthStrategy) {
this.proxyAuthStrategy = proxyAuthStrategy;
return this;
}
/**
* Sets the callback that will be invoked when the client's IOReactor encounters an uncaught exception.
*
* @since 5.2
*/
public final H2AsyncClientBuilder setIoReactorExceptionCallback(final Callback<Exception> ioReactorExceptionCallback) {
this.ioReactorExceptionCallback = ioReactorExceptionCallback;
return this;
}
/**
* Sets the {@link IOSession} {@link Decorator} that will be use with the client's IOReactor.
*
* @since 5.2
*/
public final H2AsyncClientBuilder setIoSessionDecorator(final Decorator<IOSession> ioSessionDecorator) {
this.ioSessionDecorator = ioSessionDecorator;
return this;
}
/**
* Adds this protocol interceptor to the head of the protocol processing list.
*/
public final H2AsyncClientBuilder 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 H2AsyncClientBuilder 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 execution interceptor before an existing interceptor.
*/
public final H2AsyncClientBuilder addExecInterceptorBefore(final String existing, final String name, final AsyncExecChainHandler 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 H2AsyncClientBuilder addExecInterceptorAfter(final String existing, final String name, final AsyncExecChainHandler 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 H2AsyncClientBuilder replaceExecInterceptor(final String existing, final AsyncExecChainHandler 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 H2AsyncClientBuilder addExecInterceptorFirst(final String name, final AsyncExecChainHandler 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 H2AsyncClientBuilder addExecInterceptorLast(final String name, final AsyncExecChainHandler 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;
}
/**
* Adds this protocol interceptor to the head of the protocol processing list.
*/
public final H2AsyncClientBuilder 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 H2AsyncClientBuilder addRequestInterceptorLast(final HttpRequestInterceptor interceptor) {
Args.notNull(interceptor, "Interceptor");
if (requestInterceptors == null) {
requestInterceptors = new LinkedList<>();
}
requestInterceptors.add(new RequestInterceptorEntry(RequestInterceptorEntry.Position.LAST, interceptor));
return this;
}
/**
* Assigns {@link HttpRequestRetryStrategy} instance.
* <p>
* Please note this value can be overridden by the {@link #disableAutomaticRetries()}
* method.
*/
public final H2AsyncClientBuilder setRetryStrategy(final HttpRequestRetryStrategy retryStrategy) {
this.retryStrategy = retryStrategy;
return this;
}
/**
* Assigns {@link RedirectStrategy} instance.
* <p>
* Please note this value can be overridden by the {@link #disableRedirectHandling()}
* method.
* </p>
*/
public H2AsyncClientBuilder setRedirectStrategy(final RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
return this;
}
/**
* Assigns {@link SchemePortResolver} instance.
*/
public final H2AsyncClientBuilder setSchemePortResolver(final SchemePortResolver schemePortResolver) {
this.schemePortResolver = schemePortResolver;
return this;
}
/**
* Assigns {@link DnsResolver} instance.
*/
public final H2AsyncClientBuilder setDnsResolver(final DnsResolver dnsResolver) {
this.dnsResolver = dnsResolver;
return this;
}
/**
* Assigns {@link TlsStrategy} instance.
*/
public final H2AsyncClientBuilder setTlsStrategy(final TlsStrategy tlsStrategy) {
this.tlsStrategy = tlsStrategy;
return this;
}
/**
* Assigns {@link ThreadFactory} instance.
*/
public final H2AsyncClientBuilder setThreadFactory(final ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
return this;
}
/**
* Assigns {@code User-Agent} value.
*/
public final H2AsyncClientBuilder setUserAgent(final String userAgent) {
this.userAgent = userAgent;
return this;
}
/**
* Assigns default request header values.
*/
public final H2AsyncClientBuilder setDefaultHeaders(final Collection<? extends Header> defaultHeaders) {
this.defaultHeaders = defaultHeaders;
return this;
}
/**
* Assigns {@link HttpRoutePlanner} instance.
*/
public final H2AsyncClientBuilder setRoutePlanner(final HttpRoutePlanner routePlanner) {
this.routePlanner = routePlanner;
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 H2AsyncClientBuilder 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 H2AsyncClientBuilder 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.
*/
public final H2AsyncClientBuilder setDefaultCookieSpecRegistry(final Lookup<CookieSpecFactory> cookieSpecRegistry) {
this.cookieSpecRegistry = cookieSpecRegistry;
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 H2AsyncClientBuilder setDefaultCookieStore(final CookieStore cookieStore) {
this.cookieStore = cookieStore;
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 H2AsyncClientBuilder setDefaultRequestConfig(final RequestConfig config) {
this.defaultRequestConfig = config;
return this;
}
/**
* Assigns {@link Resolver} for {@link ConnectionConfig} on a per host basis.
*
* @since 5.2
*/
public final H2AsyncClientBuilder setConnectionConfigResolver(final Resolver<HttpHost, ConnectionConfig> connectionConfigResolver) {
this.connectionConfigResolver = connectionConfigResolver;
return this;
}
/**
* Assigns the same {@link ConnectionConfig} for all hosts.
*
* @since 5.2
*/
public final H2AsyncClientBuilder setDefaultConnectionConfig(final ConnectionConfig connectionConfig) {
this.connectionConfigResolver = (host) -> connectionConfig;
return this;
}
/**
* Use system properties when creating and configuring default
* implementations.
*/
public final H2AsyncClientBuilder useSystemProperties() {
this.systemProperties = true;
return this;
}
/**
* Disables automatic redirect handling.
*/
public final H2AsyncClientBuilder disableRedirectHandling() {
redirectHandlingDisabled = true;
return this;
}
/**
* Disables automatic request recovery and re-execution.
*/
public final H2AsyncClientBuilder disableAutomaticRetries() {
automaticRetriesDisabled = true;
return this;
}
/**
* Disables state (cookie) management.
*/
public final H2AsyncClientBuilder disableCookieManagement() {
this.cookieManagementDisabled = true;
return this;
}
/**
* Disables authentication scheme caching.
*/
public final H2AsyncClientBuilder disableAuthCaching() {
this.authCachingDisabled = 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 CloseableHttpAsyncClient#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.
*
* @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.
*/
public final H2AsyncClientBuilder evictIdleConnections(final TimeValue maxIdleTime) {
this.evictIdleConnections = true;
this.maxIdleTime = maxIdleTime;
return this;
}
/**
* Request exec chain customization and extension.
* <p>
* For internal use.
*/
@Internal
protected void customizeExecChain(final NamedElementChain<AsyncExecChainHandler> 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 CloseableHttpAsyncClient build() {
AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy;
if (targetAuthStrategyCopy == null) {
targetAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
}
AuthenticationStrategy proxyAuthStrategyCopy = this.proxyAuthStrategy;
if (proxyAuthStrategyCopy == null) {
proxyAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
}
String userAgentCopy = this.userAgent;
if (userAgentCopy == null) {
if (systemProperties) {
userAgentCopy = getProperty("http.agent", null);
}
if (userAgentCopy == null) {
userAgentCopy = VersionInfo.getSoftwareInfo("Apache-HttpAsyncClient",
"org.apache.hc.client5", getClass());
}
}
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 H2RequestTargetHost(),
new RequestDefaultHeaders(defaultHeaders),
new RequestUserAgent(userAgentCopy),
new RequestExpectContinue(),
new H2RequestContent(),
new H2RequestConnControl());
if (!cookieManagementDisabled) {
b.add(RequestAddCookies.INSTANCE);
}
if (!cookieManagementDisabled) {
b.add(ResponseProcessCookies.INSTANCE);
}
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();
final NamedElementChain<AsyncExecChainHandler> execChainDefinition = new NamedElementChain<>();
execChainDefinition.addLast(
new H2AsyncMainClientExec(httpProcessor),
ChainElement.MAIN_TRANSPORT.name());
execChainDefinition.addFirst(
new AsyncConnectExec(
new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
proxyAuthStrategyCopy,
schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE,
authCachingDisabled),
ChainElement.CONNECT.name());
execChainDefinition.addFirst(
new AsyncProtocolExec(
targetAuthStrategyCopy,
proxyAuthStrategyCopy,
schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE,
authCachingDisabled),
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 AsyncHttpRequestRetryExec(retryStrategyCopy),
ChainElement.RETRY.name());
}
HttpRoutePlanner routePlannerCopy = this.routePlanner;
if (routePlannerCopy == null) {
SchemePortResolver schemePortResolverCopy = this.schemePortResolver;
if (schemePortResolverCopy == null) {
schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE;
}
routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
}
// Add redirect executor, if not disabled
if (!redirectHandlingDisabled) {
RedirectStrategy redirectStrategyCopy = this.redirectStrategy;
if (redirectStrategyCopy == null) {
redirectStrategyCopy = DefaultRedirectStrategy.INSTANCE;
}
execChainDefinition.addFirst(
new AsyncRedirectExec(routePlannerCopy, redirectStrategyCopy),
ChainElement.REDIRECT.name());
}
final AsyncPushConsumerRegistry pushConsumerRegistry = new AsyncPushConsumerRegistry();
final IOEventHandlerFactory ioEventHandlerFactory = new H2AsyncClientProtocolStarter(
HttpProcessorBuilder.create().build(),
(request, context) -> pushConsumerRegistry.get(request),
h2Config != null ? h2Config : H2Config.DEFAULT,
charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT);
final DefaultConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(
ioEventHandlerFactory,
ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT,
threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-dispatch", true),
ioSessionDecorator != null ? ioSessionDecorator : LoggingIOSessionDecorator.INSTANCE,
ioReactorExceptionCallback != null ? ioReactorExceptionCallback : LoggingExceptionCallback.INSTANCE,
ioSessionListener,
ioSession -> ioSession.enqueue(new ShutdownCommand(CloseMode.GRACEFUL), Command.Priority.IMMEDIATE));
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 H2AsyncMainClientExec, 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<AsyncExecChainHandler>.Node current = execChainDefinition.getLast();
AsyncExecChainElement execChain = null;
while (current != null) {
execChain = new AsyncExecChainElement(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.BEARER, BearerSchemeFactory.INSTANCE)
.build();
}
Lookup<CookieSpecFactory> cookieSpecRegistryCopy = this.cookieSpecRegistry;
if (cookieSpecRegistryCopy == null) {
cookieSpecRegistryCopy = CookieSpecSupport.createDefault();
}
CookieStore cookieStoreCopy = this.cookieStore;
if (cookieStoreCopy == null) {
cookieStoreCopy = new BasicCookieStore();
}
CredentialsProvider credentialsProviderCopy = this.credentialsProvider;
if (credentialsProviderCopy == null) {
if (systemProperties) {
credentialsProviderCopy = new SystemDefaultCredentialsProvider();
} else {
credentialsProviderCopy = new BasicCredentialsProvider();
}
}
TlsStrategy tlsStrategyCopy = this.tlsStrategy;
if (tlsStrategyCopy == null) {
if (systemProperties) {
tlsStrategyCopy = DefaultClientTlsStrategy.createSystemDefault();
} else {
tlsStrategyCopy = DefaultClientTlsStrategy.createDefault();
}
}
final MultihomeConnectionInitiator connectionInitiator = new MultihomeConnectionInitiator(ioReactor, dnsResolver);
final InternalH2ConnPool connPool = new InternalH2ConnPool(connectionInitiator, host -> null, tlsStrategyCopy);
connPool.setConnectionConfigResolver(connectionConfigResolver);
List<Closeable> closeablesCopy = closeables != null ? new ArrayList<>(closeables) : null;
if (closeablesCopy == null) {
closeablesCopy = new ArrayList<>(1);
}
if (evictIdleConnections) {
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(connPool,
maxIdleTime != null ? maxIdleTime : TimeValue.ofSeconds(30L));
closeablesCopy.add(connectionEvictor::shutdown);
connectionEvictor.start();
}
closeablesCopy.add(connPool);
return new InternalH2AsyncClient(
ioReactor,
execChain,
pushConsumerRegistry,
threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-main", true),
connPool,
routePlannerCopy,
cookieSpecRegistryCopy,
authSchemeRegistryCopy,
cookieStoreCopy,
credentialsProviderCopy,
defaultRequestConfig,
closeablesCopy);
}
private static String getProperty(final String key, final String defaultValue) {
return AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty(key, defaultValue));
}
static class IdleConnectionEvictor implements Closeable {
private final Thread thread;
public IdleConnectionEvictor(final InternalH2ConnPool connPool, final TimeValue maxIdleTime) {
this.thread = new DefaultThreadFactory("idle-connection-evictor", true).newThread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
maxIdleTime.sleep();
connPool.closeIdle(maxIdleTime);
}
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
} catch (final Exception ex) {
}
});
}
public void start() {
thread.start();
}
public void shutdown() {
thread.interrupt();
}
@Override
public void close() throws IOException {
shutdown();
}
}
}