diff --git a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HttpClients.java b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HttpClients.java index 337aa8d8f1..8fe6c9bdb3 100644 --- a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HttpClients.java +++ b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HttpClients.java @@ -476,7 +476,9 @@ public static SingleAddressHttpClientBuilder forSingleAddress( mappingServiceDiscoverer(u -> (R) u, "from " + address.getClass().getSimpleName() + " to an " + Object.class.getSimpleName()), - __ -> address); + // Reverse the cast above instead of returning the captured address: the connection + // factory receives the proxy address when a proxy is configured, not the origin address. + r -> (U) r); default: throw new IllegalArgumentException("Unsupported strategy: " + discoveryStrategy); } diff --git a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpProxyTest.java b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpProxyTest.java index 90bca95357..cce502ec5d 100644 --- a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpProxyTest.java +++ b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpProxyTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019, 2021-2022 Apple Inc. and the ServiceTalk project authors + * Copyright © 2019-2026 Apple Inc. and the ServiceTalk project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import io.servicetalk.http.api.HttpResponse; import io.servicetalk.http.api.ProxyConfigBuilder; import io.servicetalk.http.api.SingleAddressHttpClientBuilder; +import io.servicetalk.http.netty.HttpClients.DiscoveryStrategy; import io.servicetalk.http.netty.HttpsProxyTest.TargetAddressCheckConnectionFactoryFilter; import io.servicetalk.transport.api.ClientSslConfigBuilder; import io.servicetalk.transport.api.HostAndPort; @@ -38,9 +39,11 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.stream.Stream; import javax.annotation.Nullable; import static io.servicetalk.concurrent.api.Single.succeeded; +import static io.servicetalk.dns.discovery.netty.DnsServiceDiscoverers.globalARecordsDnsServiceDiscoverer; import static io.servicetalk.http.api.HttpHeaderNames.HOST; import static io.servicetalk.http.api.HttpResponseStatus.OK; import static io.servicetalk.http.api.HttpSerializers.textSerializerUtf8; @@ -119,6 +122,19 @@ void testRequestForSingleAddress(HttpProtocol clientProtocol, HttpProtocol serve testRequest(clientProtocol, serverProtocol, HttpClients::forSingleAddress); } + private static Stream protocolsAndDiscoveryStrategies() { + return protocols().stream().flatMap(protocols -> Stream.of(DiscoveryStrategy.values()) + .map(strategy -> Arguments.of(protocols.get()[0], protocols.get()[1], strategy))); + } + + @ParameterizedTest(name = "[{index}] clientProtocol={0} serverProtocol={1} discoveryStrategy={2}") + @MethodSource("protocolsAndDiscoveryStrategies") + void testRequestForSingleAddressWithServiceDiscoverer(HttpProtocol clientProtocol, HttpProtocol serverProtocol, + DiscoveryStrategy discoveryStrategy) throws Exception { + testRequest(clientProtocol, serverProtocol, address -> + HttpClients.forSingleAddress(globalARecordsDnsServiceDiscoverer(), address, discoveryStrategy)); + } + @ParameterizedTest(name = "[{index}] clientProtocol={0} serverProtocol={1}") @MethodSource("protocols") void testRequestForResolvedAddress(HttpProtocol clientProtocol, HttpProtocol serverProtocol) throws Exception { diff --git a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpsProxyTest.java b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpsProxyTest.java index e9bb3e8df2..af1b051a83 100644 --- a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpsProxyTest.java +++ b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpsProxyTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019-2023 Apple Inc. and the ServiceTalk project authors + * Copyright © 2019-2026 Apple Inc. and the ServiceTalk project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ import io.servicetalk.http.api.ProxyConnectException; import io.servicetalk.http.api.ProxyConnectResponseException; import io.servicetalk.http.api.ReservedBlockingHttpConnection; +import io.servicetalk.http.api.SingleAddressHttpClientBuilder; +import io.servicetalk.http.netty.HttpClients.DiscoveryStrategy; import io.servicetalk.test.resources.DefaultTestCerts; import io.servicetalk.transport.api.ClientSslConfigBuilder; import io.servicetalk.transport.api.ConnectionObserver; @@ -66,6 +68,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Stream; import javax.annotation.Nullable; import javax.net.ssl.KeyManagerFactory; @@ -76,6 +79,7 @@ import static io.servicetalk.concurrent.api.Single.succeeded; import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; +import static io.servicetalk.dns.discovery.netty.DnsServiceDiscoverers.globalARecordsDnsServiceDiscoverer; import static io.servicetalk.http.api.HttpContextKeys.HTTP_TARGET_ADDRESS_BEHIND_PROXY; import static io.servicetalk.http.api.HttpHeaderNames.CONNECTION; import static io.servicetalk.http.api.HttpHeaderNames.CONTENT_LENGTH; @@ -164,13 +168,22 @@ void setUp(List protocols, boolean failHandshake, void setUp(List protocols, boolean failHandshake, boolean failProxyHandshake, Consumer connectRequestHeadersInitializer, boolean proxyTls) throws Exception { + setUp(protocols, failHandshake, failProxyHandshake, connectRequestHeadersInitializer, proxyTls, + address -> BuilderUtils.newClientBuilder(address, CLIENT_CTX)); + } + + void setUp(List protocols, boolean failHandshake, boolean failProxyHandshake, + Consumer connectRequestHeadersInitializer, boolean proxyTls, + Function> + clientBuilderFactory) throws Exception { initMocks(); if (proxyTls) { proxyTunnel.sslContext(buildProxySslContext()); } proxyAddress = proxyTunnel.startProxy(); startServer(protocols); - createClient(protocols, failHandshake, failProxyHandshake, connectRequestHeadersInitializer, proxyTls); + createClient(protocols, failHandshake, failProxyHandshake, connectRequestHeadersInitializer, proxyTls, + clientBuilderFactory); } /** @@ -254,8 +267,10 @@ private void startServer(List protocols) throws Exception { } private void createClient(List protocols, boolean failHandshake, boolean failProxyHandshake, - Consumer connectRequestHeadersInitializer, boolean proxyTls) { - assert serverContext != null && proxyAddress != null; + Consumer connectRequestHeadersInitializer, boolean proxyTls, + Function> + clientBuilderFactory) { + assert serverAddress != null && proxyAddress != null; final ProxyConfigBuilder proxyBuilder = new ProxyConfigBuilder<>(proxyAddress) .connectRequestHeadersInitializer(connectRequestHeadersInitializer); if (proxyTls) { @@ -267,7 +282,7 @@ private void createClient(List protocols, boolean failHandshake, b new ClientSslConfigBuilder(DefaultTestCerts::loadServerCAPem); proxyBuilder.sslConfig(proxySslConfig.build()); } - client = BuilderUtils.newClientBuilder(serverContext, CLIENT_CTX) + client = clientBuilderFactory.apply(serverAddress) .proxyConfig(proxyBuilder.build()) .sslConfig(new ClientSslConfigBuilder(DefaultTestCerts::loadServerCAPem) .peerHost(failHandshake ? "unknown" : serverPemHostname()).build()) @@ -285,6 +300,25 @@ void testClientRequest(List protocols, boolean proxyTls) throws Ex assertResponse(client.request(client.get("/path")), protocols.get(0).version, proxyTls); } + static Stream discoveryStrategiesAndProxyTls() { + return Stream.of(DiscoveryStrategy.values()) + .flatMap(strategy -> Stream.of(Arguments.of(strategy, false), Arguments.of(strategy, true))); + } + + @ParameterizedTest(name = "{displayName} [{index}] discoveryStrategy={0} proxyTls={1}") + @MethodSource("discoveryStrategiesAndProxyTls") + void testClientRequestWithServiceDiscoverer(DiscoveryStrategy discoveryStrategy, boolean proxyTls) + throws Exception { + setUp(singletonList(HttpProtocol.HTTP_1), false, false, __ -> { /* noop */ }, proxyTls, + address -> HttpClients.forSingleAddress(globalARecordsDnsServiceDiscoverer(), address, + discoveryStrategy) + .ioExecutor(CLIENT_CTX.ioExecutor()) + .executor(CLIENT_CTX.executor()) + .bufferAllocator(CLIENT_CTX.bufferAllocator())); + assertThat(client, is(notNullValue())); + assertResponse(client.request(client.get("/path")), HttpProtocol.HTTP_1.version, proxyTls); + } + @ParameterizedTest(name = "{displayName} [{index}] protocols={0} proxyTls={1}") @MethodSource("protocolsAndProxyTls") void testConnectionRequest(List protocols, boolean proxyTls) throws Exception {