Skip to content

Add ProxyConfiguration support for UrlConnectionHttpClient #3112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 31, 2022

Conversation

joviegas
Copy link
Contributor

@joviegas joviegas commented Mar 21, 2022

Motivation and Context

Raised a new request for #2986

Resolves #2458. The goal of this change is to add full proxy support to the UrlConnectionHttpClient so that our project can remove the Apache HTTP Client and instead rely on Java builtins. This change must support HTTP and HTTPS proxies with username/password authorization.

Modifications

Adds HTTP proxy support to the UrlConnectionHttpClient by using largely the same ProxyConfiguration class as is in the Apache HTTP Client. If the user configures a proxy using the builder, and the request isn't going to a non-proxy host, then the request will go through the application proxy using Java's builtin Proxy class.

Testing

Manually tested using a local squid proxy via HTTP without authentication.

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the CONTRIBUTING document
  • Local run of mvn install succeeds
  • My code follows the code style of this project
  • My change requires a change to the Javadoc documentation
  • I have updated the Javadoc documentation accordingly
  • I have added tests to cover my changes
  • All new and existing tests passed
  • I have added a changelog entry. Adding a new entry must be accomplished by running the scripts/new-change script and following the instructions. Commit the new file created by the script in .changes/next-release with your changes.
  • My change is to implement 1.11 parity feature and I have updated LaunchChangelog

License

  • I confirm that this pull request can be released under the Apache 2 license

Misc

  • tested in EC2 with below code
  • launched squid server in ec2
  • Not https testing for squid with Auth is bit complicated, I haved tested auth with http. and rest of cases as in test cases
package software.amazon.awssdk.http.urlconnection;

import static org.assertj.core.api.Assertions.assertThat;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES;

import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
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.utils.AttributeMap;
import software.amazon.awssdk.utils.IoUtils;

/**
 * Tests to ensure that {@link UrlConnectionHttpClient} can properly support TLS client authentication.
 */
public class ProxyConfigurationMockTest {
    public static final String HOST_IP_ADDRESS = "xx.xxx.xxx.xxx";
    public static final String HOST_IP_ADDRESS_WITH_AUTH = "yy.yyy.yyy.yyy";
    @Rule
    public ExpectedException thrown = ExpectedException.none();
    private SdkHttpClient client;

    @BeforeClass
    public static void setUp() throws IOException {
        System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
        System.setProperty("jdk.http.auth.proxying.disabledSchemes", "");

    }

    @AfterClass
    public static void teardown()  {
        System.clearProperty("javax.net.ssl.trustStore");
        System.clearProperty("javax.net.ssl.trustStorePassword");
        System.clearProperty("javax.net.ssl.trustStoreType");
    }

    @After
    public void methodTeardown() {
        if (client != null) {
            client.close();
        }
        client = null;
    }


    private SdkHttpClient createSdkHttpsClient(ProxyConfiguration proxyConfiguration) {
        UrlConnectionHttpClient.Builder builder =
            UrlConnectionHttpClient.builder().proxyConfiguration(proxyConfiguration
            );
        AttributeMap.Builder attributeMap = AttributeMap.builder();
        attributeMap.put(TRUST_ALL_CERTIFICATES, true);
        return builder.buildWithDefaults(attributeMap.build());
    }


    private SdkHttpClient createHttpClientForHttpServer(ProxyConfiguration proxyConfiguration) {
        UrlConnectionHttpClient.Builder builder =
            UrlConnectionHttpClient.builder().proxyConfiguration(proxyConfiguration);
        AttributeMap.Builder attributeMap = AttributeMap.builder();

        return builder.buildWithDefaults(attributeMap.build());
    }


    @Test
    public void clientWithHttpsAndNoAuth() throws IOException {
        SdkHttpClient sdkHttpClient = createSdkHttpsClient(ProxyConfiguration.builder()
                                                                             .endpoint(URI.create("https://" + HOST_IP_ADDRESS + ":3128"))
                                                                             .build());
        HttpExecuteResponse httpExecuteResponse = makeRequestWithHttpsClient(sdkHttpClient);
        Map<String, List<String>> headers = httpExecuteResponse.httpResponse().headers();
        System.out.println(httpExecuteResponse.httpResponse().statusCode());
        String actualResult = IoUtils.toUtf8String(httpExecuteResponse.responseBody().get());

        System.out.println("actualResult" +actualResult);
        assertThat(httpExecuteResponse.httpResponse().statusCode()).isEqualTo(200);
    }

    @Test
    public void clientWithHttpsAndWrongPassword() throws IOException {
        SdkHttpClient sdkHttpClient = createSdkHttpsClient(ProxyConfiguration.builder()
                                                                             .endpoint(URI.create("https://" + HOST_IP_ADDRESS + ":3128"))
                                                                             .password("1232222456")
                                                                             .username("joviegas")
                                                                             .build());
        HttpExecuteResponse httpExecuteResponse = makeRequestWithHttpsClient(sdkHttpClient);
        Map<String, List<String>> headers = httpExecuteResponse.httpResponse().headers();
        System.out.println(headers);
        System.out.println(httpExecuteResponse.httpResponse().statusCode());
        assertThat(httpExecuteResponse.httpResponse().statusCode()).isEqualTo(200);
    }


    @Test
    public void clientWithHttpsAndPassword() throws IOException {
        SdkHttpClient sdkHttpClient = createSdkHttpsClient(ProxyConfiguration.builder()
                                                                             .endpoint(URI.create("https://" + HOST_IP_ADDRESS_WITH_AUTH + ":3128"))
                                                                             .password("123456")
                                                                             .username("user1")
                                                                             .build());
        HttpExecuteResponse httpExecuteResponse = makeRequestWithHttpsClient(sdkHttpClient);
        assertThat(httpExecuteResponse.httpResponse().statusCode()).isEqualTo(200);
    }

    @Test
    public void httpClientWithWrongPassword() throws IOException {
        SdkHttpClient sdkHttpClient = createHttpClientForHttpServer(ProxyConfiguration.builder()
                                                                                      .endpoint(URI.create("http://" + HOST_IP_ADDRESS_WITH_AUTH + ":3128"))
                                                                                      .password("123433356")
                                                                                      .username("joviegas")
                                                                                      .build());
        HttpExecuteResponse httpExecuteResponse = makeHttpRequest(sdkHttpClient);
        Map<String, List<String>> headers = httpExecuteResponse.httpResponse().headers();
        System.out.println(headers);
        System.out.println(httpExecuteResponse.httpResponse().statusCode());
        String actualResult = IoUtils.toUtf8String(httpExecuteResponse.responseBody().get());
        System.out.println("actualResult " + actualResult);
        assertThat(httpExecuteResponse.httpResponse().statusCode()).isEqualTo(407);
    }

    @Test
    public void httpClientWithCorrectPassword() throws IOException {
        SdkHttpClient sdkHttpClient = createHttpClientForHttpServer(
            ProxyConfiguration.builder()
                              .endpoint(URI.create("http://" + HOST_IP_ADDRESS_WITH_AUTH + ":3128"))
                              .password("123456")
                              .username("user1")
                              .build());
        HttpExecuteResponse httpExecuteResponse = makeHttpRequest(sdkHttpClient);
        assertThat(httpExecuteResponse.httpResponse().statusCode()).isEqualTo(200);
    }

    @Test
    public void httpClientWithNoAuth() throws IOException {
        SdkHttpClient sdkHttpClient = createHttpClientForHttpServer(
            ProxyConfiguration.builder()
                      //        .endpoint(URI.create("http://" + HOST_IP_ADDRESS + ":3128"))
                              .build());
        HttpExecuteResponse httpExecuteResponse = makeHttpRequest(sdkHttpClient);
        String actualResult = IoUtils.toUtf8String(httpExecuteResponse.responseBody().get());
        assertThat(httpExecuteResponse.httpResponse().statusCode()).isEqualTo(200);
    }


    private HttpExecuteResponse makeRequestWithHttpsClient(SdkHttpClient httpClient) throws IOException {
        SdkHttpRequest httpRequest = SdkHttpFullRequest.builder()
                                                       .method(SdkHttpMethod.GET)
                                                       .protocol("https")
                                                       .host("www.google.com")
                                                       .build();

        HttpExecuteRequest request = HttpExecuteRequest.builder()
                                                       .request(httpRequest)
                                                       .build();

        return httpClient.prepareRequest(request).call();
    }


    private HttpExecuteResponse makeHttpRequest(SdkHttpClient httpClient) throws IOException {
        SdkHttpRequest httpRequest = SdkHttpFullRequest.builder()
                                                       .method(SdkHttpMethod.GET)
                                                       .protocol("http")
                                                       .host("httpforever.com")
                                                       .build();

        HttpExecuteRequest request = HttpExecuteRequest.builder()
                                                       .request(httpRequest)
                                                       .build();

        return httpClient.prepareRequest(request).call();
    }
}

@joviegas joviegas requested a review from a team as a code owner March 21, 2022 01:38
@joviegas joviegas force-pushed the url_connection_http branch 2 times, most recently from cfa933a to f0073e6 Compare March 21, 2022 08:40
@joviegas joviegas force-pushed the url_connection_http branch from f0073e6 to 7034585 Compare March 21, 2022 17:07
@@ -0,0 +1,6 @@
{
"category": "UrlConnectionHttpClient",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "URLConnection HTTP Client"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 217 to 218
new InetSocketAddress(this.proxyConfiguration.host(), this.proxyConfiguration.port())));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this resolves the address at construction time, which is not what we want. We should use InetSocketAddress.createUnresolved()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.utils.AttributeMap;

public class UrlConnectionHttpClientWithProxyTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: for the test method name, can you include the expected outcome? It makes tests easier to read and reason about

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@aws aws deleted a comment from dagnir Mar 21, 2022
@aws aws deleted a comment from dagnir Mar 21, 2022
@joviegas joviegas force-pushed the url_connection_http branch from 61047a8 to 746c30e Compare March 21, 2022 23:09
@joviegas joviegas enabled auto-merge (squash) March 22, 2022 17:55
@sonarqubecloud
Copy link

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 2 Code Smells

72.8% 72.8% Coverage
15.0% 15.0% Duplication

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UrlConnectionHttpClient should have builder methods for setting a Proxy
2 participants