15
15
16
16
package software .amazon .awssdk .http .crt ;
17
17
18
+ import static software .amazon .awssdk .http .SdkHttpConfigurationOption .GLOBAL_HTTP_DEFAULTS ;
19
+ import static software .amazon .awssdk .http .SdkHttpConfigurationOption .MAX_CONNECTIONS ;
18
20
import static software .amazon .awssdk .utils .CollectionUtils .isNullOrEmpty ;
19
21
import static software .amazon .awssdk .utils .FunctionalUtils .invokeSafely ;
20
22
25
27
import java .util .Optional ;
26
28
import java .util .concurrent .CompletableFuture ;
27
29
import java .util .concurrent .ConcurrentHashMap ;
28
-
29
30
import software .amazon .awssdk .annotations .SdkPublicApi ;
30
- import software .amazon .awssdk .crt .http .HttpConnection ;
31
+ import software .amazon .awssdk .crt .http .HttpConnectionPoolManager ;
31
32
import software .amazon .awssdk .crt .http .HttpHeader ;
32
33
import software .amazon .awssdk .crt .http .HttpRequest ;
34
+ import software .amazon .awssdk .crt .http .HttpRequestOptions ;
33
35
import software .amazon .awssdk .crt .io .ClientBootstrap ;
34
36
import software .amazon .awssdk .crt .io .SocketOptions ;
35
37
import software .amazon .awssdk .crt .io .TlsContext ;
@@ -59,23 +61,26 @@ public class AwsCrtAsyncHttpClient implements SdkAsyncHttpClient {
59
61
private static final int DEFAULT_STREAM_WINDOW_SIZE = 16 * 1024 * 1024 ; // 16 MB Total Buffer size
60
62
private static final int DEFAULT_HTTP_BODY_UPDATE_SIZE = 4 * 1024 * 1024 ; // 4 MB Update size from Native
61
63
62
- private final Map <URI , HttpConnection > connections = new ConcurrentHashMap <>();
64
+ private final Map <URI , HttpConnectionPoolManager > connectionPools = new ConcurrentHashMap <>();
63
65
private final ClientBootstrap bootstrap ;
64
66
private final SocketOptions socketOptions ;
65
67
private final TlsContext tlsContext ;
66
68
private final int windowSize ;
69
+ private final int maxConnectionsPerEndpoint ;
67
70
private final int httpBodyUpdateSize ;
68
71
69
- public AwsCrtAsyncHttpClient (DefaultBuilder builder , AttributeMap serviceDefaultsMap ) {
70
- this (builder .bootstrap , builder .socketOptions , builder .tlsContext , builder .windowSize , builder .httpBodyUpdateSize );
72
+ public AwsCrtAsyncHttpClient (DefaultBuilder builder , AttributeMap config ) {
73
+ this (builder .bootstrap , builder .socketOptions , builder .tlsContext , builder .windowSize ,
74
+ config .get (SdkHttpConfigurationOption .MAX_CONNECTIONS ), builder .httpBodyUpdateSize );
71
75
}
72
76
73
77
public AwsCrtAsyncHttpClient (ClientBootstrap bootstrap , SocketOptions sockOpts , TlsContext tlsContext ) {
74
- this (bootstrap , sockOpts , tlsContext , DEFAULT_STREAM_WINDOW_SIZE , DEFAULT_HTTP_BODY_UPDATE_SIZE );
78
+ this (bootstrap , sockOpts , tlsContext , DEFAULT_STREAM_WINDOW_SIZE , GLOBAL_HTTP_DEFAULTS .get (MAX_CONNECTIONS ),
79
+ DEFAULT_HTTP_BODY_UPDATE_SIZE );
75
80
}
76
81
77
82
public AwsCrtAsyncHttpClient (ClientBootstrap bootstrap , SocketOptions sockOpts , TlsContext tlsContext ,
78
- int windowSize , int httpBodyUpdateSize ) {
83
+ int windowSize , int maxConns , int httpBodyUpdateSize ) {
79
84
Validate .notNull (bootstrap , "ClientBootstrap must not be null" );
80
85
Validate .notNull (sockOpts , "SocketOptions must not be null" );
81
86
Validate .notNull (tlsContext , "TlsContext must not be null" );
@@ -85,6 +90,7 @@ public AwsCrtAsyncHttpClient(ClientBootstrap bootstrap, SocketOptions sockOpts,
85
90
this .socketOptions = sockOpts ;
86
91
this .tlsContext = tlsContext ;
87
92
this .windowSize = windowSize ;
93
+ this .maxConnectionsPerEndpoint = maxConns ;
88
94
this .httpBodyUpdateSize = httpBodyUpdateSize ;
89
95
}
90
96
@@ -103,38 +109,30 @@ public String clientName() {
103
109
return AWS_COMMON_RUNTIME ;
104
110
}
105
111
106
- private HttpConnection createConnection (URI uri ) {
112
+ private HttpConnectionPoolManager createConnectionPool (URI uri ) {
107
113
Validate .notNull (uri , "URI must not be null" );
108
- log .debug (() -> "Creating Connection to: " + uri );
109
- return invokeSafely (() -> HttpConnection .createConnection (uri , bootstrap , socketOptions , tlsContext ,
110
- windowSize , httpBodyUpdateSize ).get ());
114
+ log .debug (() -> "Creating ConnectionPool for: " + uri );
115
+ return new HttpConnectionPoolManager (bootstrap , socketOptions , tlsContext , uri , windowSize , maxConnectionsPerEndpoint );
111
116
}
112
117
113
- private HttpConnection getOrCreateConnection (URI uri ) {
118
+ private HttpConnectionPoolManager getOrCreateConnectionPool (URI uri ) {
114
119
Validate .notNull (uri , "URI must not be null" );
115
- HttpConnection connToReturn = connections .get (uri );
120
+ HttpConnectionPoolManager connPool = connectionPools .get (uri );
116
121
117
- if (connToReturn == null ) {
118
- HttpConnection newConn = createConnection (uri );
119
- HttpConnection alreadyExistingConn = connections .putIfAbsent (uri , newConn );
122
+ if (connPool == null ) {
123
+ HttpConnectionPoolManager newConnPool = createConnectionPool (uri );
124
+ HttpConnectionPoolManager alreadyExistingConnPool = connectionPools .putIfAbsent (uri , newConnPool );
120
125
121
- if (alreadyExistingConn == null ) {
122
- connToReturn = newConn ;
126
+ if (alreadyExistingConnPool == null ) {
127
+ connPool = newConnPool ;
123
128
} else {
124
129
// Multiple threads trying to open connections to the same URI at once, close the newer one
125
- newConn .close ();
126
- connToReturn = alreadyExistingConn ;
130
+ newConnPool .close ();
131
+ connPool = alreadyExistingConnPool ;
127
132
}
128
133
}
129
134
130
- // If connection was shutdown by peer, open a new connection
131
- if (connToReturn .getShutdownFuture ().isDone ()) {
132
- connections .remove (uri , connToReturn );
133
- connToReturn .close ();
134
- return getOrCreateConnection (uri );
135
- }
136
-
137
- return connToReturn ;
135
+ return connPool ;
138
136
}
139
137
140
138
private List <HttpHeader > createHttpHeaderList (URI uri , AsyncExecuteRequest asyncRequest ) {
@@ -191,22 +189,38 @@ public CompletableFuture<Void> execute(AsyncExecuteRequest asyncRequest) {
191
189
Validate .notNull (asyncRequest .responseHandler (), "ResponseHandler must not be null" );
192
190
193
191
URI uri = toUri (asyncRequest .request ());
194
- HttpConnection crtConn = getOrCreateConnection (uri );
192
+ HttpConnectionPoolManager crtConnPool = getOrCreateConnectionPool (uri );
195
193
HttpRequest crtRequest = toCrtRequest (uri , asyncRequest );
196
194
197
195
CompletableFuture <Void > requestFuture = new CompletableFuture <>();
198
196
AwsCrtAsyncHttpStreamAdapter crtToSdkAdapter =
199
197
new AwsCrtAsyncHttpStreamAdapter (requestFuture , asyncRequest , windowSize );
200
198
201
- invokeSafely (() -> crtConn .makeRequest (crtRequest , crtToSdkAdapter ));
199
+ HttpRequestOptions reqOptions = new HttpRequestOptions ();
200
+ reqOptions .setBodyBufferSize (httpBodyUpdateSize );
201
+
202
+ // When a Connection is ready from the Connection Pool, schedule the Request on the connection
203
+ crtConnPool .acquireConnection ().whenComplete ((crtConn , throwable ) -> {
204
+ // If we didn't get a connection for some reason, fail the request
205
+ if (throwable != null ) {
206
+ requestFuture .completeExceptionally (throwable );
207
+ return ;
208
+ }
209
+
210
+ // When the Request is complete, return our connection back to the Connection Pool
211
+ requestFuture .whenComplete ((v , t ) -> crtConnPool .releaseConnection (crtConn ));
212
+
213
+ // Submit the Request on this Connection
214
+ invokeSafely (() -> crtConn .makeRequest (crtRequest , reqOptions , crtToSdkAdapter ));
215
+ });
202
216
203
217
return requestFuture ;
204
218
}
205
219
206
220
@ Override
207
221
public void close () {
208
- for (HttpConnection conn : connections .values ()) {
209
- conn .close ();
222
+ for (HttpConnectionPoolManager connPool : connectionPools .values ()) {
223
+ connPool .close ();
210
224
}
211
225
}
212
226
0 commit comments