diff --git a/.changes/next-release/feature-AWSSDKforJavav2-eeeef1c.json b/.changes/next-release/feature-AWSSDKforJavav2-eeeef1c.json new file mode 100644 index 000000000000..36834ed8fe7b --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-eeeef1c.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Add support for resolving the disable host prefix injection setting from the aws.disableHostPrefixInjection system property, the AWS_DISABLE_HOST_PREFIX_INJECTION environment variable, and the disable_host_prefix_injection profile property." +} diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java index 325585dc6d7b..ae10ffcff95b 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java @@ -48,7 +48,6 @@ import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder; -import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.http.EnableDefaultSocketTimeout2026Resolver; @@ -146,7 +145,6 @@ protected AwsDefaultClientBuilder() { protected final SdkClientConfiguration mergeChildDefaults(SdkClientConfiguration configuration) { SdkClientConfiguration config = mergeServiceDefaults(configuration); config = config.merge(c -> c.option(AwsAdvancedClientOption.ENABLE_DEFAULT_REGION_DETECTION, true) - .option(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION, false) .option(AwsClientOption.SERVICE_SIGNING_NAME, signingName()) .option(SdkClientOption.SERVICE_NAME, serviceName()) .option(AwsClientOption.ENDPOINT_PREFIX, serviceEndpointPrefix())); diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java index c76b5bb36823..34cbfd68de03 100644 --- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java @@ -25,6 +25,7 @@ import static software.amazon.awssdk.awscore.client.config.AwsAdvancedClientOption.ENABLE_DEFAULT_REGION_DETECTION; import static software.amazon.awssdk.awscore.client.config.AwsClientOption.SERVICE_SIGNING_NAME; import static software.amazon.awssdk.awscore.client.config.AwsClientOption.SIGNING_REGION; +import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION; import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.SIGNER; import java.beans.BeanInfo; @@ -37,6 +38,7 @@ import java.util.Optional; import java.util.function.Predicate; 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.api.extension.ExtendWith; @@ -53,6 +55,7 @@ import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.ClientEndpointProvider; +import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.internal.retry.SdkDefaultRetryStrategy; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.SdkHttpClient; @@ -99,6 +102,32 @@ public class DefaultAwsClientBuilderTest { public void setup() { when(defaultHttpClientBuilder.buildWithDefaults(any())).thenReturn(mock(SdkHttpClient.class)); when(defaultAsyncHttpClientFactory.buildWithDefaults(any())).thenReturn(mock(SdkAsyncHttpClient.class)); + System.clearProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property()); + } + + @AfterEach + public void cleanup() { + System.clearProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property()); + } + + @Test + public void disableHostPrefixInjection_nothingSet_defaultsToFalse() { + TestClient client = testClientBuilder().region(Region.US_WEST_1).build(); + assertThat(client.clientConfiguration.option(DISABLE_HOST_PREFIX_INJECTION)).isFalse(); + } + + @Test + public void disableHostPrefixInjection_systemPropertyTrue_resolvesTrue() { + System.setProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property(), "true"); + TestClient client = testClientBuilder().region(Region.US_WEST_1).build(); + assertThat(client.clientConfiguration.option(DISABLE_HOST_PREFIX_INJECTION)).isTrue(); + } + + @Test + public void disableHostPrefixInjection_asyncClient_systemPropertyTrue_resolvesTrue() { + System.setProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property(), "true"); + TestAsyncClient client = testAsyncClientBuilder().region(Region.US_WEST_1).build(); + assertThat(client.clientConfiguration.option(DISABLE_HOST_PREFIX_INJECTION)).isTrue(); } @Test diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java index b3243ba6c29b..2a345136861c 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java @@ -177,6 +177,12 @@ public final class ProfileProperty { */ public static final String DISABLE_REQUEST_COMPRESSION = "disable_request_compression"; + /** + * Whether the SDK should skip prepending the modeled hostPrefix to the endpoint host. The default value is false, i.e., + * the hostPrefix is prepended. + */ + public static final String DISABLE_HOST_PREFIX_INJECTION = "disable_host_prefix_injection"; + /** * The minimum compression size in bytes, inclusive, for a request to be compressed. The default value is 10_240. * The value must be non-negative and no greater than 10_485_760. diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java index 0da073035e64..6033f6d348fa 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java @@ -230,6 +230,12 @@ public enum SdkSystemSetting implements SystemSetting { */ AWS_DISABLE_REQUEST_COMPRESSION("aws.disableRequestCompression", null), + /** + * Whether the SDK should skip prepending the modeled hostPrefix to the endpoint host. The default value is false, i.e., + * the hostPrefix is prepended. + */ + AWS_DISABLE_HOST_PREFIX_INJECTION("aws.disableHostPrefixInjection", null), + /** * Defines the minimum compression size in bytes, inclusive, for a request to be compressed. The default value is 10_240. * The value must be non-negative and no greater than 10_485_760. diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java index 34f165dd1767..ff41a0370b2a 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java @@ -18,6 +18,7 @@ import static software.amazon.awssdk.core.ClientType.ASYNC; import static software.amazon.awssdk.core.ClientType.SYNC; import static software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR; +import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION; import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.USER_AGENT_PREFIX; import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.USER_AGENT_SUFFIX; import static software.amazon.awssdk.core.client.config.SdkClientOption.ADDITIONAL_HTTP_HEADERS; @@ -360,7 +361,8 @@ private SdkClientConfiguration finalizeConfiguration(SdkClientConfiguration conf .option(EXECUTION_INTERCEPTORS, resolveExecutionInterceptors(config)) .lazyOption(CLIENT_USER_AGENT, this::resolveClientUserAgent) .lazyOption(COMPRESSION_CONFIGURATION, this::resolveCompressionConfiguration) - .lazyOptionIfAbsent(IDENTITY_PROVIDERS, c -> IdentityProviders.builder().build()); + .lazyOptionIfAbsent(IDENTITY_PROVIDERS, c -> IdentityProviders.builder().build()) + .lazyOptionIfAbsent(DISABLE_HOST_PREFIX_INJECTION, this::resolveDisableHostPrefixInjection); builder.computeOptionIfAbsent(HTTP_CLIENT_CONFIG_TYPE, () -> BusinessMetricFeatureId.HTTP_CLIENT_AUTO); return builder.build(); } @@ -390,6 +392,17 @@ private Boolean resolveCompressionEnabled(LazyValueSource config, CompressionCon .orElse(true); } + private Boolean resolveDisableHostPrefixInjection(LazyValueSource config) { + Supplier> profileFileConfiguration = + () -> config.get(PROFILE_FILE_SUPPLIER).get() + .profile(config.get(PROFILE_NAME)) + .flatMap(p -> p.booleanProperty(ProfileProperty.DISABLE_HOST_PREFIX_INJECTION)); + + return OptionalUtils.firstPresent(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.getBooleanValue(), + profileFileConfiguration) + .orElse(false); + } + private Integer resolveMinCompressionThreshold(LazyValueSource config, CompressionConfiguration compressionConfig) { Supplier> systemSettingConfiguration = SdkSystemSetting.AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES::getIntegerValue; diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedClientOption.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedClientOption.java index 28924e9aea2d..697e6a3f973e 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedClientOption.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedClientOption.java @@ -84,6 +84,14 @@ public class SdkAdvancedClientOption extends ClientOption { * the endpoint host that the API request is sent to. * * Customers can set this value to True to disable the behavior. + * + *

If this option is not set, the SDK resolves it from the following sources, in order: + *

    + *
  1. The {@code aws.disableHostPrefixInjection} system property
  2. + *
  3. The {@code AWS_DISABLE_HOST_PREFIX_INJECTION} environment variable
  4. + *
  5. The {@code disable_host_prefix_injection} property in the configured profile
  6. + *
+ * If none of them is set, the default is false. */ public static final SdkAdvancedClientOption DISABLE_HOST_PREFIX_INJECTION = new SdkAdvancedClientOption<>(Boolean.class); diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java index cfefe8254cec..bca63997bf28 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java @@ -63,6 +63,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.function.Supplier; import org.assertj.core.api.Assertions; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -72,6 +73,7 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.core.ClientEndpointProvider; +import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; @@ -89,6 +91,7 @@ import software.amazon.awssdk.metrics.MetricCollection; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.StringInputStream; @@ -111,6 +114,9 @@ public class DefaultClientBuilderTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + @Rule + public EnvironmentVariableHelper environmentVariableHelper = new EnvironmentVariableHelper(); + @Mock private SdkHttpClient.Builder defaultHttpClientFactory; @@ -121,6 +127,12 @@ public class DefaultClientBuilderTest { public void setup() { when(defaultHttpClientFactory.buildWithDefaults(any())).thenReturn(mock(SdkHttpClient.class)); when(defaultAsyncHttpClientFactory.buildWithDefaults(any())).thenReturn(mock(SdkAsyncHttpClient.class)); + System.clearProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property()); + } + + @After + public void cleanup() { + System.clearProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property()); } @Test @@ -520,6 +532,108 @@ public void defaultProfileFileSupplier_isStaticOrHasIdentityCaching() { assertThat(secondGet).isSameAs(firstGet); } + @Test + public void disableHostPrefixInjection_nothingSet_defaultsToFalse() { + assertThat(disableHostPrefixInjection(null, null)).isFalse(); + } + + @Test + public void disableHostPrefixInjection_asyncClient_nothingSet_defaultsToFalse() { + SdkClientConfiguration config = testAsyncClientBuilder() + .overrideConfiguration(o -> o.defaultProfileFile(profileFile("")) + .defaultProfileName("p")) + .build().clientConfiguration; + assertThat(config.option(DISABLE_HOST_PREFIX_INJECTION)).isFalse(); + } + + @Test + public void disableHostPrefixInjection_systemPropertyTrue_resolvesTrue() { + System.setProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property(), "TRUE"); + assertThat(disableHostPrefixInjection(null, null)).isTrue(); + } + + @Test + public void disableHostPrefixInjection_asyncClient_systemPropertyTrue_resolvesTrue() { + System.setProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property(), "true"); + SdkClientConfiguration config = testAsyncClientBuilder().build().clientConfiguration; + assertThat(config.option(DISABLE_HOST_PREFIX_INJECTION)).isTrue(); + } + + @Test + public void disableHostPrefixInjection_envVarTrue_resolvesTrue() { + environmentVariableHelper.set(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION, "true"); + assertThat(disableHostPrefixInjection(null, null)).isTrue(); + } + + @Test + public void disableHostPrefixInjection_profileTrue_resolvesTrue() { + assertThat(disableHostPrefixInjection(null, "[profile p]\ndisable_host_prefix_injection = True")).isTrue(); + } + + @Test + public void disableHostPrefixInjection_profileTrueInOtherProfile_isIgnored() { + assertThat(disableHostPrefixInjection(null, "[profile other]\ndisable_host_prefix_injection = true")).isFalse(); + } + + @Test + public void disableHostPrefixInjection_explicitFalse_beatsSystemProperty() { + System.setProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property(), "true"); + assertThat(disableHostPrefixInjection(Boolean.FALSE, null)).isFalse(); + } + + @Test + public void disableHostPrefixInjection_explicitTrue_beatsSystemProperty() { + System.setProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property(), "false"); + assertThat(disableHostPrefixInjection(Boolean.TRUE, null)).isTrue(); + } + + @Test + public void disableHostPrefixInjection_systemPropertyFalse_beatsEnvVar() { + System.setProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property(), "false"); + environmentVariableHelper.set(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION, "true"); + assertThat(disableHostPrefixInjection(null, null)).isFalse(); + } + + @Test + public void disableHostPrefixInjection_envVarFalse_beatsProfile() { + environmentVariableHelper.set(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION, "false"); + assertThat(disableHostPrefixInjection(null, "[profile p]\ndisable_host_prefix_injection = true")).isFalse(); + } + + @Test + public void disableHostPrefixInjection_invalidSystemProperty_throwsOnBuild() { + System.setProperty(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property(), "yes"); + assertThatThrownBy(() -> disableHostPrefixInjection(null, null)).isInstanceOf(IllegalStateException.class); + } + + @Test + public void disableHostPrefixInjection_invalidProfileValue_throwsOnBuild() { + assertThatThrownBy(() -> disableHostPrefixInjection(null, "[profile p]\ndisable_host_prefix_injection = yes")) + .isInstanceOf(IllegalStateException.class); + } + + private Boolean disableHostPrefixInjection(Boolean explicitValue, String profileContent) { + ClientOverrideConfiguration.Builder overrideConfig = + ClientOverrideConfiguration.builder() + .putAdvancedOption(SIGNER, TEST_SIGNER) + .defaultProfileFile(profileFile(profileContent == null ? "" : profileContent)) + .defaultProfileName("p"); + if (explicitValue != null) { + overrideConfig.putAdvancedOption(DISABLE_HOST_PREFIX_INJECTION, explicitValue); + } + return new TestClientBuilder().overrideConfiguration(overrideConfig.build()) + .build() + .clientConfiguration + .option(DISABLE_HOST_PREFIX_INJECTION); + } + + private static ProfileFile profileFile(String content) { + return ProfileFile.builder() + .content(new StringInputStream(content)) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + } + private SdkDefaultClientBuilder testClientBuilder() { ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder() diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HostPrefixInjectionConfigurationTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HostPrefixInjectionConfigurationTest.java new file mode 100644 index 000000000000..42bef94e4fed --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/HostPrefixInjectionConfigurationTest.java @@ -0,0 +1,192 @@ +/* + * 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.services; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; + +import java.net.URI; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClient; +import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; +import software.amazon.awssdk.utils.IoUtils; +import software.amazon.awssdk.utils.StringInputStream; + +/** + * Verifies that the disable host prefix injection setting is resolved from the system property, the environment variable + * and the profile file, and that a generated client then skips the modeled hostPrefix. + */ +public class HostPrefixInjectionConfigurationTest { + private static final String PROPERTY = SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION.property(); + + private final EnvironmentVariableHelper environmentVariableHelper = new EnvironmentVariableHelper(); + private MockSyncHttpClient mockHttpClient; + private MockAsyncHttpClient mockAsyncHttpClient; + private AtomicReference capturedBody; + + @BeforeEach + public void setup() { + System.clearProperty(PROPERTY); + mockHttpClient = new MockSyncHttpClient(); + mockAsyncHttpClient = new MockAsyncHttpClient(); + mockHttpClient.stubNextResponse(okResponse()); + mockAsyncHttpClient.stubNextResponse(okResponse()); + capturedBody = new AtomicReference<>(); + } + + @AfterEach + public void cleanup() { + System.clearProperty(PROPERTY); + environmentVariableHelper.reset(); + mockAsyncHttpClient.close(); + } + + @Test + public void nothingSet_hostPrefixIsApplied() { + syncClient(emptyProfileOverride()).operationWithHostPrefix(b -> b.stringMember("123")); + assertThat(mockHttpClient.getLastRequest().getUri().getHost()).isEqualTo("123-foo.localhost"); + + asyncClient(emptyProfileOverride()).operationWithHostPrefix(b -> b.stringMember("123")).join(); + assertThat(mockAsyncHttpClient.getLastRequest().getUri().getHost()).isEqualTo("123-foo.localhost"); + } + + @Test + public void systemPropertyTrue_sync_hostPrefixIsNotApplied_memberStillSerialized() { + System.setProperty(PROPERTY, "true"); + syncClient(emptyProfileOverride()).operationWithHostPrefix(b -> b.stringMember("123")); + assertThat(mockHttpClient.getLastRequest().getUri().getHost()).isEqualTo("localhost"); + assertThat(capturedBody.get()).contains("\"StringMember\":\"123\""); + } + + @Test + public void systemPropertyTrue_async_hostPrefixIsNotApplied() { + System.setProperty(PROPERTY, "true"); + asyncClient(emptyProfileOverride()).operationWithHostPrefix(b -> b.stringMember("123")).join(); + assertThat(mockAsyncHttpClient.getLastRequest().getUri().getHost()).isEqualTo("localhost"); + } + + @Test + public void environmentVariableTrue_hostPrefixIsNotApplied() { + environmentVariableHelper.set(SdkSystemSetting.AWS_DISABLE_HOST_PREFIX_INJECTION, "true"); + syncClient(emptyProfileOverride()).operationWithHostPrefix(b -> b.stringMember("123")); + assertThat(mockHttpClient.getLastRequest().getUri().getHost()).isEqualTo("localhost"); + + asyncClient(emptyProfileOverride()).operationWithHostPrefix(b -> b.stringMember("123")).join(); + assertThat(mockAsyncHttpClient.getLastRequest().getUri().getHost()).isEqualTo("localhost"); + } + + @Test + public void profileTrue_hostPrefixIsNotApplied() { + ClientOverrideConfiguration override = + profileOverride("[profile test]\ndisable_host_prefix_injection = true"); + syncClient(override).operationWithHostPrefix(b -> b.stringMember("123")); + assertThat(mockHttpClient.getLastRequest().getUri().getHost()).isEqualTo("localhost"); + assertThat(capturedBody.get()).contains("\"StringMember\":\"123\""); + + asyncClient(override).operationWithHostPrefix(b -> b.stringMember("123")).join(); + assertThat(mockAsyncHttpClient.getLastRequest().getUri().getHost()).isEqualTo("localhost"); + } + + @Test + public void explicitFalse_beatsSystemProperty_hostPrefixIsApplied() { + System.setProperty(PROPERTY, "true"); + ClientOverrideConfiguration override = + emptyProfileOverride().toBuilder() + .putAdvancedOption(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION, false) + .build(); + syncClient(override).operationWithHostPrefix(b -> b.stringMember("123")); + assertThat(mockHttpClient.getLastRequest().getUri().getHost()).isEqualTo("123-foo.localhost"); + + asyncClient(override).operationWithHostPrefix(b -> b.stringMember("123")).join(); + assertThat(mockAsyncHttpClient.getLastRequest().getUri().getHost()).isEqualTo("123-foo.localhost"); + } + + private ProtocolRestJsonClient syncClient(ClientOverrideConfiguration override) { + ClientOverrideConfiguration withCapture = override.toBuilder() + .addExecutionInterceptor(new BodyCapturingInterceptor()) + .build(); + return ProtocolRestJsonClient.builder() + .credentialsProvider(credentials()) + .region(Region.US_EAST_1) + .endpointOverride(URI.create("http://localhost")) + .httpClient(mockHttpClient) + .overrideConfiguration(withCapture) + .build(); + } + + private ProtocolRestJsonAsyncClient asyncClient(ClientOverrideConfiguration override) { + return ProtocolRestJsonAsyncClient.builder() + .credentialsProvider(credentials()) + .region(Region.US_EAST_1) + .endpointOverride(URI.create("http://localhost")) + .httpClient(mockAsyncHttpClient) + .overrideConfiguration(override) + .build(); + } + + private static ClientOverrideConfiguration emptyProfileOverride() { + return profileOverride(""); + } + + private static ClientOverrideConfiguration profileOverride(String content) { + ProfileFile profileFile = ProfileFile.builder() + .content(new StringInputStream(content)) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + return ClientOverrideConfiguration.builder() + .defaultProfileFileSupplier(() -> profileFile) + .defaultProfileName("test") + .build(); + } + + private static StaticCredentialsProvider credentials() { + return StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")); + } + + private static HttpExecuteResponse okResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(""))) + .build(); + } + + private class BodyCapturingInterceptor implements ExecutionInterceptor { + @Override + public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + context.requestBody().ifPresent(body -> capturedBody.set( + invokeSafely(() -> IoUtils.toUtf8String(body.contentStreamProvider().newStream())))); + } + } +}