Skip to content

Commit 2e77e5d

Browse files
committed
Add CRT HTTP/2 support
1 parent 23fd3c9 commit 2e77e5d

28 files changed

+915
-650
lines changed

http-clients/aws-crt-client/pom.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,31 @@
173173
<artifactId>mockito-junit-jupiter</artifactId>
174174
<scope>test</scope>
175175
</dependency>
176+
<dependency>
177+
<groupId>io.netty</groupId>
178+
<artifactId>netty-codec-http2</artifactId>
179+
<scope>test</scope>
180+
</dependency>
181+
<dependency>
182+
<groupId>io.netty</groupId>
183+
<artifactId>netty-common</artifactId>
184+
<scope>test</scope>
185+
</dependency>
186+
<dependency>
187+
<groupId>io.netty</groupId>
188+
<artifactId>netty-transport</artifactId>
189+
<scope>test</scope>
190+
</dependency>
191+
<dependency>
192+
<groupId>io.netty</groupId>
193+
<artifactId>netty-codec-http</artifactId>
194+
<scope>test</scope>
195+
</dependency>
196+
<dependency>
197+
<groupId>io.netty</groupId>
198+
<artifactId>netty-handler</artifactId>
199+
<scope>test</scope>
200+
</dependency>
176201
</dependencies>
177202

178203
<build>

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClient.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
import java.util.concurrent.CompletableFuture;
2323
import java.util.function.Consumer;
2424
import software.amazon.awssdk.annotations.SdkPublicApi;
25-
import software.amazon.awssdk.crt.http.HttpClientConnectionManager;
25+
import software.amazon.awssdk.crt.http.HttpStreamManager;
26+
import software.amazon.awssdk.http.Protocol;
2627
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
2728
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
2829
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
@@ -91,15 +92,15 @@ public CompletableFuture<Void> execute(AsyncExecuteRequest asyncRequest) {
9192
* we have a pool and no one can destroy it underneath us until we've finished submitting the
9293
* request)
9394
*/
94-
try (HttpClientConnectionManager crtConnPool = getOrCreateConnectionPool(poolKey(asyncRequest.request()))) {
95-
CrtAsyncRequestContext context = CrtAsyncRequestContext.builder()
96-
.crtConnPool(crtConnPool)
97-
.readBufferSize(this.readBufferSize)
98-
.request(asyncRequest)
99-
.build();
100-
101-
return new CrtAsyncRequestExecutor().execute(context);
102-
}
95+
HttpStreamManager crtConnPool = getOrCreateConnectionPool(poolKey(asyncRequest.request()));
96+
CrtAsyncRequestContext context = CrtAsyncRequestContext.builder()
97+
.crtConnPool(crtConnPool)
98+
.readBufferSize(this.readBufferSize)
99+
.request(asyncRequest)
100+
.protocol(this.protocol)
101+
.build();
102+
103+
return new CrtAsyncRequestExecutor().execute(context);
103104
}
104105

105106
/**
@@ -222,6 +223,14 @@ AwsCrtAsyncHttpClient.Builder connectionHealthConfiguration(Consumer<ConnectionH
222223
AwsCrtAsyncHttpClient.Builder tcpKeepAliveConfiguration(Consumer<TcpKeepAliveConfiguration.Builder>
223224
tcpKeepAliveConfigurationBuilder);
224225

226+
/**
227+
* Configure the HTTP protocol version to use for connections.
228+
*
229+
* @param protocol the HTTP protocol version
230+
* @return The builder for method chaining.
231+
*/
232+
AwsCrtAsyncHttpClient.Builder protocol(Protocol protocol);
233+
225234
/**
226235
* Configure whether to enable a hybrid post-quantum key exchange option for the Transport Layer Security (TLS) network
227236
* encryption protocol when communicating with services that support Post Quantum TLS. If Post Quantum cipher suites are
@@ -246,6 +255,13 @@ AwsCrtAsyncHttpClient.Builder tcpKeepAliveConfiguration(Consumer<TcpKeepAliveCon
246255
private static final class DefaultAsyncBuilder
247256
extends AwsCrtClientBuilderBase<AwsCrtAsyncHttpClient.Builder> implements Builder {
248257

258+
259+
@Override
260+
public Builder protocol(Protocol protocol) {
261+
getAttributeMap().put(SdkHttpConfigurationOption.PROTOCOL, protocol);
262+
return this;
263+
}
264+
249265
@Override
250266
public SdkAsyncHttpClient build() {
251267
return new AwsCrtAsyncHttpClient(this, getAttributeMap().build()
@@ -258,5 +274,6 @@ public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
258274
.merge(serviceDefaults)
259275
.merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS));
260276
}
277+
261278
}
262279
}

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClient.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@
2323
import java.util.concurrent.CompletionException;
2424
import java.util.function.Consumer;
2525
import software.amazon.awssdk.annotations.SdkPublicApi;
26-
import software.amazon.awssdk.crt.http.HttpClientConnectionManager;
2726
import software.amazon.awssdk.crt.http.HttpException;
27+
import software.amazon.awssdk.crt.http.HttpStreamManager;
2828
import software.amazon.awssdk.http.ExecutableHttpRequest;
2929
import software.amazon.awssdk.http.HttpExecuteRequest;
3030
import software.amazon.awssdk.http.HttpExecuteResponse;
31+
import software.amazon.awssdk.http.Protocol;
3132
import software.amazon.awssdk.http.SdkHttpClient;
3233
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
3334
import software.amazon.awssdk.http.SdkHttpFullResponse;
@@ -56,6 +57,9 @@ public final class AwsCrtHttpClient extends AwsCrtHttpClientBase implements SdkH
5657

5758
private AwsCrtHttpClient(DefaultBuilder builder, AttributeMap config) {
5859
super(builder, config);
60+
if (this.protocol == Protocol.HTTP2) {
61+
throw new UnsupportedOperationException("HTTP/2 is not supported in sync client. Use AwsCrtAsyncHttpClient instead");
62+
}
5963
}
6064

6165
public static AwsCrtHttpClient.Builder builder() {
@@ -91,14 +95,13 @@ public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
9195
* we have a pool and no one can destroy it underneath us until we've finished submitting the
9296
* request)
9397
*/
94-
try (HttpClientConnectionManager crtConnPool = getOrCreateConnectionPool(poolKey(request.httpRequest()))) {
95-
CrtRequestContext context = CrtRequestContext.builder()
96-
.crtConnPool(crtConnPool)
97-
.readBufferSize(this.readBufferSize)
98-
.request(request)
99-
.build();
100-
return new CrtHttpRequest(context);
101-
}
98+
HttpStreamManager crtConnPool = getOrCreateConnectionPool(poolKey(request.httpRequest()));
99+
CrtRequestContext context = CrtRequestContext.builder()
100+
.crtConnPool(crtConnPool)
101+
.readBufferSize(this.readBufferSize)
102+
.request(request)
103+
.build();
104+
return new CrtHttpRequest(context);
102105
}
103106

104107
private static final class CrtHttpRequest implements ExecutableHttpRequest {
@@ -140,7 +143,7 @@ public HttpExecuteResponse call() throws IOException {
140143
@Override
141144
public void abort() {
142145
if (responseFuture != null) {
143-
responseFuture.completeExceptionally(new IOException("Request ws cancelled"));
146+
responseFuture.completeExceptionally(new IOException("Request was cancelled"));
144147
}
145148
}
146149
}

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBase.java

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@
2828
import java.util.concurrent.ConcurrentHashMap;
2929
import software.amazon.awssdk.annotations.SdkProtectedApi;
3030
import software.amazon.awssdk.crt.CrtResource;
31-
import software.amazon.awssdk.crt.http.HttpClientConnectionManager;
31+
import software.amazon.awssdk.crt.http.Http2StreamManagerOptions;
3232
import software.amazon.awssdk.crt.http.HttpClientConnectionManagerOptions;
3333
import software.amazon.awssdk.crt.http.HttpMonitoringOptions;
3434
import software.amazon.awssdk.crt.http.HttpProxyOptions;
35+
import software.amazon.awssdk.crt.http.HttpStreamManager;
36+
import software.amazon.awssdk.crt.http.HttpStreamManagerOptions;
37+
import software.amazon.awssdk.crt.http.HttpVersion;
3538
import software.amazon.awssdk.crt.io.ClientBootstrap;
3639
import software.amazon.awssdk.crt.io.SocketOptions;
3740
import software.amazon.awssdk.crt.io.TlsContext;
@@ -57,7 +60,8 @@ abstract class AwsCrtHttpClientBase implements SdkAutoCloseable {
5760
private static final long DEFAULT_STREAM_WINDOW_SIZE = 16L * 1024L * 1024L; // 16 MB
5861

5962
protected final long readBufferSize;
60-
private final Map<URI, HttpClientConnectionManager> connectionPools = new ConcurrentHashMap<>();
63+
protected final Protocol protocol;
64+
private final Map<URI, HttpStreamManager> connectionPools = new ConcurrentHashMap<>();
6165
private final LinkedList<CrtResource> ownedSubResources = new LinkedList<>();
6266
private final ClientBootstrap bootstrap;
6367
private final SocketOptions socketOptions;
@@ -70,31 +74,30 @@ abstract class AwsCrtHttpClientBase implements SdkAutoCloseable {
7074
private boolean isClosed = false;
7175

7276
AwsCrtHttpClientBase(AwsCrtClientBuilderBase builder, AttributeMap config) {
73-
if (config.get(PROTOCOL) == Protocol.HTTP2) {
74-
throw new UnsupportedOperationException("HTTP/2 is not supported in AwsCrtHttpClient yet. Use "
75-
+ "NettyNioAsyncHttpClient instead.");
77+
ClientBootstrap clientBootstrap = new ClientBootstrap(null, null);
78+
SocketOptions clientSocketOptions = buildSocketOptions(builder.getTcpKeepAliveConfiguration(),
79+
config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT));
80+
TlsContextOptions clientTlsContextOptions =
81+
TlsContextOptions.createDefaultClient()
82+
.withCipherPreference(resolveCipherPreference(builder.getPostQuantumTlsEnabled()))
83+
.withVerifyPeer(!config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES));
84+
this.protocol = config.get(PROTOCOL);
85+
if (protocol == Protocol.HTTP2) {
86+
clientTlsContextOptions = clientTlsContextOptions.withAlpnList("h2");
7687
}
7788

78-
try (ClientBootstrap clientBootstrap = new ClientBootstrap(null, null);
79-
SocketOptions clientSocketOptions = buildSocketOptions(builder.getTcpKeepAliveConfiguration(),
80-
config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT));
81-
TlsContextOptions clientTlsContextOptions =
82-
TlsContextOptions.createDefaultClient()
83-
.withCipherPreference(resolveCipherPreference(builder.getPostQuantumTlsEnabled()))
84-
.withVerifyPeer(!config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES));
85-
TlsContext clientTlsContext = new TlsContext(clientTlsContextOptions)) {
86-
87-
this.bootstrap = registerOwnedResource(clientBootstrap);
88-
this.socketOptions = registerOwnedResource(clientSocketOptions);
89-
this.tlsContext = registerOwnedResource(clientTlsContext);
90-
this.readBufferSize = builder.getReadBufferSizeInBytes() == null ?
91-
DEFAULT_STREAM_WINDOW_SIZE : builder.getReadBufferSizeInBytes();
92-
this.maxConnectionsPerEndpoint = config.get(SdkHttpConfigurationOption.MAX_CONNECTIONS);
93-
this.monitoringOptions = resolveHttpMonitoringOptions(builder.getConnectionHealthConfiguration()).orElse(null);
94-
this.maxConnectionIdleInMilliseconds = config.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis();
95-
this.connectionAcquisitionTimeout = config.get(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT).toMillis();
96-
this.proxyOptions = resolveProxy(builder.getProxyConfiguration(), tlsContext).orElse(null);
97-
}
89+
TlsContext clientTlsContext = new TlsContext(clientTlsContextOptions);
90+
91+
this.bootstrap = registerOwnedResource(clientBootstrap);
92+
this.socketOptions = registerOwnedResource(clientSocketOptions);
93+
this.tlsContext = registerOwnedResource(clientTlsContext);
94+
this.readBufferSize = builder.getReadBufferSizeInBytes() == null ?
95+
DEFAULT_STREAM_WINDOW_SIZE : builder.getReadBufferSizeInBytes();
96+
this.maxConnectionsPerEndpoint = config.get(SdkHttpConfigurationOption.MAX_CONNECTIONS);
97+
this.monitoringOptions = resolveHttpMonitoringOptions(builder.getConnectionHealthConfiguration()).orElse(null);
98+
this.maxConnectionIdleInMilliseconds = config.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis();
99+
this.connectionAcquisitionTimeout = config.get(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT).toMillis();
100+
this.proxyOptions = resolveProxy(builder.getProxyConfiguration(), tlsContext).orElse(null);
98101
}
99102

100103
/**
@@ -106,7 +109,6 @@ abstract class AwsCrtHttpClientBase implements SdkAutoCloseable {
106109
*/
107110
private <T extends CrtResource> T registerOwnedResource(T subresource) {
108111
if (subresource != null) {
109-
subresource.addRef();
110112
ownedSubResources.push(subresource);
111113
}
112114
return subresource;
@@ -116,13 +118,16 @@ String clientName() {
116118
return AWS_COMMON_RUNTIME;
117119
}
118120

119-
private HttpClientConnectionManager createConnectionPool(URI uri) {
121+
private HttpStreamManager createConnectionPool(URI uri) {
120122
log.debug(() -> "Creating ConnectionPool for: URI:" + uri + ", MaxConns: " + maxConnectionsPerEndpoint);
121123

122-
HttpClientConnectionManagerOptions options = new HttpClientConnectionManagerOptions()
124+
boolean isHttps = "https".equalsIgnoreCase(uri.getScheme());
125+
TlsContext poolTlsContext = isHttps ? tlsContext : null;
126+
127+
HttpClientConnectionManagerOptions h1Options = new HttpClientConnectionManagerOptions()
123128
.withClientBootstrap(bootstrap)
124129
.withSocketOptions(socketOptions)
125-
.withTlsContext(tlsContext)
130+
.withTlsContext(poolTlsContext)
126131
.withUri(uri)
127132
.withWindowSize(readBufferSize)
128133
.withMaxConnections(maxConnectionsPerEndpoint)
@@ -132,7 +137,24 @@ private HttpClientConnectionManager createConnectionPool(URI uri) {
132137
.withMaxConnectionIdleInMilliseconds(maxConnectionIdleInMilliseconds)
133138
.withConnectionAcquisitionTimeoutInMilliseconds(connectionAcquisitionTimeout);
134139

135-
return HttpClientConnectionManager.create(options);
140+
HttpStreamManagerOptions options = new HttpStreamManagerOptions()
141+
.withHTTP1ConnectionManagerOptions(h1Options);
142+
143+
if (protocol == Protocol.HTTP2) {
144+
Http2StreamManagerOptions h2Options = new Http2StreamManagerOptions()
145+
.withConnectionManagerOptions(h1Options);
146+
147+
if (!isHttps) {
148+
h2Options.withPriorKnowledge(true);
149+
}
150+
151+
options.withHTTP2StreamManagerOptions(h2Options);
152+
options.withExpectedProtocol(HttpVersion.HTTP_2);
153+
} else {
154+
options.withExpectedProtocol(HttpVersion.HTTP_1_1);
155+
}
156+
157+
return HttpStreamManager.create(options);
136158
}
137159

138160
/*
@@ -150,14 +172,13 @@ private HttpClientConnectionManager createConnectionPool(URI uri) {
150172
* existing pool. If we add all of execute() to the scope, we include, at minimum a JNI call to the native
151173
* pool implementation.
152174
*/
153-
HttpClientConnectionManager getOrCreateConnectionPool(URI uri) {
175+
HttpStreamManager getOrCreateConnectionPool(URI uri) {
154176
synchronized (this) {
155177
if (isClosed) {
156178
throw new IllegalStateException("Client is closed. No more requests can be made with this client.");
157179
}
158180

159-
HttpClientConnectionManager connPool = connectionPools.computeIfAbsent(uri, this::createConnectionPool);
160-
connPool.addRef();
181+
HttpStreamManager connPool = connectionPools.computeIfAbsent(uri, this::createConnectionPool);
161182
return connPool;
162183
}
163184
}

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/CrtAsyncRequestContext.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,25 @@
1616
package software.amazon.awssdk.http.crt.internal;
1717

1818
import software.amazon.awssdk.annotations.SdkInternalApi;
19-
import software.amazon.awssdk.crt.http.HttpClientConnectionManager;
19+
import software.amazon.awssdk.crt.http.HttpStreamManager;
20+
import software.amazon.awssdk.http.Protocol;
2021
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
2122
import software.amazon.awssdk.metrics.MetricCollector;
2223

2324
@SdkInternalApi
2425
public final class CrtAsyncRequestContext {
2526
private final AsyncExecuteRequest request;
2627
private final long readBufferSize;
27-
private final HttpClientConnectionManager crtConnPool;
28+
private final HttpStreamManager crtConnPool;
2829
private final MetricCollector metricCollector;
30+
private final Protocol protocol;
2931

3032
private CrtAsyncRequestContext(Builder builder) {
3133
this.request = builder.request;
3234
this.readBufferSize = builder.readBufferSize;
3335
this.crtConnPool = builder.crtConnPool;
3436
this.metricCollector = request.metricCollector().orElse(null);
37+
this.protocol = builder.protocol;
3538
}
3639

3740
public static Builder builder() {
@@ -46,7 +49,11 @@ public long readBufferSize() {
4649
return readBufferSize;
4750
}
4851

49-
public HttpClientConnectionManager crtConnPool() {
52+
public Protocol protocol() {
53+
return protocol;
54+
}
55+
56+
public HttpStreamManager crtConnPool() {
5057
return crtConnPool;
5158
}
5259

@@ -57,7 +64,8 @@ public MetricCollector metricCollector() {
5764
public static final class Builder {
5865
private AsyncExecuteRequest request;
5966
private long readBufferSize;
60-
private HttpClientConnectionManager crtConnPool;
67+
private HttpStreamManager crtConnPool;
68+
private Protocol protocol;
6169

6270
private Builder() {
6371
}
@@ -72,13 +80,19 @@ public Builder readBufferSize(long readBufferSize) {
7280
return this;
7381
}
7482

75-
public Builder crtConnPool(HttpClientConnectionManager crtConnPool) {
83+
public Builder crtConnPool(HttpStreamManager crtConnPool) {
7684
this.crtConnPool = crtConnPool;
7785
return this;
7886
}
7987

88+
public Builder protocol(Protocol protocol) {
89+
this.protocol = protocol;
90+
return this;
91+
}
92+
8093
public CrtAsyncRequestContext build() {
8194
return new CrtAsyncRequestContext(this);
8295
}
96+
8397
}
8498
}

0 commit comments

Comments
 (0)