/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include "crt.h" #include "http_connection_manager.h" #include "http_request_response.h" #include "http_request_utils.h" #include "java_class_ids.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* on 32-bit platforms, casting pointers to longs throws a warning we don't need */ #if UINTPTR_MAX == 0xffffffff # if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable : 4305) /* 'type cast': truncation from 'jlong' to 'jni_tls_ctx_options *' */ # else # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpointer-to-int-cast" # pragma GCC diagnostic ignored "-Wint-to-pointer-cast" # endif #endif /* * Stream manager binding, persists across the lifetime of the native object. */ struct aws_http2_stream_manager_binding { JavaVM *jvm; jweak java_http2_stream_manager; struct aws_http2_stream_manager *stream_manager; }; static void s_destroy_manager_binding(struct aws_http2_stream_manager_binding *binding, JNIEnv *env) { if (binding == NULL) { return; } if (binding->java_http2_stream_manager != NULL) { (*env)->DeleteWeakGlobalRef(env, binding->java_http2_stream_manager); } aws_mem_release(aws_jni_get_allocator(), binding); } static void s_on_stream_manager_shutdown_complete_callback(void *user_data) { struct aws_http2_stream_manager_binding *binding = (struct aws_http2_stream_manager_binding *)user_data; /********** JNI ENV ACQUIRE **********/ JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm); if (env == NULL) { /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */ return; } AWS_LOGF_DEBUG(AWS_LS_HTTP_STREAM_MANAGER, "Java Stream Manager Shutdown Complete"); jobject java_http2_stream_manager = (*env)->NewLocalRef(env, binding->java_http2_stream_manager); if (java_http2_stream_manager != NULL) { (*env)->CallVoidMethod(env, java_http2_stream_manager, http2_stream_manager_properties.onShutdownComplete); /* If exception raised from Java callback, but we already closed the stream manager, just move on */ aws_jni_check_and_clear_exception(env); (*env)->DeleteLocalRef(env, java_http2_stream_manager); } /* We're done with this wrapper, free it. */ s_destroy_manager_binding(binding, env); aws_jni_release_thread_env(binding->jvm, env); /********** JNI ENV RELEASE **********/ } JNIEXPORT jlong JNICALL 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) { (void)jni_class; aws_cache_jni_ids(env); struct aws_client_bootstrap *client_bootstrap = (struct aws_client_bootstrap *)jni_client_bootstrap; struct aws_socket_options *socket_options = (struct aws_socket_options *)jni_socket_options; struct aws_tls_ctx *tls_ctx = (struct aws_tls_ctx *)jni_tls_ctx; struct aws_tls_connection_options *tls_connection_options = (struct aws_tls_connection_options *)jni_tls_connection_options; struct aws_http2_stream_manager_binding *binding = NULL; struct aws_allocator *allocator = aws_jni_get_allocator(); if (!client_bootstrap) { aws_jni_throw_illegal_argument_exception(env, "ClientBootstrap can't be null"); return (jlong)NULL; } if (!socket_options) { aws_jni_throw_illegal_argument_exception(env, "SocketOptions can't be null"); return (jlong)NULL; } const size_t marshalled_len = (*env)->GetArrayLength(env, java_marshalled_settings); AWS_ASSERT(marshalled_len % 2 == 0); size_t num_initial_settings = marshalled_len / 2; struct aws_http2_setting *initial_settings = num_initial_settings ? aws_mem_calloc(allocator, num_initial_settings, sizeof(struct aws_http2_setting)) : NULL; jlong *marshalled_settings = (*env)->GetLongArrayElements(env, java_marshalled_settings, NULL); for (size_t i = 0; i < num_initial_settings; i++) { jlong id = marshalled_settings[i * 2]; initial_settings[i].id = (uint32_t)id; jlong value = marshalled_settings[i * 2 + 1]; /* We checked the value can fit into uint32_t in Java already */ initial_settings[i].value = (uint32_t)value; } struct aws_byte_cursor endpoint = aws_jni_byte_cursor_from_jbyteArray_acquire(env, jni_endpoint); if (jni_port == 0) { aws_jni_throw_illegal_argument_exception(env, "Port must not be 0"); goto cleanup; } if (jni_max_conns <= 0) { aws_jni_throw_illegal_argument_exception(env, "Max Connections must be > 0"); goto cleanup; } uint32_t port = (uint32_t)jni_port; bool new_tls_conn_opts = (jni_tls_ctx != 0 && !tls_connection_options); struct aws_tls_connection_options tls_conn_options; AWS_ZERO_STRUCT(tls_conn_options); if (new_tls_conn_opts) { aws_tls_connection_options_init_from_ctx(&tls_conn_options, tls_ctx); aws_tls_connection_options_set_server_name(&tls_conn_options, allocator, &endpoint); tls_connection_options = &tls_conn_options; } binding = aws_mem_calloc(allocator, 1, sizeof(struct aws_http2_stream_manager_binding)); AWS_FATAL_ASSERT(binding); binding->java_http2_stream_manager = (*env)->NewWeakGlobalRef(env, stream_manager_jobject); jint jvmresult = (*env)->GetJavaVM(env, &binding->jvm); (void)jvmresult; AWS_FATAL_ASSERT(jvmresult == 0); struct aws_http2_stream_manager_options manager_options = { .bootstrap = client_bootstrap, .initial_settings_array = initial_settings, .num_initial_settings = num_initial_settings, .socket_options = socket_options, .http2_prior_knowledge = jni_prior_knowledge, .tls_connection_options = tls_connection_options, .monitoring_options = NULL, .host = endpoint, .port = port, .shutdown_complete_callback = &s_on_stream_manager_shutdown_complete_callback, .shutdown_complete_user_data = binding, .enable_read_back_pressure = jni_manual_window_management, .close_connection_on_server_error = jni_close_connection_on_server_error, .connection_ping_period_ms = jni_connection_ping_period_ms, .connection_ping_timeout_ms = jni_connection_ping_timeout_ms, .ideal_concurrent_streams_per_connection = (size_t)jni_ideal_concurrent_streams_per_connection, .max_concurrent_streams_per_connection = (size_t)jni_max_concurrent_streams_per_connection, .max_connections = (size_t)jni_max_conns, }; struct aws_http_connection_monitoring_options monitoring_options; AWS_ZERO_STRUCT(monitoring_options); if (jni_monitoring_throughput_threshold_in_bytes_per_second >= 0 && jni_monitoring_failure_interval_in_seconds >= 2) { monitoring_options.minimum_throughput_bytes_per_second = jni_monitoring_throughput_threshold_in_bytes_per_second; monitoring_options.allowable_throughput_failure_interval_seconds = jni_monitoring_failure_interval_in_seconds; manager_options.monitoring_options = &monitoring_options; } struct aws_http_proxy_options proxy_options; AWS_ZERO_STRUCT(proxy_options); struct aws_tls_connection_options proxy_tls_conn_options; AWS_ZERO_STRUCT(proxy_tls_conn_options); aws_http_proxy_options_jni_init( env, &proxy_options, jni_proxy_connection_type, &proxy_tls_conn_options, jni_proxy_host, jni_proxy_port, jni_proxy_authorization_username, jni_proxy_authorization_password, jni_proxy_authorization_type, (struct aws_tls_ctx *)jni_proxy_tls_context); if (jni_proxy_host != NULL) { manager_options.proxy_options = &proxy_options; } binding->stream_manager = aws_http2_stream_manager_new(allocator, &manager_options); if (binding->stream_manager == NULL) { aws_jni_throw_runtime_exception(env, "Failed to create stream manager: %s", aws_error_str(aws_last_error())); } aws_http_proxy_options_jni_clean_up( env, &proxy_options, jni_proxy_host, jni_proxy_authorization_username, jni_proxy_authorization_password); if (new_tls_conn_opts) { aws_tls_connection_options_clean_up(&tls_conn_options); } cleanup: aws_jni_byte_cursor_from_jbyteArray_release(env, jni_endpoint, endpoint); if (binding->stream_manager == NULL) { s_destroy_manager_binding(binding, env); binding = NULL; } return (jlong)binding; } /* * Stream manager binding, persists across the lifetime of the native object. */ struct aws_sm_acquire_stream_callback_data { JavaVM *jvm; struct http_stream_binding *stream_binding; jobject java_async_callback; }; static void s_cleanup_sm_acquire_stream_callback_data( struct aws_sm_acquire_stream_callback_data *callback_data, JNIEnv *env) { if (callback_data->java_async_callback) { (*env)->DeleteGlobalRef(env, callback_data->java_async_callback); } aws_mem_release(aws_jni_get_allocator(), callback_data); } static struct aws_sm_acquire_stream_callback_data *s_new_sm_acquire_stream_callback_data( JNIEnv *env, struct aws_allocator *allocator, struct http_stream_binding *stream_binding, jobject async_callback) { struct aws_sm_acquire_stream_callback_data *callback_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_sm_acquire_stream_callback_data)); jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm); AWS_FATAL_ASSERT(jvmresult == 0); callback_data->java_async_callback = async_callback ? (*env)->NewGlobalRef(env, async_callback) : NULL; AWS_FATAL_ASSERT(callback_data->java_async_callback != NULL); callback_data->stream_binding = stream_binding; return callback_data; } static void s_on_stream_acquired(struct aws_http_stream *stream, int error_code, void *user_data) { struct aws_sm_acquire_stream_callback_data *callback_data = user_data; /********** JNI ENV ACQUIRE **********/ JNIEnv *env = aws_jni_acquire_thread_env(callback_data->jvm); if (env == NULL) { /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */ return; } if (error_code) { AWS_ASSERT(stream == NULL); jobject crt_exception = aws_jni_new_crt_exception_from_error_code(env, error_code); (*env)->CallVoidMethod( env, callback_data->java_async_callback, async_callback_properties.on_failure, crt_exception); (*env)->DeleteLocalRef(env, crt_exception); aws_http_stream_binding_release(env, callback_data->stream_binding); } else { /* Acquire for the native stream. The destroy callback for native stream will release the ref. */ aws_http_stream_binding_acquire(callback_data->stream_binding); callback_data->stream_binding->native_stream = stream; jobject j_http_stream = aws_java_http_stream_from_native_new(env, callback_data->stream_binding, AWS_HTTP_VERSION_2); if (!j_http_stream) { jthrowable crt_exception = (*env)->ExceptionOccurred(env); AWS_ASSERT(crt_exception); (*env)->CallVoidMethod( env, callback_data->java_async_callback, async_callback_properties.on_failure, crt_exception); (*env)->DeleteLocalRef(env, crt_exception); (*env)->ExceptionClear(env); /* Release the refcount on binding for the java object that failed to be created. */ aws_http_stream_binding_release(env, callback_data->stream_binding); } else { callback_data->stream_binding->java_http_stream_base = (*env)->NewGlobalRef(env, j_http_stream); (*env)->CallVoidMethod( env, callback_data->java_async_callback, async_callback_properties.on_success_with_object, callback_data->stream_binding->java_http_stream_base); (*env)->DeleteLocalRef(env, j_http_stream); } } AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env)); s_cleanup_sm_acquire_stream_callback_data(callback_data, env); aws_jni_release_thread_env(callback_data->jvm, env); /********** JNI ENV RELEASE **********/ } JNIEXPORT void JNICALL 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) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http2_stream_manager_binding *sm_binding = (struct aws_http2_stream_manager_binding *)jni_stream_manager; struct aws_http2_stream_manager *stream_manager = sm_binding->stream_manager; if (!stream_manager) { aws_jni_throw_illegal_argument_exception(env, "Stream Manager can't be null"); return; } if (!jni_http_response_callback_handler) { aws_jni_throw_illegal_argument_exception( env, "Http2StreamManager.acquireStream: Invalid jni_http_response_callback_handler"); return; } if (!java_async_callback) { aws_jni_throw_illegal_argument_exception(env, "Http2StreamManager.acquireStream: Invalid async callback"); return; } struct http_stream_binding *stream_binding = aws_http_stream_binding_new(env, jni_http_response_callback_handler); if (!stream_binding) { /* Exception already thrown */ return; } stream_binding->native_request = aws_http_request_new_from_java_http_request(env, marshalled_request, jni_http_request_body_stream); if (stream_binding->native_request == NULL) { /* Exception already thrown */ aws_http_stream_binding_release(env, stream_binding); return; } struct aws_http_make_request_options request_options = { .self_size = sizeof(request_options), .request = stream_binding->native_request, /* Set Callbacks */ .on_response_headers = aws_java_http_stream_on_incoming_headers_fn, .on_response_header_block_done = aws_java_http_stream_on_incoming_header_block_done_fn, .on_response_body = aws_java_http_stream_on_incoming_body_fn, .on_complete = aws_java_http_stream_on_stream_complete_fn, .on_destroy = aws_java_http_stream_on_stream_destroy_fn, .user_data = stream_binding, }; struct aws_allocator *allocator = aws_jni_get_allocator(); struct aws_sm_acquire_stream_callback_data *callback_data = s_new_sm_acquire_stream_callback_data(env, allocator, stream_binding, java_async_callback); struct aws_http2_stream_manager_acquire_stream_options acquire_options = { .options = &request_options, .callback = s_on_stream_acquired, .user_data = callback_data, }; aws_http2_stream_manager_acquire_stream(sm_binding->stream_manager, &acquire_options); } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerRelease( JNIEnv *env, jclass jni_class, jlong jni_stream_manager) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http2_stream_manager_binding *sm_binding = (struct aws_http2_stream_manager_binding *)jni_stream_manager; struct aws_http2_stream_manager *stream_manager = sm_binding->stream_manager; if (!stream_manager) { aws_jni_throw_runtime_exception(env, "Stream Manager can't be null"); return; } AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION, "Releasing StreamManager: id: %p", (void *)stream_manager); aws_http2_stream_manager_release(stream_manager); } JNIEXPORT jobject JNICALL Java_software_amazon_awssdk_crt_http_Http2StreamManager_http2StreamManagerFetchMetrics( JNIEnv *env, jclass jni_class, jlong jni_stream_manager) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http2_stream_manager_binding *sm_binding = (struct aws_http2_stream_manager_binding *)jni_stream_manager; struct aws_http2_stream_manager *stream_manager = sm_binding->stream_manager; if (!stream_manager) { aws_jni_throw_runtime_exception(env, "Stream Manager can't be null"); return NULL; } struct aws_http_manager_metrics metrics; aws_http2_stream_manager_fetch_metrics(stream_manager, &metrics); return (*env)->NewObject( env, http_manager_metrics_properties.http_manager_metrics_class, http_manager_metrics_properties.constructor_method_id, (jlong)metrics.available_concurrency, (jlong)metrics.pending_concurrency_acquires, (jlong)metrics.leased_concurrency); }