xref: /aosp_15_r20/external/aws-crt-java/src/native/http2_stream_manager.c (revision 3c7ae9de214676c52d19f01067dc1a404272dc11)
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