Skip to content

Commit

Permalink
ALPN H2 support in Netty client
Browse files Browse the repository at this point in the history
  • Loading branch information
davidh44 committed Jan 14, 2025
1 parent 5a6d4b5 commit 050d7ce
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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;

import software.amazon.awssdk.annotations.SdkPublicApi;

/**
* The protocol negotiation used by the HTTP client to establish connections
*/
@SdkPublicApi
public enum ProtocolNegotiation {

/**
* Uses prior knowledge
*/
ASSUME_PROTOCOL,

/**
* Uses Application Level Protocol Negotiation
*/
ALPN
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ public final class SdkHttpConfigurationOption<T> extends AttributeMap.Key<T> {
public static final SdkHttpConfigurationOption<Protocol> PROTOCOL =
new SdkHttpConfigurationOption<>("Protocol", Protocol.class);

/**
* HTTP protocol negotiation to use.
*/
public static final SdkHttpConfigurationOption<ProtocolNegotiation> PROTOCOL_NEGOTIATION =
new SdkHttpConfigurationOption<>("ProtocolNegotiation", ProtocolNegotiation.class);

/**
* Maximum number of requests allowed to wait for a connection.
*/
Expand Down Expand Up @@ -148,6 +154,7 @@ public final class SdkHttpConfigurationOption<T> extends AttributeMap.Key<T> {
private static final Boolean DEFAULT_TRUST_ALL_CERTIFICATES = Boolean.FALSE;

private static final Protocol DEFAULT_PROTOCOL = Protocol.HTTP1_1;
private static final ProtocolNegotiation DEFAULT_PROTOCOL_NEGOTIATION = ProtocolNegotiation.ASSUME_PROTOCOL;

private static final TlsTrustManagersProvider DEFAULT_TLS_TRUST_MANAGERS_PROVIDER = null;
private static final TlsKeyManagersProvider DEFAULT_TLS_KEY_MANAGERS_PROVIDER = SystemPropertyTlsKeyManagersProvider.create();
Expand All @@ -163,6 +170,7 @@ public final class SdkHttpConfigurationOption<T> extends AttributeMap.Key<T> {
.put(MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
.put(MAX_PENDING_CONNECTION_ACQUIRES, DEFAULT_MAX_CONNECTION_ACQUIRES)
.put(PROTOCOL, DEFAULT_PROTOCOL)
.put(PROTOCOL_NEGOTIATION, DEFAULT_PROTOCOL_NEGOTIATION)
.put(TRUST_ALL_CERTIFICATES, DEFAULT_TRUST_ALL_CERTIFICATES)
.put(REAP_IDLE_CONNECTIONS, DEFAULT_REAP_IDLE_CONNECTIONS)
.put(TCP_KEEPALIVE, DEFAULT_TCP_KEEPALIVE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static software.amazon.awssdk.crtcore.CrtConfigurationUtils.resolveHttpMonitoringOptions;
import static software.amazon.awssdk.crtcore.CrtConfigurationUtils.resolveProxy;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.PROTOCOL;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.PROTOCOL_NEGOTIATION;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.buildSocketOptions;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.resolveCipherPreference;
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
Expand All @@ -37,6 +38,7 @@
import software.amazon.awssdk.crt.io.TlsContext;
import software.amazon.awssdk.crt.io.TlsContextOptions;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.ProtocolNegotiation;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.crt.internal.AwsCrtClientBuilderBase;
Expand Down Expand Up @@ -73,6 +75,11 @@ abstract class AwsCrtHttpClientBase implements SdkAutoCloseable {
+ "NettyNioAsyncHttpClient instead.");
}

if (config.get(PROTOCOL_NEGOTIATION) == ProtocolNegotiation.ALPN) {
throw new UnsupportedOperationException("LPN is not supported in AwsCrtHttpClient yet. Use "
+ "NettyNioAsyncHttpClient instead.");
}

try (ClientBootstrap clientBootstrap = new ClientBootstrap(null, null);
SocketOptions clientSocketOptions = buildSocketOptions(builder.getTcpKeepAliveConfiguration(),
config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_FUTURE_TIMEOUT_SECONDS;
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_QUIET_PERIOD_SECONDS;
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_TIMEOUT_SECONDS;
import static software.amazon.awssdk.http.nio.netty.internal.utils.NettyUtils.isAlpnSupported;
import static software.amazon.awssdk.http.nio.netty.internal.utils.NettyUtils.runAndLogError;
import static software.amazon.awssdk.http.nio.netty.internal.utils.NettyUtils.validateAlpnSupported;
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;

import io.netty.channel.ChannelOption;
Expand All @@ -37,6 +39,7 @@
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.ProtocolNegotiation;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.SystemPropertyTlsKeyManagersProvider;
Expand Down Expand Up @@ -86,6 +89,8 @@ public final class NettyNioAsyncHttpClient implements SdkAsyncHttpClient {
private NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefaultsMap) {
this.configuration = new NettyConfiguration(serviceDefaultsMap);
Protocol protocol = serviceDefaultsMap.get(SdkHttpConfigurationOption.PROTOCOL);
ProtocolNegotiation protocolNegotiation = resolveProtocolNegotiation(builder.protocolNegotiation, serviceDefaultsMap,
protocol);
this.sdkEventLoopGroup = eventLoopGroup(builder);

Http2Configuration http2Configuration = builder.http2Configuration;
Expand All @@ -97,6 +102,7 @@ private NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefa
.sdkChannelOptions(builder.sdkChannelOptions)
.configuration(configuration)
.protocol(protocol)
.protocolNegotiation(protocolNegotiation)
.maxStreams(maxStreams)
.initialWindowSize(initialWindowSize)
.healthCheckPingPeriod(resolveHealthCheckPingPeriod(http2Configuration))
Expand Down Expand Up @@ -162,6 +168,36 @@ private SslProvider resolveSslProvider(DefaultBuilder builder) {
return SslContext.defaultClientProvider();
}

private ProtocolNegotiation resolveProtocolNegotiation(ProtocolNegotiation userSetValue, AttributeMap serviceDefaultsMap,
Protocol protocol) {
if (userSetValue == ProtocolNegotiation.ALPN) {
// TODO - remove once we implement support for ALPN with HTTP1
if (protocol == Protocol.HTTP1_1) {
throw new UnsupportedOperationException("ALPN with HTTP1 is not yet supported, use prior knowledge instead with "
+ "ProtocolNegotiation.ASSUME_PROTOCOL, or use ALPN with H2.");
}

// throw error if not supported and user set ALPN
validateAlpnSupported();
return ProtocolNegotiation.ALPN;
}
if (userSetValue == ProtocolNegotiation.ASSUME_PROTOCOL) {
return ProtocolNegotiation.ASSUME_PROTOCOL;
}

ProtocolNegotiation protocolNegotiation = serviceDefaultsMap.get(SdkHttpConfigurationOption.PROTOCOL_NEGOTIATION);
if (protocolNegotiation == ProtocolNegotiation.ALPN) {
if (!isAlpnSupported()) {
// fallback to prior knowledge if not supported and SDK defaults to ALPN
protocolNegotiation = ProtocolNegotiation.ASSUME_PROTOCOL;
log.warn(null, () -> "ALPN is not supported in the current Java version, falling back to prior knowledge for "
+ "protocol negotiation");
}
}

return protocolNegotiation;
}

private long resolveMaxHttp2Streams(Integer topLevelValue, Http2Configuration http2Configuration) {
if (topLevelValue != null) {
return topLevelValue;
Expand Down Expand Up @@ -371,6 +407,20 @@ public interface Builder extends SdkAsyncHttpClient.Builder<NettyNioAsyncHttpCli
*/
Builder protocol(Protocol protocol);

/**
* If set to {@code ProtocolNegotiation.ALPN}, the request will be made using ALPN, without a fallback protocol.
* If ALPN is not supported by the server an exception will be thrown.
* <p>
* Default value is true for services with H2 protocol setting, with the exception of the following services:
* Kinesis, TranscribeStreaming, Lex Runtime v2, Q Business
* <p>
* Note: For Java 8, ALPN is only supported in versions 1.8.0_251 and newer.
* If on unsupported Java version:
* 1) Default SDK setting of true -> SDK will fallback to prior knowledge and not use ALPN
* 2) User explicitly sets value of true -> Exception will be thrown
*/
Builder protocolNegotiation(ProtocolNegotiation protocolNegotiation);

/**
* Configure whether to enable or disable TCP KeepAlive.
* The configuration will be passed to the socket option {@link SocketOptions#SO_KEEPALIVE}.
Expand Down Expand Up @@ -503,6 +553,7 @@ private static final class DefaultBuilder implements Builder {
private SslProvider sslProvider;
private ProxyConfiguration proxyConfiguration = ProxyConfiguration.builder().build();
private Boolean useNonBlockingDnsResolver;
private ProtocolNegotiation protocolNegotiation;

private DefaultBuilder() {
}
Expand Down Expand Up @@ -640,8 +691,14 @@ public Builder protocol(Protocol protocol) {
return this;
}

public void setProtocol(Protocol protocol) {
protocol(protocol);
@Override
public Builder protocolNegotiation(ProtocolNegotiation protocolNegotiation) {
this.protocolNegotiation = protocolNegotiation;
return this;
}

public void setProtocolNegotiation(ProtocolNegotiation protocolNegotiation) {
protocolNegotiation(protocolNegotiation);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.ProtocolNegotiation;
import software.amazon.awssdk.http.nio.netty.ProxyConfiguration;
import software.amazon.awssdk.http.nio.netty.SdkEventLoopGroup;
import software.amazon.awssdk.http.nio.netty.internal.http2.HttpOrHttp2ChannelPool;
Expand Down Expand Up @@ -76,6 +77,7 @@ public void channelCreated(Channel ch) throws Exception {

private final NettyConfiguration configuration;
private final Protocol protocol;
private final ProtocolNegotiation protocolNegotiation;
private final long maxStreams;
private final Duration healthCheckPingPeriod;
private final int initialWindowSize;
Expand All @@ -88,13 +90,14 @@ public void channelCreated(Channel ch) throws Exception {
private AwaitCloseChannelPoolMap(Builder builder, Function<Builder, BootstrapProvider> createBootStrapProvider) {
this.configuration = builder.configuration;
this.protocol = builder.protocol;
this.protocolNegotiation = builder.protocolNegotiation;
this.maxStreams = builder.maxStreams;
this.healthCheckPingPeriod = builder.healthCheckPingPeriod;
this.initialWindowSize = builder.initialWindowSize;
this.sslProvider = builder.sslProvider;
this.proxyConfiguration = builder.proxyConfiguration;
this.bootstrapProvider = createBootStrapProvider.apply(builder);
this.sslContextProvider = new SslContextProvider(configuration, protocol, sslProvider);
this.sslContextProvider = new SslContextProvider(configuration, protocol, protocolNegotiation, sslProvider);
this.useNonBlockingDnsResolver = builder.useNonBlockingDnsResolver;
}

Expand Down Expand Up @@ -126,6 +129,7 @@ protected SimpleChannelPoolAwareChannelPool newPool(URI key) {
AtomicReference<ChannelPool> channelPoolRef = new AtomicReference<>();

ChannelPipelineInitializer pipelineInitializer = new ChannelPipelineInitializer(protocol,
protocolNegotiation,
sslContext,
sslProvider,
maxStreams,
Expand Down Expand Up @@ -282,6 +286,7 @@ public static class Builder {
private SdkEventLoopGroup sdkEventLoopGroup;
private NettyConfiguration configuration;
private Protocol protocol;
private ProtocolNegotiation protocolNegotiation;
private long maxStreams;
private int initialWindowSize;
private Duration healthCheckPingPeriod;
Expand Down Expand Up @@ -312,6 +317,11 @@ public Builder protocol(Protocol protocol) {
return this;
}

public Builder protocolNegotiation(ProtocolNegotiation protocolNegotiation) {
this.protocolNegotiation = protocolNegotiation;
return this;
}

public Builder maxStreams(long maxStreams) {
this.maxStreams = maxStreams;
return this;
Expand Down
Loading

0 comments on commit 050d7ce

Please sign in to comment.