diff --git a/.changes/next-release/bugfix-AWSCRTHTTPClient-cfe4870.json b/.changes/next-release/bugfix-AWSCRTHTTPClient-cfe4870.json new file mode 100644 index 000000000000..f4c2b6e1c75d --- /dev/null +++ b/.changes/next-release/bugfix-AWSCRTHTTPClient-cfe4870.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "AWS CRT HTTP Client", + "contributor": "", + "description": "Addressing Issue [#4745](https://github.com/aws/aws-sdk-java-v2/issues/4745) , Netty and CRT clients' default proxy settings have been made consistent with the Apache client, now using environment and system property settings by default.\\n To disable the use of environment variables and system properties by default, set useSystemPropertyValue(false) and useEnvironmentVariablesValues(false) in ProxyConfigurations." +} diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-c277e61.json b/.changes/next-release/bugfix-AWSSDKforJavav2-c277e61.json new file mode 100644 index 000000000000..003fe1e06c0c --- /dev/null +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-c277e61.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Addressing Issue [#4745](https://github.com/aws/aws-sdk-java-v2/issues/4745) , Netty and CRT clients' default proxy settings have been made consistent with the Apache client, now using environment and system property settings by default.\n To disable the use of environment variables and system properties by default, set useSystemPropertyValue(false) and useEnvironmentVariablesValues(false) in ProxyConfigurations." +} diff --git a/.changes/next-release/bugfix-NettyNIOAsyncHTTPClient-edd86be.json b/.changes/next-release/bugfix-NettyNIOAsyncHTTPClient-edd86be.json new file mode 100644 index 000000000000..637cc3f02d89 --- /dev/null +++ b/.changes/next-release/bugfix-NettyNIOAsyncHTTPClient-edd86be.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "Netty NIO Async HTTP Client", + "contributor": "", + "description": "Addressing Issue [#4745](https://github.com/aws/aws-sdk-java-v2/issues/4745) , Netty and CRT clients' default proxy settings have been made consistent with the Apache client, now using environment and system property settings by default.\\n To disable the use of environment variables and system properties by default, set useSystemPropertyValue(false) and useEnvironmentVariablesValues(false) in ProxyConfigurations" +} diff --git a/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheClientProxyConfigurationTest.java b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheClientProxyConfigurationTest.java new file mode 100644 index 000000000000..4b9ff8549912 --- /dev/null +++ b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheClientProxyConfigurationTest.java @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package software.amazon.awssdk.http.apache; + +import org.apache.http.conn.HttpHostConnectException; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.proxy.HttpClientDefaultProxyConfigTestSuite; +import java.net.ConnectException; + +public class ApacheClientProxyConfigurationTest extends HttpClientDefaultProxyConfigTestSuite { + + @Override + protected Class getProxyFailedExceptionType() { + return HttpHostConnectException.class; + + } + + @Override + protected Class getProxyFailedCauseExceptionType() { + return ConnectException.class; + } + + @Override + protected boolean isSyncClient() { + return true; + } + + @Override + protected SdkAsyncHttpClient createHttpClientWithDefaultProxy() { + throw new IllegalArgumentException("Async client is not supported for this test."); + } + + @Override + protected SdkHttpClient createSyncHttpClientWithDefaultProxy() { + return ApacheHttpClient.create(); + } + +} \ No newline at end of file diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtClientBuilderBase.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtClientBuilderBase.java index 5fb70b7e2512..dbb1e84f3c8e 100644 --- a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtClientBuilderBase.java +++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtClientBuilderBase.java @@ -29,7 +29,7 @@ public class AwsCrtClientBuilderBase { private final AttributeMap.Builder standardOptions = AttributeMap.builder(); private Long readBufferSize; - private ProxyConfiguration proxyConfiguration; + private ProxyConfiguration proxyConfiguration = ProxyConfiguration.builder().build(); private ConnectionHealthConfiguration connectionHealthConfiguration; private TcpKeepAliveConfiguration tcpKeepAliveConfiguration; private Boolean postQuantumTlsEnabled; diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AsyncCrtClientProxyConfigurationTest.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AsyncCrtClientProxyConfigurationTest.java new file mode 100644 index 000000000000..224ad778a95c --- /dev/null +++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AsyncCrtClientProxyConfigurationTest.java @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package software.amazon.awssdk.http.crt; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.proxy.HttpClientDefaultProxyConfigTestSuite; + +public class AsyncCrtClientProxyConfigurationTest extends HttpClientDefaultProxyConfigTestSuite { + @Override + protected Class getProxyFailedExceptionType() { + return ExecutionException.class; + } + + @Override + protected Class getProxyFailedCauseExceptionType() { + return IOException.class; + } + + @Override + protected boolean isSyncClient() { + return false; + } + + @Override + protected SdkAsyncHttpClient createHttpClientWithDefaultProxy() { + return AwsCrtAsyncHttpClient.create(); + } + + @Override + protected SdkHttpClient createSyncHttpClientWithDefaultProxy() { + throw new IllegalArgumentException("Sync client is not supported for this test."); + } +} diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/SyncCrtClientProxyConfigurationTest.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/SyncCrtClientProxyConfigurationTest.java new file mode 100644 index 000000000000..40208abc4d40 --- /dev/null +++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/SyncCrtClientProxyConfigurationTest.java @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package software.amazon.awssdk.http.crt; + +import java.io.IOException; +import software.amazon.awssdk.crt.http.HttpException; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.proxy.HttpClientDefaultProxyConfigTestSuite; + +public class SyncCrtClientProxyConfigurationTest extends HttpClientDefaultProxyConfigTestSuite { + + @Override + protected Class getProxyFailedExceptionType() { + return IOException.class; + } + + @Override + protected Class getProxyFailedCauseExceptionType() { + return HttpException.class; + } + + @Override + protected boolean isSyncClient() { + return true; + } + + @Override + protected SdkAsyncHttpClient createHttpClientWithDefaultProxy() { + throw new UnsupportedOperationException("Async client is not supported for this test."); + } + + @Override + protected SdkHttpClient createSyncHttpClientWithDefaultProxy() { + return AwsCrtHttpClient.create(); + } +} diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java index c12aeab10180..70f62e1f6e77 100644 --- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java @@ -501,7 +501,7 @@ private static final class DefaultBuilder implements Builder { private Integer maxHttp2Streams; private Http2Configuration http2Configuration; private SslProvider sslProvider; - private ProxyConfiguration proxyConfiguration; + private ProxyConfiguration proxyConfiguration = ProxyConfiguration.builder().build(); private Boolean useNonBlockingDnsResolver; private DefaultBuilder() { diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java index ca310593f040..e2fbd1c1adca 100644 --- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java @@ -186,7 +186,7 @@ private Bootstrap createBootstrap(URI poolKey) { private boolean shouldUseProxyForHost(URI remoteAddr) { - if (proxyConfiguration == null) { + if (proxyConfiguration == null || proxyConfiguration.host() == null) { return false; } diff --git a/http-clients/netty-nio-client/src/test/java/NettyClientProxyConfigurationTest.java b/http-clients/netty-nio-client/src/test/java/NettyClientProxyConfigurationTest.java new file mode 100644 index 000000000000..0a149a972bd7 --- /dev/null +++ b/http-clients/netty-nio-client/src/test/java/NettyClientProxyConfigurationTest.java @@ -0,0 +1,48 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import java.net.ConnectException; +import java.util.concurrent.ExecutionException; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; +import software.amazon.awssdk.http.proxy.HttpClientDefaultProxyConfigTestSuite; + +public class NettyClientProxyConfigurationTest extends HttpClientDefaultProxyConfigTestSuite { + @Override + protected boolean isSyncClient() { + return false; + } + + @Override + protected SdkAsyncHttpClient createHttpClientWithDefaultProxy() { + return NettyNioAsyncHttpClient.create(); + } + + @Override + protected SdkHttpClient createSyncHttpClientWithDefaultProxy() { + throw new IllegalArgumentException("Sync client is not supported for this test."); + } + + @Override + protected Class getProxyFailedExceptionType() { + return ExecutionException.class; + } + + @Override + protected Class getProxyFailedCauseExceptionType() { + return ConnectException.class; + } +} diff --git a/http-clients/url-connection-client/src/main/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.java b/http-clients/url-connection-client/src/main/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.java index e4bde8ffd958..4d1761a12c33 100644 --- a/http-clients/url-connection-client/src/main/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.java +++ b/http-clients/url-connection-client/src/main/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.java @@ -496,7 +496,7 @@ public interface Builder extends SdkHttpClient.Builder getProxyFailedExceptionType() { + return IOException.class; + } + + @Override + protected Class getProxyFailedCauseExceptionType() { + return null; + } +} diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/http/proxy/HttpClientDefaultProxyConfigTestSuite.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/proxy/HttpClientDefaultProxyConfigTestSuite.java new file mode 100644 index 000000000000..04fc4a5d41f9 --- /dev/null +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/proxy/HttpClientDefaultProxyConfigTestSuite.java @@ -0,0 +1,340 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package software.amazon.awssdk.http.proxy; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import java.net.URI; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import software.amazon.awssdk.http.EmptyPublisher; +import software.amazon.awssdk.http.ExecutableHttpRequest; +import software.amazon.awssdk.http.HttpExecuteRequest; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.http.async.AsyncExecuteRequest; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.utils.Pair; + +public abstract class HttpClientDefaultProxyConfigTestSuite { + + private static final EnvironmentVariableHelper ENVIRONMENT_VARIABLE_HELPER = new EnvironmentVariableHelper(); + + SecureRandom random = new SecureRandom(); + WireMockServer mockProxy = new WireMockServer(new WireMockConfiguration() + .dynamicPort() + .dynamicHttpsPort() + .enableBrowserProxying(true)); + + WireMockServer mockServer = new WireMockServer(new WireMockConfiguration() + .dynamicPort() + .dynamicHttpsPort()); + + protected abstract boolean isSyncClient(); + + protected abstract SdkAsyncHttpClient createHttpClientWithDefaultProxy(); + + protected abstract SdkHttpClient createSyncHttpClientWithDefaultProxy(); + + protected abstract Class getProxyFailedExceptionType(); + + protected abstract Class getProxyFailedCauseExceptionType(); + + @BeforeEach + public void setup() { + System.clearProperty("http.proxyHost"); + System.clearProperty("http.proxyPort"); + System.clearProperty("https.proxyHost"); + System.clearProperty("https.proxyPort"); + System.clearProperty("http.nonProxyHosts"); + System.clearProperty("https.nonProxyHosts"); + System.clearProperty("https.proxyPort"); + mockProxy.start(); + mockServer.start(); + mockServer.stubFor(get(WireMock.urlMatching(".*")) + .willReturn(aResponse().withStatus(200).withBody("hello"))); + } + + @AfterEach + public void teardown() { + mockServer.stop(); + mockProxy.stop(); + ENVIRONMENT_VARIABLE_HELPER.reset(); + } + + public static Stream proxyConfigurationSettingsForEnvironmentAndSystemProperty() { + return Stream.of( + Arguments.of(new TestData() + .addSystemProperKeyValue("http.proxyHost", "localhost") + .addSystemProperKeyValue("http.proxyPort", "%s") + .addEnvironmentPropertyProperKeyValue("https_proxy", "http://" + "localhost" + ":" + "%s" + "/"), + "Provided system and environment variable when configured uses proxy config"), + + Arguments.of(new TestData() + .addSystemProperKeyValue("http.proxyHost", "localhost") + .addSystemProperKeyValue("http.proxyPort", "%s") + .addEnvironmentPropertyProperKeyValue("none", "none"), + "Provided system and No environment variables uses proxy config"), + + Arguments.of(new TestData() + .addSystemProperKeyValue("none", "none") + .addEnvironmentPropertyProperKeyValue("http_proxy", "http://" + "localhost" + ":" + "%s"), + "Provided Environment Variables and No system variables uses proxy config") + ); + } + + @ParameterizedTest(name = "{index} - {1}.") + @MethodSource("proxyConfigurationSettingsForEnvironmentAndSystemProperty") + public void ensureProxyErrorsWhenIncorrectPortUsed(TestData testData, String testCaseName) throws Throwable { + setSystemPropertyAndEnvironmentVariables(testData, getRandomPort(mockProxy.port())); + if (isSyncClient()) { + SdkHttpClient syncHttpClientWithDefaultProxy = createSyncHttpClientWithDefaultProxy(); + defaultProxyConfigurationSyncHttp(syncHttpClientWithDefaultProxy, + getProxyFailedExceptionType(), + getProxyFailedCauseExceptionType()); + syncHttpClientWithDefaultProxy.close(); + } else { + SdkAsyncHttpClient httpClientWithDefaultProxy = createHttpClientWithDefaultProxy(); + defaultProxyConfigurationForAsyncHttp(httpClientWithDefaultProxy, + getProxyFailedExceptionType(), + getProxyFailedCauseExceptionType()); + httpClientWithDefaultProxy.close(); + } + } + + + private void setSystemPropertyAndEnvironmentVariablesToDivertToHttpsProxy(TestData testData, int port) { + testData.systemPropertyPair.stream() + .map(r -> isSyncClient() ? r : Pair.of(r.left().replace("http", "https"), + r.right().replace("http", "https"))) + .forEach(settingsPair -> System.setProperty(settingsPair.left(), + String.format(settingsPair.right(), port))); + + testData.envSystemSetting.stream() + .map(r -> isSyncClient() ? r : Pair.of(r.left().replace("http", "https"), + r.right().replace("http", "https"))) + .forEach(settingsPair -> ENVIRONMENT_VARIABLE_HELPER.set(settingsPair.left(), + String.format(settingsPair.right(), + port))); + } + + private void setSystemPropertyAndEnvironmentVariables(TestData testData, int port) { + testData.systemPropertyPair + .forEach(settingsPair -> System.setProperty(settingsPair.left(), + String.format(settingsPair.right(), port))); + testData.envSystemSetting + .forEach(settingsPair -> ENVIRONMENT_VARIABLE_HELPER.set(settingsPair.left(), + String.format(settingsPair.right(), port))); + } + + @ParameterizedTest(name = "{index} - {1}.") + @MethodSource("proxyConfigurationSettingsForEnvironmentAndSystemProperty") + public void ensureHttpCallsPassesWhenProxyWithCorrectPortIsUsed(TestData testData, String testCaseName) throws Throwable { + setSystemPropertyAndEnvironmentVariablesToDivertToHttpsProxy(testData, mockProxy.port()); + if (isSyncClient()) { + SdkHttpClient syncHttpClientWithDefaultProxy = createSyncHttpClientWithDefaultProxy(); + defaultProxyConfigurationSyncHttp(syncHttpClientWithDefaultProxy, null, null); + syncHttpClientWithDefaultProxy.close(); + + } else { + SdkAsyncHttpClient asyncHttpClient = createHttpClientWithDefaultProxy(); + defaultProxyConfigurationForAsyncHttp(asyncHttpClient, null, null); + asyncHttpClient.close(); + } + } + + @Test + public void ensureHttpCallsPassesWhenProxyIsNotUsed() throws Throwable { + if (isSyncClient()) { + SdkHttpClient sdkHttpClient = createSyncHttpClientWithDefaultProxy(); + defaultProxyConfigurationSyncHttp(sdkHttpClient, null, null); + sdkHttpClient.close(); + } else { + SdkAsyncHttpClient asyncHttpClient = createHttpClientWithDefaultProxy(); + defaultProxyConfigurationForAsyncHttp(asyncHttpClient, null, null); + asyncHttpClient.close(); + } + } + + public void defaultProxyConfigurationForAsyncHttp(SdkAsyncHttpClient client, + Class proxyFailedExceptionType, + Class proxyFailedCauseExceptionType) throws Throwable { + CompletableFuture streamReceived = new CompletableFuture<>(); + AtomicReference response = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + Subscriber subscriber = createDummySubscriber(); + SdkAsyncHttpResponseHandler handler = createTestResponseHandler(response, streamReceived, error, subscriber); + URI uri = URI.create("http://localhost:" + mockServer.port()); + SdkHttpRequest request = createRequest(uri, "/server/test", null, SdkHttpMethod.GET, emptyMap()); + CompletableFuture future = client.execute(AsyncExecuteRequest.builder() + .request(request) + .responseHandler(handler) + .requestContentPublisher(new EmptyPublisher()) + .build()); + if (proxyFailedExceptionType != null && proxyFailedCauseExceptionType != null) { + assertThatExceptionOfType(proxyFailedExceptionType).isThrownBy(() -> future.get(60, TimeUnit.SECONDS)) + .withCauseInstanceOf(proxyFailedCauseExceptionType); + } else { + future.get(60, TimeUnit.SECONDS); + assertThat(error.get()).isNull(); + assertThat(streamReceived.get(60, TimeUnit.SECONDS)).isTrue(); + assertThat(response.get().statusCode()).isEqualTo(200); + } + } + + + public void defaultProxyConfigurationSyncHttp(SdkHttpClient client, Class exceptionType, + Class proxyFailedCauseExceptionType) throws Throwable { + CompletableFuture streamReceived = new CompletableFuture<>(); + AtomicReference response = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + Subscriber subscriber = createDummySubscriber(); + SdkAsyncHttpResponseHandler handler = createTestResponseHandler(response, streamReceived, error, subscriber); + URI uri = URI.create("http://localhost:" + mockServer.port()); + SdkHttpRequest request = createRequest(uri, "/server/test", null, SdkHttpMethod.GET, emptyMap()); + ExecutableHttpRequest executableHttpRequest = client.prepareRequest(HttpExecuteRequest.builder() + .request(request) + .build()); + + if (exceptionType != null) { + if (proxyFailedCauseExceptionType != null) { + assertThatExceptionOfType(exceptionType).isThrownBy(() -> executableHttpRequest.call()) + .withCauseInstanceOf(proxyFailedCauseExceptionType); + } else { + assertThatExceptionOfType(exceptionType).isThrownBy(() -> executableHttpRequest.call()); + } + } else { + HttpExecuteResponse executeResponse = executableHttpRequest.call(); + assertThat(error.get()).isNull(); + assertThat(executeResponse.httpResponse().statusCode()).isEqualTo(200); + } + } + + static Subscriber createDummySubscriber() { + return new Subscriber() { + @Override + public void onSubscribe(Subscription subscription) { + subscription.request(Long.MAX_VALUE); + } + + @Override + public void onNext(ByteBuffer byteBuffer) { + } + + @Override + public void onError(Throwable throwable) { + } + + @Override + public void onComplete() { + } + }; + } + + static SdkAsyncHttpResponseHandler createTestResponseHandler(AtomicReference response, + CompletableFuture streamReceived, + AtomicReference error, + Subscriber subscriber) { + return new SdkAsyncHttpResponseHandler() { + @Override + public void onHeaders(SdkHttpResponse headers) { + response.compareAndSet(null, headers); + } + + @Override + public void onStream(Publisher stream) { + stream.subscribe(subscriber); + streamReceived.complete(true); + } + + @Override + public void onError(Throwable t) { + error.compareAndSet(null, t); + } + }; + } + + static SdkHttpFullRequest createRequest(URI endpoint, + String resourcePath, + byte[] body, + SdkHttpMethod method, + Map params) { + String contentLength = (body == null) ? null : String.valueOf(body.length); + return SdkHttpFullRequest.builder() + .uri(endpoint) + .method(method) + .encodedPath(resourcePath) + .applyMutation(b -> params.forEach(b::putRawQueryParameter)) + .applyMutation(b -> { + b.putHeader("Host", endpoint.getHost()); + if (contentLength != null) { + b.putHeader("Content-Length", contentLength); + } + }).build(); + } + + private int getRandomPort(int currentPort) { + int randomPort; + do { + randomPort = random.nextInt(65535); + } while (randomPort == currentPort); + return randomPort; + } + + private static class TestData { + private List> envSystemSetting = new ArrayList<>(); + private List> systemPropertyPair = new ArrayList<>(); + + public TestData addSystemProperKeyValue(String key, String value) { + systemPropertyPair.add(Pair.of(key, value)); + return this; + } + + public TestData addEnvironmentPropertyProperKeyValue(String key, String value) { + envSystemSetting.add(Pair.of(key, value)); + return this; + } + } +}