1 /**
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0.
4 */
5
6 #include "crt.h"
7 #include "http_connection_manager.h"
8 #include "http_request_response.h"
9 #include "http_request_utils.h"
10 #include "java_class_ids.h"
11
12 #include <http_proxy_options.h>
13 #include <jni.h>
14 #include <string.h>
15
16 #include <aws/common/condition_variable.h>
17 #include <aws/common/string.h>
18
19 #include <aws/io/channel_bootstrap.h>
20 #include <aws/io/event_loop.h>
21 #include <aws/io/logging.h>
22 #include <aws/io/socket.h>
23 #include <aws/io/tls_channel_handler.h>
24
25 #include <aws/http/connection.h>
26 #include <aws/http/connection_manager.h>
27 #include <aws/http/http.h>
28 #include <aws/http/http2_stream_manager.h>
29 #include <aws/http/proxy.h>
30
31 /* on 32-bit platforms, casting pointers to longs throws a warning we don't need */
32 #if UINTPTR_MAX == 0xffffffff
33 # if defined(_MSC_VER)
34 # pragma warning(push)
35 # pragma warning(disable : 4305) /* 'type cast': truncation from 'jlong' to 'jni_tls_ctx_options *' */
36 # else
37 # pragma GCC diagnostic push
38 # pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
39 # pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
40 # endif
41 #endif
42
43 /*
44 * Stream manager binding, persists across the lifetime of the native object.
45 */
46 struct aws_http2_stream_manager_binding {
47 JavaVM *jvm;
48 jweak java_http2_stream_manager;
49 struct aws_http2_stream_manager *stream_manager;
50 };
51
s_destroy_manager_binding(struct aws_http2_stream_manager_binding * binding,JNIEnv * env)52 static void s_destroy_manager_binding(struct aws_http2_stream_manager_binding *binding, JNIEnv *env) {
53 if (binding == NULL) {
54 return;
55 }
56 if (binding->java_http2_stream_manager != NULL) {
57 (*env)->DeleteWeakGlobalRef(env, binding->java_http2_stream_manager);
58 }
59
60 aws_mem_release(aws_jni_get_allocator(), binding);
61 }
62
s_on_stream_manager_shutdown_complete_callback(void * user_data)63 static void s_on_stream_manager_shutdown_complete_callback(void *user_data) {
64
65 struct aws_http2_stream_manager_binding *binding = (struct aws_http2_stream_manager_binding *)user_data;
66 /********** JNI ENV ACQUIRE **********/
67 JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm);
68 if (env == NULL) {
69 /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
70 return;
71 }
72
73 AWS_LOGF_DEBUG(AWS_LS_HTTP_STREAM_MANAGER, "Java Stream Manager Shutdown Complete");
74 jobject java_http2_stream_manager = (*env)->NewLocalRef(env, binding->java_http2_stream_manager);
75 if (java_http2_stream_manager != NULL) {
76 (*env)->CallVoidMethod(env, java_http2_stream_manager, http2_stream_manager_properties.onShutdownComplete);
77
78 /* If exception raised from Java callback, but we already closed the stream manager, just move on */
79 aws_jni_check_and_clear_exception(env);
80
81 (*env)->DeleteLocalRef(env, java_http2_stream_manager);
82 }
83
84 /* We're done with this wrapper, free it. */
85 s_destroy_manager_binding(binding, env);
86 aws_jni_release_thread_env(binding->jvm, env);
87 /********** JNI ENV RELEASE **********/
88 }
89
Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerNew(JNIEnv * env,jclass jni_class,jobject stream_manager_jobject,jlong jni_client_bootstrap,jlong jni_socket_options,jlong jni_tls_ctx,jlong jni_tls_connection_options,jlongArray java_marshalled_settings,jbyteArray jni_endpoint,jint jni_port,jint jni_proxy_connection_type,jbyteArray jni_proxy_host,jint jni_proxy_port,jlong jni_proxy_tls_context,jint jni_proxy_authorization_type,jbyteArray jni_proxy_authorization_username,jbyteArray jni_proxy_authorization_password,jboolean jni_manual_window_management,jlong jni_monitoring_throughput_threshold_in_bytes_per_second,jint jni_monitoring_failure_interval_in_seconds,jint jni_max_conns,jint jni_ideal_concurrent_streams_per_connection,jint jni_max_concurrent_streams_per_connection,jboolean jni_prior_knowledge,jboolean jni_close_connection_on_server_error,jint jni_connection_ping_period_ms,jint jni_connection_ping_timeout_ms)90 JNIEXPORT jlong JNICALL Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerNew(
91 JNIEnv *env,
92 jclass jni_class,
93 jobject stream_manager_jobject,
94 jlong jni_client_bootstrap,
95 jlong jni_socket_options,
96 jlong jni_tls_ctx,
97 jlong jni_tls_connection_options,
98 jlongArray java_marshalled_settings,
99 jbyteArray jni_endpoint,
100 jint jni_port,
101 jint jni_proxy_connection_type,
102 jbyteArray jni_proxy_host,
103 jint jni_proxy_port,
104 jlong jni_proxy_tls_context,
105 jint jni_proxy_authorization_type,
106 jbyteArray jni_proxy_authorization_username,
107 jbyteArray jni_proxy_authorization_password,
108 jboolean jni_manual_window_management,
109 jlong jni_monitoring_throughput_threshold_in_bytes_per_second,
110 jint jni_monitoring_failure_interval_in_seconds,
111 jint jni_max_conns,
112 jint jni_ideal_concurrent_streams_per_connection,
113 jint jni_max_concurrent_streams_per_connection,
114 jboolean jni_prior_knowledge,
115 jboolean jni_close_connection_on_server_error,
116 jint jni_connection_ping_period_ms,
117 jint jni_connection_ping_timeout_ms) {
118
119 (void)jni_class;
120 aws_cache_jni_ids(env);
121
122 struct aws_client_bootstrap *client_bootstrap = (struct aws_client_bootstrap *)jni_client_bootstrap;
123 struct aws_socket_options *socket_options = (struct aws_socket_options *)jni_socket_options;
124 struct aws_tls_ctx *tls_ctx = (struct aws_tls_ctx *)jni_tls_ctx;
125 struct aws_tls_connection_options *tls_connection_options =
126 (struct aws_tls_connection_options *)jni_tls_connection_options;
127 struct aws_http2_stream_manager_binding *binding = NULL;
128 struct aws_allocator *allocator = aws_jni_get_allocator();
129
130 if (!client_bootstrap) {
131 aws_jni_throw_illegal_argument_exception(env, "ClientBootstrap can't be null");
132 return (jlong)NULL;
133 }
134
135 if (!socket_options) {
136 aws_jni_throw_illegal_argument_exception(env, "SocketOptions can't be null");
137 return (jlong)NULL;
138 }
139
140 const size_t marshalled_len = (*env)->GetArrayLength(env, java_marshalled_settings);
141 AWS_ASSERT(marshalled_len % 2 == 0);
142
143 size_t num_initial_settings = marshalled_len / 2;
144 struct aws_http2_setting *initial_settings =
145 num_initial_settings ? aws_mem_calloc(allocator, num_initial_settings, sizeof(struct aws_http2_setting)) : NULL;
146
147 jlong *marshalled_settings = (*env)->GetLongArrayElements(env, java_marshalled_settings, NULL);
148 for (size_t i = 0; i < num_initial_settings; i++) {
149 jlong id = marshalled_settings[i * 2];
150 initial_settings[i].id = (uint32_t)id;
151 jlong value = marshalled_settings[i * 2 + 1];
152 /* We checked the value can fit into uint32_t in Java already */
153 initial_settings[i].value = (uint32_t)value;
154 }
155
156 struct aws_byte_cursor endpoint = aws_jni_byte_cursor_from_jbyteArray_acquire(env, jni_endpoint);
157
158 if (jni_port == 0) {
159 aws_jni_throw_illegal_argument_exception(env, "Port must not be 0");
160 goto cleanup;
161 }
162
163 if (jni_max_conns <= 0) {
164 aws_jni_throw_illegal_argument_exception(env, "Max Connections must be > 0");
165 goto cleanup;
166 }
167
168 uint32_t port = (uint32_t)jni_port;
169
170 bool new_tls_conn_opts = (jni_tls_ctx != 0 && !tls_connection_options);
171
172 struct aws_tls_connection_options tls_conn_options;
173 AWS_ZERO_STRUCT(tls_conn_options);
174 if (new_tls_conn_opts) {
175 aws_tls_connection_options_init_from_ctx(&tls_conn_options, tls_ctx);
176 aws_tls_connection_options_set_server_name(&tls_conn_options, allocator, &endpoint);
177 tls_connection_options = &tls_conn_options;
178 }
179
180 binding = aws_mem_calloc(allocator, 1, sizeof(struct aws_http2_stream_manager_binding));
181 AWS_FATAL_ASSERT(binding);
182 binding->java_http2_stream_manager = (*env)->NewWeakGlobalRef(env, stream_manager_jobject);
183
184 jint jvmresult = (*env)->GetJavaVM(env, &binding->jvm);
185 (void)jvmresult;
186 AWS_FATAL_ASSERT(jvmresult == 0);
187
188 struct aws_http2_stream_manager_options manager_options = {
189 .bootstrap = client_bootstrap,
190 .initial_settings_array = initial_settings,
191 .num_initial_settings = num_initial_settings,
192 .socket_options = socket_options,
193 .http2_prior_knowledge = jni_prior_knowledge,
194 .tls_connection_options = tls_connection_options,
195 .monitoring_options = NULL,
196 .host = endpoint,
197 .port = port,
198 .shutdown_complete_callback = &s_on_stream_manager_shutdown_complete_callback,
199 .shutdown_complete_user_data = binding,
200 .enable_read_back_pressure = jni_manual_window_management,
201 .close_connection_on_server_error = jni_close_connection_on_server_error,
202 .connection_ping_period_ms = jni_connection_ping_period_ms,
203 .connection_ping_timeout_ms = jni_connection_ping_timeout_ms,
204 .ideal_concurrent_streams_per_connection = (size_t)jni_ideal_concurrent_streams_per_connection,
205 .max_concurrent_streams_per_connection = (size_t)jni_max_concurrent_streams_per_connection,
206 .max_connections = (size_t)jni_max_conns,
207 };
208
209 struct aws_http_connection_monitoring_options monitoring_options;
210 AWS_ZERO_STRUCT(monitoring_options);
211 if (jni_monitoring_throughput_threshold_in_bytes_per_second >= 0 &&
212 jni_monitoring_failure_interval_in_seconds >= 2) {
213 monitoring_options.minimum_throughput_bytes_per_second =
214 jni_monitoring_throughput_threshold_in_bytes_per_second;
215 monitoring_options.allowable_throughput_failure_interval_seconds = jni_monitoring_failure_interval_in_seconds;
216
217 manager_options.monitoring_options = &monitoring_options;
218 }
219
220 struct aws_http_proxy_options proxy_options;
221 AWS_ZERO_STRUCT(proxy_options);
222
223 struct aws_tls_connection_options proxy_tls_conn_options;
224 AWS_ZERO_STRUCT(proxy_tls_conn_options);
225
226 aws_http_proxy_options_jni_init(
227 env,
228 &proxy_options,
229 jni_proxy_connection_type,
230 &proxy_tls_conn_options,
231 jni_proxy_host,
232 jni_proxy_port,
233 jni_proxy_authorization_username,
234 jni_proxy_authorization_password,
235 jni_proxy_authorization_type,
236 (struct aws_tls_ctx *)jni_proxy_tls_context);
237
238 if (jni_proxy_host != NULL) {
239 manager_options.proxy_options = &proxy_options;
240 }
241
242 binding->stream_manager = aws_http2_stream_manager_new(allocator, &manager_options);
243 if (binding->stream_manager == NULL) {
244 aws_jni_throw_runtime_exception(env, "Failed to create stream manager: %s", aws_error_str(aws_last_error()));
245 }
246
247 aws_http_proxy_options_jni_clean_up(
248 env, &proxy_options, jni_proxy_host, jni_proxy_authorization_username, jni_proxy_authorization_password);
249
250 if (new_tls_conn_opts) {
251 aws_tls_connection_options_clean_up(&tls_conn_options);
252 }
253
254 cleanup:
255 aws_jni_byte_cursor_from_jbyteArray_release(env, jni_endpoint, endpoint);
256
257 if (binding->stream_manager == NULL) {
258 s_destroy_manager_binding(binding, env);
259 binding = NULL;
260 }
261
262 return (jlong)binding;
263 }
264
265 /*
266 * Stream manager binding, persists across the lifetime of the native object.
267 */
268 struct aws_sm_acquire_stream_callback_data {
269 JavaVM *jvm;
270 struct http_stream_binding *stream_binding;
271 jobject java_async_callback;
272 };
273
s_cleanup_sm_acquire_stream_callback_data(struct aws_sm_acquire_stream_callback_data * callback_data,JNIEnv * env)274 static void s_cleanup_sm_acquire_stream_callback_data(
275 struct aws_sm_acquire_stream_callback_data *callback_data,
276 JNIEnv *env) {
277
278 if (callback_data->java_async_callback) {
279 (*env)->DeleteGlobalRef(env, callback_data->java_async_callback);
280 }
281 aws_mem_release(aws_jni_get_allocator(), callback_data);
282 }
283
s_new_sm_acquire_stream_callback_data(JNIEnv * env,struct aws_allocator * allocator,struct http_stream_binding * stream_binding,jobject async_callback)284 static struct aws_sm_acquire_stream_callback_data *s_new_sm_acquire_stream_callback_data(
285 JNIEnv *env,
286 struct aws_allocator *allocator,
287 struct http_stream_binding *stream_binding,
288 jobject async_callback) {
289 struct aws_sm_acquire_stream_callback_data *callback_data =
290 aws_mem_calloc(allocator, 1, sizeof(struct aws_sm_acquire_stream_callback_data));
291
292 jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
293 AWS_FATAL_ASSERT(jvmresult == 0);
294 callback_data->java_async_callback = async_callback ? (*env)->NewGlobalRef(env, async_callback) : NULL;
295 AWS_FATAL_ASSERT(callback_data->java_async_callback != NULL);
296 callback_data->stream_binding = stream_binding;
297
298 return callback_data;
299 }
300
s_on_stream_acquired(struct aws_http_stream * stream,int error_code,void * user_data)301 static void s_on_stream_acquired(struct aws_http_stream *stream, int error_code, void *user_data) {
302 struct aws_sm_acquire_stream_callback_data *callback_data = user_data;
303 /********** JNI ENV ACQUIRE **********/
304 JNIEnv *env = aws_jni_acquire_thread_env(callback_data->jvm);
305 if (env == NULL) {
306 /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
307 return;
308 }
309 if (error_code) {
310 AWS_ASSERT(stream == NULL);
311 jobject crt_exception = aws_jni_new_crt_exception_from_error_code(env, error_code);
312 (*env)->CallVoidMethod(
313 env, callback_data->java_async_callback, async_callback_properties.on_failure, crt_exception);
314 (*env)->DeleteLocalRef(env, crt_exception);
315 aws_http_stream_binding_release(env, callback_data->stream_binding);
316 } else {
317 /* Acquire for the native stream. The destroy callback for native stream will release the ref. */
318 aws_http_stream_binding_acquire(callback_data->stream_binding);
319
320 callback_data->stream_binding->native_stream = stream;
321 jobject j_http_stream =
322 aws_java_http_stream_from_native_new(env, callback_data->stream_binding, AWS_HTTP_VERSION_2);
323 if (!j_http_stream) {
324 jthrowable crt_exception = (*env)->ExceptionOccurred(env);
325 AWS_ASSERT(crt_exception);
326 (*env)->CallVoidMethod(
327 env, callback_data->java_async_callback, async_callback_properties.on_failure, crt_exception);
328 (*env)->DeleteLocalRef(env, crt_exception);
329 (*env)->ExceptionClear(env);
330 /* Release the refcount on binding for the java object that failed to be created. */
331 aws_http_stream_binding_release(env, callback_data->stream_binding);
332 } else {
333 callback_data->stream_binding->java_http_stream_base = (*env)->NewGlobalRef(env, j_http_stream);
334 (*env)->CallVoidMethod(
335 env,
336 callback_data->java_async_callback,
337 async_callback_properties.on_success_with_object,
338 callback_data->stream_binding->java_http_stream_base);
339 (*env)->DeleteLocalRef(env, j_http_stream);
340 }
341 }
342 AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env));
343 s_cleanup_sm_acquire_stream_callback_data(callback_data, env);
344 aws_jni_release_thread_env(callback_data->jvm, env);
345 /********** JNI ENV RELEASE **********/
346 }
347
Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerAcquireStream(JNIEnv * env,jclass jni_class,jlong jni_stream_manager,jbyteArray marshalled_request,jobject jni_http_request_body_stream,jobject jni_http_response_callback_handler,jobject java_async_callback)348 JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerAcquireStream(
349 JNIEnv *env,
350 jclass jni_class,
351 jlong jni_stream_manager,
352 jbyteArray marshalled_request,
353 jobject jni_http_request_body_stream,
354 jobject jni_http_response_callback_handler,
355 jobject java_async_callback) {
356 (void)jni_class;
357 aws_cache_jni_ids(env);
358
359 struct aws_http2_stream_manager_binding *sm_binding = (struct aws_http2_stream_manager_binding *)jni_stream_manager;
360 struct aws_http2_stream_manager *stream_manager = sm_binding->stream_manager;
361
362 if (!stream_manager) {
363 aws_jni_throw_illegal_argument_exception(env, "Stream Manager can't be null");
364 return;
365 }
366
367 if (!jni_http_response_callback_handler) {
368 aws_jni_throw_illegal_argument_exception(
369 env, "Http2StreamManager.acquireStream: Invalid jni_http_response_callback_handler");
370 return;
371 }
372 if (!java_async_callback) {
373 aws_jni_throw_illegal_argument_exception(env, "Http2StreamManager.acquireStream: Invalid async callback");
374 return;
375 }
376
377 struct http_stream_binding *stream_binding = aws_http_stream_binding_new(env, jni_http_response_callback_handler);
378 if (!stream_binding) {
379 /* Exception already thrown */
380 return;
381 }
382
383 stream_binding->native_request =
384 aws_http_request_new_from_java_http_request(env, marshalled_request, jni_http_request_body_stream);
385 if (stream_binding->native_request == NULL) {
386 /* Exception already thrown */
387 aws_http_stream_binding_release(env, stream_binding);
388 return;
389 }
390
391 struct aws_http_make_request_options request_options = {
392 .self_size = sizeof(request_options),
393 .request = stream_binding->native_request,
394 /* Set Callbacks */
395 .on_response_headers = aws_java_http_stream_on_incoming_headers_fn,
396 .on_response_header_block_done = aws_java_http_stream_on_incoming_header_block_done_fn,
397 .on_response_body = aws_java_http_stream_on_incoming_body_fn,
398 .on_complete = aws_java_http_stream_on_stream_complete_fn,
399 .on_destroy = aws_java_http_stream_on_stream_destroy_fn,
400 .user_data = stream_binding,
401 };
402
403 struct aws_allocator *allocator = aws_jni_get_allocator();
404 struct aws_sm_acquire_stream_callback_data *callback_data =
405 s_new_sm_acquire_stream_callback_data(env, allocator, stream_binding, java_async_callback);
406
407 struct aws_http2_stream_manager_acquire_stream_options acquire_options = {
408 .options = &request_options,
409 .callback = s_on_stream_acquired,
410 .user_data = callback_data,
411 };
412
413 aws_http2_stream_manager_acquire_stream(sm_binding->stream_manager, &acquire_options);
414 }
415
Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerRelease(JNIEnv * env,jclass jni_class,jlong jni_stream_manager)416 JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerRelease(
417 JNIEnv *env,
418 jclass jni_class,
419 jlong jni_stream_manager) {
420 (void)jni_class;
421 aws_cache_jni_ids(env);
422
423 struct aws_http2_stream_manager_binding *sm_binding = (struct aws_http2_stream_manager_binding *)jni_stream_manager;
424 struct aws_http2_stream_manager *stream_manager = sm_binding->stream_manager;
425
426 if (!stream_manager) {
427 aws_jni_throw_runtime_exception(env, "Stream Manager can't be null");
428 return;
429 }
430
431 AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION, "Releasing StreamManager: id: %p", (void *)stream_manager);
432 aws_http2_stream_manager_release(stream_manager);
433 }
434
Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerFetchMetrics(JNIEnv * env,jclass jni_class,jlong jni_stream_manager)435 JNIEXPORT jobject JNICALL Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerFetchMetrics(
436 JNIEnv *env,
437 jclass jni_class,
438 jlong jni_stream_manager) {
439 (void)jni_class;
440 aws_cache_jni_ids(env);
441
442 struct aws_http2_stream_manager_binding *sm_binding = (struct aws_http2_stream_manager_binding *)jni_stream_manager;
443 struct aws_http2_stream_manager *stream_manager = sm_binding->stream_manager;
444
445 if (!stream_manager) {
446 aws_jni_throw_runtime_exception(env, "Stream Manager can't be null");
447 return NULL;
448 }
449
450 struct aws_http_manager_metrics metrics;
451 aws_http2_stream_manager_fetch_metrics(stream_manager, &metrics);
452
453 return (*env)->NewObject(
454 env,
455 http_manager_metrics_properties.http_manager_metrics_class,
456 http_manager_metrics_properties.constructor_method_id,
457 (jlong)metrics.available_concurrency,
458 (jlong)metrics.pending_concurrency_acquires,
459 (jlong)metrics.leased_concurrency);
460 }
461