Skip to content
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

ALPN H2 Support for Netty Client #5794

Merged
merged 24 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
050d7ce
ALPN H2 support in Netty client
davidh44 Jan 14, 2025
815a0da
Add codegen customizations to skip ALPN for existing H2 services
davidh44 Jan 14, 2025
6df6a6c
Add changelog
davidh44 Jan 14, 2025
1a01e77
Add benchmarks
davidh44 Jan 14, 2025
1e99886
Add tests
davidh44 Jan 14, 2025
93d096a
Update test
davidh44 Jan 14, 2025
b557813
Update test
davidh44 Jan 14, 2025
59c8096
Address comments
davidh44 Jan 14, 2025
d54d3ac
Update test file name
davidh44 Jan 14, 2025
a0eab5f
Revert to checking Java version for ALPN support
davidh44 Jan 15, 2025
86b6638
Update Kinesis integ tests
davidh44 Jan 15, 2025
d2b2d3a
Propagate exception and close channel
davidh44 Jan 15, 2025
deb0bcd
Add tests and update ALPN support check for OpenSsl
davidh44 Jan 15, 2025
c041db7
Set max streams when completing protocol future
davidh44 Jan 15, 2025
5b60892
Add test dependencies
davidh44 Jan 15, 2025
40c3bfc
Do not use FATAL_ALERT if OpenSSL is used
davidh44 Jan 16, 2025
65353bb
Remove completing future in ALPN handler
davidh44 Jan 17, 2025
a3af0ca
Remove import
davidh44 Jan 17, 2025
f79e297
Merge branch 'master' into feature/master/alpn-h2-netty-client
davidh44 Jan 21, 2025
6caf668
Update ALPN support check to check for getApplicationProtocol method …
davidh44 Jan 21, 2025
0629830
Address comments
davidh44 Jan 22, 2025
1dca786
Merge branch 'master' into feature/master/alpn-h2-netty-client
davidh44 Jan 22, 2025
422b6ab
Use Lazy for alpn support check
davidh44 Jan 22, 2025
edee03f
Merge branch 'master' into feature/master/alpn-h2-netty-client
davidh44 Jan 22, 2025
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-abb9c7e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "Netty NIO HTTP Client",
"contributor": "",
"description": "Adds ALPN H2 support for Netty client"
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ public class CustomizationConfig {
*/
private boolean generateEndpointClientTests;

/**
* Whether to use prior knowledge protocol negotiation for H2
*/
private boolean usePriorKnowledgeForH2;

/**
* A mapping from the skipped test's description to the reason why it's being skipped.
*/
Expand Down Expand Up @@ -746,6 +751,14 @@ public void setGenerateEndpointClientTests(boolean generateEndpointClientTests)
this.generateEndpointClientTests = generateEndpointClientTests;
}

public boolean isUsePriorKnowledgeForH2() {
return usePriorKnowledgeForH2;
}

public void setUsePriorKnowledgeForH2(boolean usePriorKnowledgeForH2) {
this.usePriorKnowledgeForH2 = usePriorKnowledgeForH2;
}

public boolean useGlobalEndpoint() {
return useGlobalEndpoint;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import software.amazon.awssdk.core.retry.RetryMode;
import software.amazon.awssdk.core.signer.Signer;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.ProtocolNegotiation;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
import software.amazon.awssdk.identity.spi.IdentityProvider;
Expand Down Expand Up @@ -718,22 +719,25 @@ private MethodSpec beanStyleSetServiceConfigurationMethod() {
private void addServiceHttpConfigIfNeeded(TypeSpec.Builder builder, IntermediateModel model) {
String serviceDefaultFqcn = model.getCustomizationConfig().getServiceSpecificHttpConfig();
boolean supportsH2 = model.getMetadata().supportsH2();
boolean usePriorKnowledgeForH2 = model.getCustomizationConfig().isUsePriorKnowledgeForH2();

if (serviceDefaultFqcn != null || supportsH2) {
builder.addMethod(serviceSpecificHttpConfigMethod(serviceDefaultFqcn, supportsH2));
builder.addMethod(serviceSpecificHttpConfigMethod(serviceDefaultFqcn, supportsH2, usePriorKnowledgeForH2));
}
}

private MethodSpec serviceSpecificHttpConfigMethod(String serviceDefaultFqcn, boolean supportsH2) {
private MethodSpec serviceSpecificHttpConfigMethod(String serviceDefaultFqcn, boolean supportsH2,
boolean usePriorKnowledgeForH2) {
return MethodSpec.methodBuilder("serviceHttpConfig")
.addAnnotation(Override.class)
.addModifiers(PROTECTED, FINAL)
.returns(AttributeMap.class)
.addCode(serviceSpecificHttpConfigMethodBody(serviceDefaultFqcn, supportsH2))
.addCode(serviceSpecificHttpConfigMethodBody(serviceDefaultFqcn, supportsH2, usePriorKnowledgeForH2))
.build();
}

private CodeBlock serviceSpecificHttpConfigMethodBody(String serviceDefaultFqcn, boolean supportsH2) {
private CodeBlock serviceSpecificHttpConfigMethodBody(String serviceDefaultFqcn, boolean supportsH2,
boolean usePriorKnowledgeForH2) {
CodeBlock.Builder builder = CodeBlock.builder();

if (serviceDefaultFqcn != null) {
Expand All @@ -745,10 +749,16 @@ private CodeBlock serviceSpecificHttpConfigMethodBody(String serviceDefaultFqcn,
}

if (supportsH2) {
builder.addStatement("return result.merge(AttributeMap.builder()"
+ ".put($T.PROTOCOL, $T.HTTP2)"
+ ".build())",
SdkHttpConfigurationOption.class, Protocol.class);
builder.add("return result.merge(AttributeMap.builder()"
+ ".put($T.PROTOCOL, $T.HTTP2)",
SdkHttpConfigurationOption.class, Protocol.class);

if (!usePriorKnowledgeForH2) {
builder.add(".put($T.PROTOCOL_NEGOTIATION, $T.ALPN)",
SdkHttpConfigurationOption.class, ProtocolNegotiation.class);
}

builder.addStatement(".build())");
} else {
builder.addStatement("return result");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,36 @@ public static IntermediateModel serviceWithNoAuth() {
return new IntermediateModelBuilder(models).build();
}

public static IntermediateModel serviceWithH2() {
File serviceModel =
new File(ClientTestModels.class.getResource("client/c2j/service-with-h2/service-2.json").getFile());
File customizationModel =
new File(ClientTestModels.class.getResource("client/c2j/service-with-h2/customization.config")
.getFile());
C2jModels models = C2jModels
.builder()
.serviceModel(getServiceModel(serviceModel))
.customizationConfig(getCustomizationConfig(customizationModel))
.build();

return new IntermediateModelBuilder(models).build();
}

public static IntermediateModel serviceWithH2UsePriorKnowledgeForH2() {
File serviceModel =
new File(ClientTestModels.class.getResource("client/c2j/service-with-h2-usePriorKnowledgeForH2/service-2.json").getFile());
File customizationModel =
new File(ClientTestModels.class.getResource("client/c2j/service-with-h2-usePriorKnowledgeForH2/customization.config")
.getFile());
C2jModels models = C2jModels
.builder()
.serviceModel(getServiceModel(serviceModel))
.customizationConfig(getCustomizationConfig(customizationModel))
.build();

return new IntermediateModelBuilder(models).build();
}

public static IntermediateModel serviceMiniS3() {
File serviceModel =
new File(ClientTestModels.class.getResource("client/c2j/mini-s3/service-2.json").getFile());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModelsEndpointAuthParamsWithAllowList;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.serviceWithH2;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.serviceWithH2UsePriorKnowledgeForH2;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.serviceWithNoAuth;
import static software.amazon.awssdk.codegen.poet.builder.BuilderClassTestUtils.validateGeneration;

Expand Down Expand Up @@ -117,6 +119,16 @@ void syncComposedDefaultClientBuilderClass_sra() {
"test-composed-sync-default-client-builder.java", true);
}

@Test
void baseClientBuilderClassWithH2() {
validateBaseClientBuilderClassGeneration(serviceWithH2(), "test-h2-service-client-builder-class.java");
}

@Test
void baseClientBuilderClassWithH2_usePriorKnowledgeForH2() {
validateBaseClientBuilderClassGeneration(serviceWithH2UsePriorKnowledgeForH2(), "test-h2-usePriorKnowledgeForH2-service-client-builder-class.java");
}

private void validateBaseClientBuilderClassGeneration(IntermediateModel model, String expectedClassName) {
validateBaseClientBuilderClassGeneration(model, expectedClassName, false);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package software.amazon.awssdk.services.h2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import software.amazon.awssdk.annotations.Generated;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.auth.signer.Aws4Signer;
import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder;
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider;
import software.amazon.awssdk.awscore.retry.AwsRetryStrategy;
import software.amazon.awssdk.core.SdkPlugin;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
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.interceptor.ClasspathInterceptorChainFactory;
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
import software.amazon.awssdk.core.retry.RetryMode;
import software.amazon.awssdk.core.signer.Signer;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.ProtocolNegotiation;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.identity.spi.IdentityProvider;
import software.amazon.awssdk.identity.spi.IdentityProviders;
import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption;
import software.amazon.awssdk.retries.api.RetryStrategy;
import software.amazon.awssdk.services.h2.endpoints.H2EndpointProvider;
import software.amazon.awssdk.services.h2.endpoints.internal.H2RequestSetEndpointInterceptor;
import software.amazon.awssdk.services.h2.endpoints.internal.H2ResolveEndpointInterceptor;
import software.amazon.awssdk.services.h2.internal.H2ServiceClientConfigurationBuilder;
import software.amazon.awssdk.utils.AttributeMap;
import software.amazon.awssdk.utils.CollectionUtils;
import software.amazon.awssdk.utils.Validate;

/**
* Internal base class for {@link DefaultH2ClientBuilder} and {@link DefaultH2AsyncClientBuilder}.
*/
@Generated("software.amazon.awssdk:codegen")
@SdkInternalApi
abstract class DefaultH2BaseClientBuilder<B extends H2BaseClientBuilder<B, C>, C> extends AwsDefaultClientBuilder<B, C> {
@Override
protected final String serviceEndpointPrefix() {
return "h2-service";
}

@Override
protected final String serviceName() {
return "H2";
}

@Override
protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfiguration config) {
return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider())
.option(SdkAdvancedClientOption.SIGNER, defaultSigner())
.option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false));
}

@Override
protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) {
List<ExecutionInterceptor> endpointInterceptors = new ArrayList<>();
endpointInterceptors.add(new H2ResolveEndpointInterceptor());
endpointInterceptors.add(new H2RequestSetEndpointInterceptor());
ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory();
List<ExecutionInterceptor> interceptors = interceptorFactory
.getInterceptors("software/amazon/awssdk/services/h2/execution.interceptors");
List<ExecutionInterceptor> additionalInterceptors = new ArrayList<>();
interceptors = CollectionUtils.mergeLists(endpointInterceptors, interceptors);
interceptors = CollectionUtils.mergeLists(interceptors, additionalInterceptors);
interceptors = CollectionUtils.mergeLists(interceptors, config.option(SdkClientOption.EXECUTION_INTERCEPTORS));
SdkClientConfiguration.Builder builder = config.toBuilder();
builder.lazyOption(SdkClientOption.IDENTITY_PROVIDERS, c -> {
IdentityProviders.Builder result = IdentityProviders.builder();
IdentityProvider<?> credentialsIdentityProvider = c.get(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER);
if (credentialsIdentityProvider != null) {
result.putIdentityProvider(credentialsIdentityProvider);
}
return result.build();
});
builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors);
builder.lazyOptionIfAbsent(
SdkClientOption.CLIENT_ENDPOINT_PROVIDER,
c -> AwsClientEndpointProvider
.builder()
.serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_H2_SERVICE")
.serviceEndpointOverrideSystemProperty("aws.endpointUrlH2")
.serviceProfileProperty("h2_service")
.serviceEndpointPrefix(serviceEndpointPrefix())
.defaultProtocol("https")
.region(c.get(AwsClientOption.AWS_REGION))
.profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER))
.profileName(c.get(SdkClientOption.PROFILE_NAME))
.putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT,
c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT))
.dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED))
.fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build());
return builder.build();
}

private Signer defaultSigner() {
return Aws4Signer.create();
}

@Override
protected final String signingName() {
return "h2-service";
}

private H2EndpointProvider defaultEndpointProvider() {
return H2EndpointProvider.defaultProvider();
}

@Override
protected final AttributeMap serviceHttpConfig() {
AttributeMap result = AttributeMap.empty();
return result.merge(AttributeMap.builder().put(SdkHttpConfigurationOption.PROTOCOL, Protocol.HTTP2)
.put(SdkHttpConfigurationOption.PROTOCOL_NEGOTIATION, ProtocolNegotiation.ALPN).build());
}

@Override
protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) {
List<SdkPlugin> internalPlugins = internalPlugins(config);
List<SdkPlugin> externalPlugins = plugins();
if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) {
return config;
}
List<SdkPlugin> plugins = CollectionUtils.mergeLists(internalPlugins, externalPlugins);
SdkClientConfiguration.Builder configuration = config.toBuilder();
H2ServiceClientConfigurationBuilder serviceConfigBuilder = new H2ServiceClientConfigurationBuilder(configuration);
for (SdkPlugin plugin : plugins) {
plugin.configureClient(serviceConfigBuilder);
}
updateRetryStrategyClientConfiguration(configuration);
return configuration.build();
}

private void updateRetryStrategyClientConfiguration(SdkClientConfiguration.Builder configuration) {
ClientOverrideConfiguration.Builder builder = configuration.asOverrideConfigurationBuilder();
RetryMode retryMode = builder.retryMode();
if (retryMode != null) {
configuration.option(SdkClientOption.RETRY_STRATEGY, AwsRetryStrategy.forRetryMode(retryMode));
} else {
Consumer<RetryStrategy.Builder<?, ?>> configurator = builder.retryStrategyConfigurator();
if (configurator != null) {
RetryStrategy.Builder<?, ?> defaultBuilder = AwsRetryStrategy.defaultRetryStrategy().toBuilder();
configurator.accept(defaultBuilder);
configuration.option(SdkClientOption.RETRY_STRATEGY, defaultBuilder.build());
} else {
RetryStrategy retryStrategy = builder.retryStrategy();
if (retryStrategy != null) {
configuration.option(SdkClientOption.RETRY_STRATEGY, retryStrategy);
}
}
}
configuration.option(SdkClientOption.CONFIGURED_RETRY_MODE, null);
configuration.option(SdkClientOption.CONFIGURED_RETRY_STRATEGY, null);
configuration.option(SdkClientOption.CONFIGURED_RETRY_CONFIGURATOR, null);
}

private List<SdkPlugin> internalPlugins(SdkClientConfiguration config) {
return Collections.emptyList();
}

protected static void validateClientOptions(SdkClientConfiguration c) {
Validate.notNull(c.option(SdkAdvancedClientOption.SIGNER),
"The 'overrideConfiguration.advancedOption[SIGNER]' must be configured in the client builder.");
}
}
Loading
Loading