rschmitt commented on code in PR #693: URL: https://github.com/apache/httpcomponents-client/pull/693#discussion_r2268498321
########## httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientHappyEyeballs.java: ########## @@ -0,0 +1,158 @@ +/* + * ==================================================================== + * 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.examples; + +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.SystemDefaultDnsResolver; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; +import org.apache.hc.client5.http.async.methods.SimpleRequestProducer; +import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.client5.http.impl.nio.HappyEyeballsV2AsyncClientConnectionOperator; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.message.StatusLine; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.io.CloseMode; + +/** + * Example demonstrating how to enable <em>Happy Eyeballs v2</em> (RFC 8305) + * in the async client with RFC 6724 address selection and on-demand TLS upgrade. + * + * <p><strong>What this example shows</strong></p> + * <ul> + * <li>Creating a {@link HappyEyeballsV2AsyncClientConnectionOperator} that: + * <ul> + * <li>resolves A/AAAA via {@link SystemDefaultDnsResolver},</li> + * <li>orders candidates per RFC 6724,</li> + * <li>stagger-dials dual-stack targets (≈50 ms opposite-family kick, then 250 ms pacing), and</li> + * <li>cancels outstanding attempts once one connects.</li> + * </ul> + * </li> + * <li>Registering an HTTPS {@link TlsStrategy} and wiring it into the operator so that + * HTTPS routes are upgraded to TLS during connect.</li> + * <li>Executing one HTTP and one HTTPS request to a dual-stack host.</li> + * </ul> + * + * <p><strong>Running</strong></p> + * <ol> + * <li>Ensure logging is configured (e.g., log4j-slf4j) if you want to see + * <code>HEv2: dialing/connected</code> debug lines.</li> + * <li>Run the {@code main}. You should see the HTTP request (often 301 to HTTPS) + * followed by an HTTPS request with a TLS handshake driven by the configured {@link TlsStrategy}.</li> + * </ol> + * + * <p><strong>Knobs you can tweak</strong></p> + * <ul> + * <li>Use the constructor {@code new HappyEyeballsV2AsyncClientConnectionOperator(dns, attemptDelayMillis, scheduler, tlsRegistry)} + * if you want a custom pacing interval or to supply your own scheduler.</li> + * <li>Swap {@link SystemDefaultDnsResolver} for a custom {@code DnsResolver} if needed.</li> + * <li>Customize the {@link ClientTlsStrategyBuilder} to control trust material, SNI, ALPN, etc.</li> + * </ul> + * + * <p><strong>References</strong></p> + * <ul> + * <li>RFC 8305: Happy Eyeballs Version 2</li> + * <li>RFC 6724: Default Address Selection for IPv6</li> + * </ul> + * + * @since 5.6 + */ +public final class AsyncClientHappyEyeballs { Review Comment: Remember, I added a runner for these: ``` $ ./run-example.sh AsyncClientHappyEyeballs Executing request GET http://ipv6-test.com/ GET http://ipv6-test.com/->HTTP/1.1 400 Bad Request Executing request GET https://ipv6-test.com/ SimpleBody{content length=442, content type=text/html; charset=iso-8859-1} GET https://ipv6-test.com/->HTTP/1.1 400 Bad Request SimpleBody{content length=442, content type=text/html; charset=iso-8859-1} Shutting down ``` The runner can pass in command line arguments, so it'd be nice if this class could take a URI as an argument, e.g.: ``` $ ./run-example.sh AsyncClientHappyEyeballs http://neverssl.com/ ``` ########## httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/HappyEyeballsV2AsyncClientConnectionOperator.java: ########## @@ -0,0 +1,821 @@ +/* + * ==================================================================== + * 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.nio; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.DatagramSocket; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.hc.client5.http.DnsResolver; +import org.apache.hc.client5.http.nio.AsyncClientConnectionOperator; +import org.apache.hc.client5.http.nio.ManagedAsyncClientConnection; +import org.apache.hc.core5.annotation.Experimental; +import org.apache.hc.core5.annotation.Internal; +import org.apache.hc.core5.concurrent.BasicFuture; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.config.Lookup; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.io.ModalCloseable; +import org.apache.hc.core5.net.NamedEndpoint; +import org.apache.hc.core5.reactor.ConnectionInitiator; +import org.apache.hc.core5.reactor.IOSession; +import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer; +import org.apache.hc.core5.util.Args; +import org.apache.hc.core5.util.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@code HappyEyeballsV2AsyncClientConnectionOperator} is an + * {@link AsyncClientConnectionOperator} implementation that applies the + * <em>Happy Eyeballs Version 2</em> algorithm (RFC 8305) to + * outbound connections. + * + * <p>The operator + * <ol> + * <li>resolves <em>A</em> and <em>AAAA</em> records via the provided + * {@link DnsResolver},</li> + * <li>orders the resulting addresses according to the RFC 6724 + * default-address-selection rules,</li> + * <li>initiates a first connection attempt immediately, launches the + * first address of the <strong>other family</strong> after + * ≈ 50 ms, and then paces subsequent attempts every 250 ms + * (configurable),</li> + * <li>cancels all remaining attempts once one socket connects, and</li> + * <li>reports the <em>last</em> exception if every attempt fails.</li> + * </ol> + * + * <h3>Usage</h3> + * <pre>{@code + * DnsResolver dns = SystemDefaultDnsResolver.INSTANCE; + * AsyncClientConnectionOperator he = + * new HappyEyeballsV2AsyncClientConnectionOperator(dns, tlsRegistry); + * + * PoolingAsyncClientConnectionManager cm = + * PoolingAsyncClientConnectionManagerBuilder.create() + * .setConnectionOperator(he) + * .build(); + * }</pre> + * + * <h3>Thread-safety</h3> + * All instances are immutable and therefore thread-safe; the internal + * {@link ScheduledExecutorService} is created with a single + * daemon thread. + * + * <h3>Limitations / TODO</h3> + * Rules 3, 4 and 7 of RFC 6724 (deprecated addresses, home vs + * care-of, native transport) are <strong>not yet implemented</strong>. + * Their placeholders are marked with TODO comments. + * + * @see <a href="https://www.rfc-editor.org/rfc/rfc8305">RFC 8305</a> + * @see <a href="https://www.rfc-editor.org/rfc/rfc6724">RFC 6724</a> + * @since 5.6 + */ +@Experimental +public final class HappyEyeballsV2AsyncClientConnectionOperator + implements AsyncClientConnectionOperator, ModalCloseable { Review Comment: I think that `AsyncClientConnectionOperator` is too high-level for this. There's unrelated stuff in here like `upgrade`, for one thing. For another, there's an interface called `ConnectionInitiator` that seems like it would be a really good fit for an algorithm that implements staggered, concurrent, asynchronous connection establishment. In fact, one thing we can consider is teaching the existing `MultihomeConnectionInitiator`/`MultihomeIOSessionRequester` to perform concurrent connection attempts, rather than sequential ones. That would provide the most important and most difficult part of RFC 8305, and it would work with any endpoint with multiple IP addresses, irrespective of IPv4/IPv6 configuration. ########## httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientHappyEyeballs.java: ########## @@ -0,0 +1,158 @@ +/* + * ==================================================================== + * 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.examples; + +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.SystemDefaultDnsResolver; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; +import org.apache.hc.client5.http.async.methods.SimpleRequestProducer; +import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.client5.http.impl.nio.HappyEyeballsV2AsyncClientConnectionOperator; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.message.StatusLine; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.io.CloseMode; + +/** + * Example demonstrating how to enable <em>Happy Eyeballs v2</em> (RFC 8305) + * in the async client with RFC 6724 address selection and on-demand TLS upgrade. + * + * <p><strong>What this example shows</strong></p> + * <ul> + * <li>Creating a {@link HappyEyeballsV2AsyncClientConnectionOperator} that: + * <ul> + * <li>resolves A/AAAA via {@link SystemDefaultDnsResolver},</li> + * <li>orders candidates per RFC 6724,</li> + * <li>stagger-dials dual-stack targets (≈50 ms opposite-family kick, then 250 ms pacing), and</li> + * <li>cancels outstanding attempts once one connects.</li> + * </ul> + * </li> + * <li>Registering an HTTPS {@link TlsStrategy} and wiring it into the operator so that + * HTTPS routes are upgraded to TLS during connect.</li> + * <li>Executing one HTTP and one HTTPS request to a dual-stack host.</li> + * </ul> + * + * <p><strong>Running</strong></p> + * <ol> + * <li>Ensure logging is configured (e.g., log4j-slf4j) if you want to see + * <code>HEv2: dialing/connected</code> debug lines.</li> + * <li>Run the {@code main}. You should see the HTTP request (often 301 to HTTPS) + * followed by an HTTPS request with a TLS handshake driven by the configured {@link TlsStrategy}.</li> + * </ol> + * + * <p><strong>Knobs you can tweak</strong></p> + * <ul> + * <li>Use the constructor {@code new HappyEyeballsV2AsyncClientConnectionOperator(dns, attemptDelayMillis, scheduler, tlsRegistry)} + * if you want a custom pacing interval or to supply your own scheduler.</li> + * <li>Swap {@link SystemDefaultDnsResolver} for a custom {@code DnsResolver} if needed.</li> + * <li>Customize the {@link ClientTlsStrategyBuilder} to control trust material, SNI, ALPN, etc.</li> + * </ul> + * + * <p><strong>References</strong></p> + * <ul> + * <li>RFC 8305: Happy Eyeballs Version 2</li> + * <li>RFC 6724: Default Address Selection for IPv6</li> + * </ul> + * + * @since 5.6 + */ +public final class AsyncClientHappyEyeballs { + + public static void main(final String[] args) throws Exception { + final SystemDefaultDnsResolver dnsResolver = new SystemDefaultDnsResolver(); + + final TlsStrategy tls = ClientTlsStrategyBuilder.create() + .useSystemProperties() + .build(); + + final Registry<TlsStrategy> tlsRegistry = RegistryBuilder.<TlsStrategy>create() + .register(URIScheme.HTTPS.id, tls) + .build(); + + final HappyEyeballsV2AsyncClientConnectionOperator operator = + new HappyEyeballsV2AsyncClientConnectionOperator(dnsResolver, tlsRegistry); + + final PoolingAsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() + .setConnectionOperator(operator) + .build(); + + final CloseableHttpAsyncClient client = HttpAsyncClients.custom() + .setConnectionManager(cm) + .build(); + + client.start(); + + final String[] schemes = {URIScheme.HTTP.id, URIScheme.HTTPS.id}; + for (final String scheme : schemes) { + final SimpleHttpRequest request = SimpleRequestBuilder.get() + .setHttpHost(new HttpHost(scheme, "ipv6-test.com")) + .setPath("/") + .build(); + + System.out.println("Executing request " + request); + final Future<SimpleHttpResponse> future = client.execute( + SimpleRequestProducer.create(request), + SimpleResponseConsumer.create(), + new FutureCallback<SimpleHttpResponse>() { + + @Override + public void completed(final SimpleHttpResponse response) { Review Comment: Do we have access to the resolved IP address from here? It'd be nice to print that -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: dev-unsubscr...@hc.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@hc.apache.org For additional commands, e-mail: dev-h...@hc.apache.org