Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-83dd6ab.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Add HTTP client configuration type metadata to the User-Agent header, tracking whether the HTTP client was auto-detected from the classpath or explicitly configured by the user."
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ private AwsExecutionContextBuilder() {
AwsSignerExecutionAttribute.AWS_CREDENTIALS).orElse(null)));

putStreamingInputOutputTypesMetadata(executionAttributes, executionParams);
putHttpClientConfigTypeMetadata(executionAttributes, clientConfig);

return ExecutionContext.builder()
.interceptorChain(executionInterceptorChain)
Expand All @@ -183,53 +184,54 @@ private AwsExecutionContextBuilder() {

private static <InputT extends SdkRequest, OutputT extends SdkResponse> void putStreamingInputOutputTypesMetadata(
ExecutionAttributes executionAttributes, ClientExecutionParams<InputT, OutputT> executionParams) {
List<AdditionalMetadata> userAgentMetadata = new ArrayList<>();

if (executionParams.getRequestBody() != null) {
userAgentMetadata.add(
AdditionalMetadata
.builder()
.name("rb")
.value(ContentStreamProvider.ProviderType.shortValueFromName(
executionParams.getRequestBody().contentStreamProvider().name())
)
.build());
addUserAgentMetadata(executionAttributes, "rb",
ContentStreamProvider.ProviderType.shortValueFromName(
executionParams.getRequestBody().contentStreamProvider().name()));
}

if (executionParams.getAsyncRequestBody() != null) {
userAgentMetadata.add(
AdditionalMetadata
.builder()
.name("rb")
.value(AsyncRequestBody.BodyType.shortValueFromName(
executionParams.getAsyncRequestBody().body())
)
.build());
addUserAgentMetadata(executionAttributes, "rb",
AsyncRequestBody.BodyType.shortValueFromName(
executionParams.getAsyncRequestBody().body()));
}

if (executionParams.getResponseTransformer() != null) {
userAgentMetadata.add(
AdditionalMetadata
.builder()
.name("rt")
.value(ResponseTransformer.TransformerType.shortValueFromName(
executionParams.getResponseTransformer().name())
)
.build());
addUserAgentMetadata(executionAttributes, "rt",
ResponseTransformer.TransformerType.shortValueFromName(
executionParams.getResponseTransformer().name()));
}

if (executionParams.getAsyncResponseTransformer() != null) {
userAgentMetadata.add(
AdditionalMetadata
.builder()
.name("rt")
.value(AsyncResponseTransformer.TransformerType.shortValueFromName(
executionParams.getAsyncResponseTransformer().name())
)
.build());
addUserAgentMetadata(executionAttributes, "rt",
AsyncResponseTransformer.TransformerType.shortValueFromName(
executionParams.getAsyncResponseTransformer().name()));
}
}

executionAttributes.putAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA, userAgentMetadata);
private static void putHttpClientConfigTypeMetadata(ExecutionAttributes executionAttributes,
SdkClientConfiguration clientConfig) {
String httpClientConfigType = clientConfig.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE);
if (httpClientConfigType == null) {
return;
}
addUserAgentMetadata(executionAttributes, "hc", httpClientConfigType);
}

private static void addUserAgentMetadata(ExecutionAttributes executionAttributes, String name, String value) {
List<AdditionalMetadata> metadata = executionAttributes.getAttribute(
SdkInternalExecutionAttribute.USER_AGENT_METADATA);
if (metadata == null) {
metadata = new ArrayList<>();
executionAttributes.putAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA, metadata);
}
metadata.add(
AdditionalMetadata
.builder()
.name(name)
.value(value)
.build());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,128 @@ public void invokeInterceptorsAndCreateExecutionContext_withAsyncResponseTransfo
);
}

@Test
public void invokeInterceptorsAndCreateExecutionContext_withDefaultHttpClient_addsHcMetadata() {
SdkClientConfiguration clientConfig = testClientConfiguration()
.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE, "d")
.build();

ExecutionContext executionContext =
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), clientConfig);

ExecutionAttributes executionAttributes = executionContext.executionAttributes();
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
Collections.singletonList(AdditionalMetadata.builder().name("hc").value("d").build())
);
}

@Test
public void invokeInterceptorsAndCreateExecutionContext_withExplicitHttpClient_addsHcMetadata() {
SdkClientConfiguration clientConfig = testClientConfiguration()
.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE, "e")
.build();

ExecutionContext executionContext =
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), clientConfig);

ExecutionAttributes executionAttributes = executionContext.executionAttributes();
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
Collections.singletonList(AdditionalMetadata.builder().name("hc").value("e").build())
);
}

@Test
public void invokeInterceptorsAndCreateExecutionContext_withRequestBodyAndHcMetadata_addsBoth() throws IOException {
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
File testFile = File.createTempFile("testFile", UUID.randomUUID().toString());
testFile.deleteOnExit();
executionParams.withRequestBody(RequestBody.fromFile(testFile));

SdkClientConfiguration clientConfig = testClientConfiguration()
.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE, "d")
.build();

ExecutionContext executionContext =
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfig);

ExecutionAttributes executionAttributes = executionContext.executionAttributes();
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
Arrays.asList(
AdditionalMetadata.builder().name("rb").value("f").build(),
AdditionalMetadata.builder().name("hc").value("d").build()
)
);
}

@Test
public void invokeInterceptorsAndCreateExecutionContext_withAsyncRequestBodyAndHcMetadata_addsBoth() throws IOException {
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
File testFile = File.createTempFile("testFile", UUID.randomUUID().toString());
testFile.deleteOnExit();
executionParams.withAsyncRequestBody(AsyncRequestBody.fromFile(testFile));

SdkClientConfiguration clientConfig = testClientConfiguration()
.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE, "e")
.build();

ExecutionContext executionContext =
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfig);

ExecutionAttributes executionAttributes = executionContext.executionAttributes();
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
Arrays.asList(
AdditionalMetadata.builder().name("rb").value("f").build(),
AdditionalMetadata.builder().name("hc").value("e").build()
)
);
}

@Test
public void invokeInterceptorsAndCreateExecutionContext_withResponseTransformerAndHcMetadata_addsBoth() throws IOException {
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
File testFile = File.createTempFile("testFile", UUID.randomUUID().toString());
testFile.deleteOnExit();
executionParams.withResponseTransformer(ResponseTransformer.toFile(testFile));

SdkClientConfiguration clientConfig = testClientConfiguration()
.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE, "d")
.build();

ExecutionContext executionContext =
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfig);

ExecutionAttributes executionAttributes = executionContext.executionAttributes();
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
Arrays.asList(
AdditionalMetadata.builder().name("rt").value("f").build(),
AdditionalMetadata.builder().name("hc").value("d").build()
)
);
}

@Test
public void invokeInterceptorsAndCreateExecutionContext_withAsyncResponseTransformerAndHcMetadata_addsBoth() throws IOException {
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
File testFile = File.createTempFile("testFile", UUID.randomUUID().toString());
testFile.deleteOnExit();
executionParams.withAsyncResponseTransformer(AsyncResponseTransformer.toFile(testFile));

SdkClientConfiguration clientConfig = testClientConfiguration()
.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE, "e")
.build();

ExecutionContext executionContext =
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfig);

ExecutionAttributes executionAttributes = executionContext.executionAttributes();
assertThat(executionAttributes.getAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA)).isEqualTo(
Arrays.asList(
AdditionalMetadata.builder().name("rt").value("f").build(),
AdditionalMetadata.builder().name("hc").value("e").build()
)
);
}

private ClientExecutionParams<SdkRequest, SdkResponse> clientExecutionParams() {
return clientExecutionParams(sdkRequest);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import static software.amazon.awssdk.core.client.config.SdkClientOption.DEFAULT_RETRY_MODE;
import static software.amazon.awssdk.core.client.config.SdkClientOption.EXECUTION_INTERCEPTORS;
import static software.amazon.awssdk.core.client.config.SdkClientOption.HTTP_CLIENT_CONFIG;
import static software.amazon.awssdk.core.client.config.SdkClientOption.HTTP_CLIENT_CONFIG_TYPE;
import static software.amazon.awssdk.core.client.config.SdkClientOption.IDENTITY_PROVIDERS;
import static software.amazon.awssdk.core.client.config.SdkClientOption.INTERNAL_USER_AGENT;
import static software.amazon.awssdk.core.client.config.SdkClientOption.METRIC_PUBLISHERS;
Expand Down Expand Up @@ -310,34 +311,55 @@ protected SdkClientConfiguration finalizeChildConfiguration(SdkClientConfigurati
*/
private SdkClientConfiguration finalizeSyncConfiguration(SdkClientConfiguration config) {
return config.toBuilder()
.option(HTTP_CLIENT_CONFIG_TYPE, resolveSyncHttpClientConfigType(config))
.lazyOption(SdkClientOption.SYNC_HTTP_CLIENT, c -> resolveSyncHttpClient(c, config))
.option(SdkClientOption.CLIENT_TYPE, SYNC)
.build();
}

private String resolveSyncHttpClientConfigType(SdkClientConfiguration config) {
SdkHttpClient httpClient = config.option(CONFIGURED_SYNC_HTTP_CLIENT);
SdkHttpClient.Builder<?> httpClientBuilder = config.option(CONFIGURED_SYNC_HTTP_CLIENT_BUILDER);
if (!(httpClient == null && httpClientBuilder == null)) {
return "e";
}
return "d";
}

/**
* Finalize async-specific configuration from the default-applied configuration.
*/
private SdkClientConfiguration finalizeAsyncConfiguration(SdkClientConfiguration config) {
return config.toBuilder()
.lazyOptionIfAbsent(FUTURE_COMPLETION_EXECUTOR, this::resolveAsyncFutureCompletionExecutor)
.option(HTTP_CLIENT_CONFIG_TYPE, resolveAsyncHttpClientConfigType(config))
.lazyOption(ASYNC_HTTP_CLIENT, c -> resolveAsyncHttpClient(c, config))
.option(SdkClientOption.CLIENT_TYPE, ASYNC)
.build();
}

private String resolveAsyncHttpClientConfigType(SdkClientConfiguration config) {
SdkAsyncHttpClient httpClient = config.option(CONFIGURED_ASYNC_HTTP_CLIENT);
SdkAsyncHttpClient.Builder<?> httpClientBuilder = config.option(CONFIGURED_ASYNC_HTTP_CLIENT_BUILDER);
if (!(httpClient == null && httpClientBuilder == null)) {
return "e";
}
return "d";
}

/**
* Finalize global configuration from the default-applied configuration.
*/
private SdkClientConfiguration finalizeConfiguration(SdkClientConfiguration config) {
return config.toBuilder()
SdkClientConfiguration.Builder builder = config.toBuilder()
.lazyOption(SCHEDULED_EXECUTOR_SERVICE, this::resolveScheduledExecutorService)
.lazyOptionIfAbsent(RETRY_STRATEGY, this::resolveRetryStrategy)
.option(EXECUTION_INTERCEPTORS, resolveExecutionInterceptors(config))
.lazyOption(CLIENT_USER_AGENT, this::resolveClientUserAgent)
.lazyOption(COMPRESSION_CONFIGURATION, this::resolveCompressionConfiguration)
.lazyOptionIfAbsent(IDENTITY_PROVIDERS, c -> IdentityProviders.builder().build())
.build();
.lazyOptionIfAbsent(IDENTITY_PROVIDERS, c -> IdentityProviders.builder().build());
builder.computeOptionIfAbsent(HTTP_CLIENT_CONFIG_TYPE, () -> "d");
return builder.build();
}

private CompressionConfiguration resolveCompressionConfiguration(LazyValueSource config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,18 @@ public final class SdkClientOption<T> extends ClientOption<T> {
public static final SdkClientOption<SdkHttpClient.Builder<?>> CONFIGURED_SYNC_HTTP_CLIENT_BUILDER =
new SdkClientOption<>(new UnsafeValueType(SdkAsyncHttpClient.Builder.class));

/**
* The HTTP client configuration type indicating how the HTTP client was selected.
* <p>
* Possible values:
* <ul>
* <li>{@code "d"} - Default: HTTP client was auto-detected from the classpath</li>
* <li>{@code "e"} - Explicit: HTTP client was explicitly configured by the user via
* {@code httpClient()} or {@code httpClientBuilder()} methods</li>
* </ul>
*/
public static final SdkClientOption<String> HTTP_CLIENT_CONFIG_TYPE = new SdkClientOption<>(String.class);

/**
* Configuration that should be used to build the {@link #SYNC_HTTP_CLIENT} or {@link #ASYNC_HTTP_CLIENT}.
*/
Expand Down
Loading
Loading