Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ public class ConnectionConfig implements Cloneable {

private static final Timeout DEFAULT_CONNECT_TIMEOUT = Timeout.ofMinutes(3);

/**
* @since 5.6
*/
private static final TimeValue DEFAULT_HE_ATTEMPT_DELAY = TimeValue.ofMilliseconds(250);
/**
* @since 5.6
*/
private static final TimeValue DEFAULT_HE_OTHER_FAMILY_DELAY = TimeValue.ofMilliseconds(50);

public static final ConnectionConfig DEFAULT = new Builder().build();

private final Timeout connectTimeout;
Expand All @@ -52,25 +61,50 @@ public class ConnectionConfig implements Cloneable {
private final TimeValue validateAfterInactivity;
private final TimeValue timeToLive;

/**
* @since 5.6
*/
private final boolean staggeredConnectEnabled;
/**
* @since 5.6
*/
private final TimeValue happyEyeballsAttemptDelay;
/**
* @since 5.6
*/
private final TimeValue happyEyeballsOtherFamilyDelay;
/**
* @since 5.6
*/
private final ProtocolFamilyPreference protocolFamilyPreference;

/**
* Intended for CDI compatibility
*/
protected ConnectionConfig() {
this(DEFAULT_CONNECT_TIMEOUT, null, null, null, null);
this(DEFAULT_CONNECT_TIMEOUT, null, null, null, null, false, DEFAULT_HE_ATTEMPT_DELAY, DEFAULT_HE_OTHER_FAMILY_DELAY, ProtocolFamilyPreference.INTERLEAVE);
}

ConnectionConfig(
final Timeout connectTimeout,
final Timeout socketTimeout,
final Timeout idleTimeout,
final TimeValue validateAfterInactivity,
final TimeValue timeToLive) {
final TimeValue timeToLive,
final boolean staggeredConnectEnabled,
final TimeValue happyEyeballsAttemptDelay,
final TimeValue happyEyeballsOtherFamilyDelay,
final ProtocolFamilyPreference protocolFamilyPreference) {
super();
this.connectTimeout = connectTimeout;
this.socketTimeout = socketTimeout;
this.idleTimeout = idleTimeout;
this.validateAfterInactivity = validateAfterInactivity;
this.timeToLive = timeToLive;
this.staggeredConnectEnabled = staggeredConnectEnabled;
this.happyEyeballsAttemptDelay = happyEyeballsAttemptDelay != null ? happyEyeballsAttemptDelay : DEFAULT_HE_ATTEMPT_DELAY;
this.happyEyeballsOtherFamilyDelay = happyEyeballsOtherFamilyDelay != null ? happyEyeballsOtherFamilyDelay : DEFAULT_HE_OTHER_FAMILY_DELAY;
this.protocolFamilyPreference = protocolFamilyPreference != null ? protocolFamilyPreference : ProtocolFamilyPreference.INTERLEAVE;
}

/**
Expand Down Expand Up @@ -108,6 +142,46 @@ public TimeValue getTimeToLive() {
return timeToLive;
}

/**
* Whether staggered (Happy Eyeballs–style) connection attempts are enabled.
*
* @see Builder#setStaggeredConnectEnabled(boolean)
* @since 5.6
*/
public boolean isStaggeredConnectEnabled() {
return staggeredConnectEnabled;
}

/**
* Delay between subsequent staggered connection attempts.
*
* @see Builder#setHappyEyeballsAttemptDelay(TimeValue)
* @since 5.6
*/
public TimeValue getHappyEyeballsAttemptDelay() {
return happyEyeballsAttemptDelay;
}

/**
* Initial delay before launching the first address of the other protocol family.
*
* @see Builder#setHappyEyeballsOtherFamilyDelay(TimeValue)
* @since 5.6
*/
public TimeValue getHappyEyeballsOtherFamilyDelay() {
return happyEyeballsOtherFamilyDelay;
}

/**
* Protocol family preference controlling address selection and ordering.
*
* @see Builder#setProtocolFamilyPreference(ProtocolFamilyPreference)
* @since 5.6
*/
public ProtocolFamilyPreference getProtocolFamilyPreference() {
return protocolFamilyPreference;
}

@Override
protected ConnectionConfig clone() throws CloneNotSupportedException {
return (ConnectionConfig) super.clone();
Expand All @@ -122,6 +196,10 @@ public String toString() {
builder.append(", idleTimeout=").append(idleTimeout);
builder.append(", validateAfterInactivity=").append(validateAfterInactivity);
builder.append(", timeToLive=").append(timeToLive);
builder.append(", staggeredConnectEnabled=").append(staggeredConnectEnabled);
builder.append(", happyEyeballsAttemptDelay=").append(happyEyeballsAttemptDelay);
builder.append(", happyEyeballsOtherFamilyDelay=").append(happyEyeballsOtherFamilyDelay);
builder.append(", protocolFamilyPreference=").append(protocolFamilyPreference);
builder.append("]");
return builder.toString();
}
Expand All @@ -135,7 +213,11 @@ public static ConnectionConfig.Builder copy(final ConnectionConfig config) {
.setConnectTimeout(config.getConnectTimeout())
.setSocketTimeout(config.getSocketTimeout())
.setValidateAfterInactivity(config.getValidateAfterInactivity())
.setTimeToLive(config.getTimeToLive());
.setTimeToLive(config.getTimeToLive())
.setStaggeredConnectEnabled(config.isStaggeredConnectEnabled())
.setHappyEyeballsAttemptDelay(config.getHappyEyeballsAttemptDelay())
.setHappyEyeballsOtherFamilyDelay(config.getHappyEyeballsOtherFamilyDelay())
.setProtocolFamilyPreference(config.getProtocolFamilyPreference());
}

public static class Builder {
Expand All @@ -146,6 +228,12 @@ public static class Builder {
private TimeValue validateAfterInactivity;
private TimeValue timeToLive;

// New fields (defaults)
private boolean staggeredConnectEnabled = false; // disabled by default
private TimeValue happyEyeballsAttemptDelay = DEFAULT_HE_ATTEMPT_DELAY;
private TimeValue happyEyeballsOtherFamilyDelay = DEFAULT_HE_OTHER_FAMILY_DELAY;
private ProtocolFamilyPreference protocolFamilyPreference = ProtocolFamilyPreference.INTERLEAVE;

Builder() {
super();
this.connectTimeout = DEFAULT_CONNECT_TIMEOUT;
Expand Down Expand Up @@ -281,13 +369,63 @@ public Builder setTimeToLive(final long timeToLive, final TimeUnit timeUnit) {
return this;
}

/**
* Enables or disables staggered (Happy Eyeballs–style) connection attempts.
*
* @since 5.6
* @return this instance.
*/
public Builder setStaggeredConnectEnabled(final boolean enabled) {
this.staggeredConnectEnabled = enabled;
return this;
}

/**
* Sets the delay between staggered connection attempts.
*
* @since 5.6
* @return this instance.
*/
public Builder setHappyEyeballsAttemptDelay(final TimeValue delay) {
this.happyEyeballsAttemptDelay = delay;
return this;
}

/**
* Sets the initial delay before launching the first address of the other
* protocol family (IPv6 vs IPv4) when interleaving attempts.
*
* @since 5.6
* @return this instance.
*/
public Builder setHappyEyeballsOtherFamilyDelay(final TimeValue delay) {
this.happyEyeballsOtherFamilyDelay = delay;
return this;
}

/**
* Sets the protocol family preference that guides address selection and ordering.
*
* @since 5.6
* @return this instance.
*/
public Builder setProtocolFamilyPreference(final ProtocolFamilyPreference preference) {
this.protocolFamilyPreference = preference;
return this;
}

public ConnectionConfig build() {
return new ConnectionConfig(
connectTimeout != null ? connectTimeout : DEFAULT_CONNECT_TIMEOUT,
socketTimeout,
idleTimeout,
validateAfterInactivity,
timeToLive);
timeToLive,
staggeredConnectEnabled,
happyEyeballsAttemptDelay != null ? happyEyeballsAttemptDelay : DEFAULT_HE_ATTEMPT_DELAY,
happyEyeballsOtherFamilyDelay != null ? happyEyeballsOtherFamilyDelay : DEFAULT_HE_OTHER_FAMILY_DELAY,
protocolFamilyPreference != null ? protocolFamilyPreference : ProtocolFamilyPreference.INTERLEAVE
);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* ====================================================================
* 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.config;

/**
* Protocol family preference for outbound connections.
*
* <p>Used by connection initiation code to filter or order destination
* addresses and, when enabled, to interleave families during staggered attempts.
*
* @since 5.6
*/
public enum ProtocolFamilyPreference {
/** Keep families as returned (or RFC 6724 ordered). */
DEFAULT,
/**
* Prefer IPv4 addresses but allow IPv6 as a fallback.
*/
PREFER_IPV4,

/**
* Prefer IPv6 addresses but allow IPv4 as a fallback.
*/
PREFER_IPV6,

/**
* Use only IPv4 addresses.
*/
IPV4_ONLY,

/**
* Use only IPv6 addresses.
*/
IPV6_ONLY,

/**
* Interleave address families (v6, then v4, then v6, …) when multiple
* addresses are available. When staggered connects are enabled, the first
* address of the other family is delayed by a small offset.
*/
INTERLEAVE
}

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.UnsupportedSchemeException;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.ConnPoolSupport;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
Expand Down Expand Up @@ -71,21 +72,14 @@ public class DefaultAsyncClientConnectionOperator implements AsyncClientConnecti
private final MultihomeIOSessionRequester sessionRequester;
private final Lookup<TlsStrategy> tlsStrategyLookup;

/**
* Constructs a new {@code DefaultAsyncClientConnectionOperator}.
*
* <p><strong>Note:</strong> this class is marked {@code @Internal}; rely on it
* only if you are prepared for incompatible changes in a future major
* release. Typical client code should use the high-level builders in
* {@code HttpAsyncClients} instead.</p>
*/
protected DefaultAsyncClientConnectionOperator(
DefaultAsyncClientConnectionOperator(
final Lookup<TlsStrategy> tlsStrategyLookup,
final SchemePortResolver schemePortResolver,
final DnsResolver dnsResolver) {
final DnsResolver dnsResolver,
final ConnectionConfig defaultConnectionConfig) {
this.tlsStrategyLookup = Args.notNull(tlsStrategyLookup, "TLS strategy lookup");
this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
this.sessionRequester = new MultihomeIOSessionRequester(dnsResolver);
this.sessionRequester = new MultihomeIOSessionRequester(dnsResolver, defaultConnectionConfig);
}

@Override
Expand Down Expand Up @@ -279,4 +273,8 @@ protected void onBeforeTlsHandshake(final HttpContext httpContext, final HttpHos
protected void onAfterTlsHandshake(final HttpContext httpContext, final HttpHost endpointHost) {
}

public void shutdown() {
sessionRequester.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import java.util.concurrent.Future;

import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.concurrent.FutureCallback;
Expand All @@ -54,8 +56,19 @@ public final class MultihomeConnectionInitiator implements ConnectionInitiator {
public MultihomeConnectionInitiator(
final ConnectionInitiator connectionInitiator,
final DnsResolver dnsResolver) {
this(connectionInitiator, dnsResolver, null);
}

/**
* @since 5.6
*/
public MultihomeConnectionInitiator(
final ConnectionInitiator connectionInitiator,
final DnsResolver dnsResolver,
final ConnectionConfig connectionConfig) {
this.connectionInitiator = Args.notNull(connectionInitiator, "Connection initiator");
this.sessionRequester = new MultihomeIOSessionRequester(dnsResolver);
final DnsResolver effectiveResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE;
this.sessionRequester = new MultihomeIOSessionRequester(effectiveResolver, connectionConfig);
}

@Override
Expand Down
Loading