All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awssdk.http.proxy.HttpClientDefaultProxyConfigTestSuite Maven / Gradle / Ivy

Go to download

A set of acceptance tests that can be extended by an SDK HTTP plugin implementation to help ensure it behaves in manner consistent with what is expected by the SDK.

There is a newer version: 2.29.39
Show newest version
/*
 * 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;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy