| /** |
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| * SPDX-License-Identifier: Apache-2.0. |
| */ |
| |
| #include "crt.h" |
| #include "java_class_ids.h" |
| |
| #include <http_proxy_options.h> |
| #include <jni.h> |
| #include <string.h> |
| |
| #include <aws/common/condition_variable.h> |
| #include <aws/common/string.h> |
| |
| #include <aws/io/channel_bootstrap.h> |
| #include <aws/io/event_loop.h> |
| #include <aws/io/logging.h> |
| #include <aws/io/socket.h> |
| #include <aws/io/tls_channel_handler.h> |
| |
| #include <aws/http/connection.h> |
| #include <aws/http/connection_manager.h> |
| #include <aws/http/http.h> |
| #include <aws/http/proxy.h> |
| #include <http_proxy_options_environment_variable.h> |
| |
| #include "http_connection_manager.h" |
| |
| /* 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 |
| |
| /* |
| * Connection manager binding, persists across the lifetime of the native object. |
| */ |
| struct http_connection_manager_binding { |
| JavaVM *jvm; |
| jweak java_http_conn_manager; |
| struct aws_http_connection_manager *manager; |
| }; |
| |
| static void s_destroy_manager_binding(struct http_connection_manager_binding *binding, JNIEnv *env) { |
| if (binding == NULL || env == NULL) { |
| return; |
| } |
| |
| if (binding->java_http_conn_manager != NULL) { |
| (*env)->DeleteWeakGlobalRef(env, binding->java_http_conn_manager); |
| } |
| |
| aws_mem_release(aws_jni_get_allocator(), binding); |
| } |
| |
| static void s_on_http_conn_manager_shutdown_complete_callback(void *user_data) { |
| |
| struct http_connection_manager_binding *binding = (struct http_connection_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_CONNECTION_MANAGER, "ConnManager Shutdown Complete"); |
| jobject java_http_conn_manager = (*env)->NewLocalRef(env, binding->java_http_conn_manager); |
| if (java_http_conn_manager != NULL) { |
| (*env)->CallVoidMethod( |
| env, java_http_conn_manager, http_client_connection_manager_properties.onShutdownComplete); |
| |
| AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env)); |
| (*env)->DeleteLocalRef(env, java_http_conn_manager); |
| } |
| |
| // We're done with this wrapper, free it. |
| JavaVM *jvm = binding->jvm; |
| s_destroy_manager_binding(binding, env); |
| |
| aws_jni_release_thread_env(jvm, env); |
| /********** JNI ENV RELEASE **********/ |
| } |
| |
| JNIEXPORT jlong JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpClientConnectionManagerNew( |
| JNIEnv *env, |
| jclass jni_class, |
| jobject conn_manager_jobject, |
| jlong jni_client_bootstrap, |
| jlong jni_socket_options, |
| jlong jni_tls_ctx, |
| jlong jni_tls_connection_options, |
| jlong jni_window_size, |
| jbyteArray jni_endpoint, |
| jint jni_port, |
| jint jni_max_conns, |
| 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, |
| jint jni_environment_variable_proxy_connection_type, |
| jlong jni_environment_variable_proxy_tls_connection_options, |
| jint jni_environment_variable_type, |
| jboolean jni_manual_window_management, |
| jlong jni_max_connection_idle_in_milliseconds, |
| jlong jni_monitoring_throughput_threshold_in_bytes_per_second, |
| jint jni_monitoring_failure_interval_in_seconds, |
| jint jni_expected_protocol_version) { |
| |
| (void)jni_class; |
| (void)jni_expected_protocol_version; |
| 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 http_connection_manager_binding *binding = NULL; |
| |
| if (!client_bootstrap) { |
| aws_jni_throw_runtime_exception(env, "ClientBootstrap can't be null"); |
| return (jlong)NULL; |
| } |
| |
| if (!socket_options) { |
| aws_jni_throw_runtime_exception(env, "SocketOptions can't be null"); |
| return (jlong)NULL; |
| } |
| |
| struct aws_allocator *allocator = aws_jni_get_allocator(); |
| struct aws_byte_cursor endpoint = aws_jni_byte_cursor_from_jbyteArray_acquire(env, jni_endpoint); |
| |
| size_t window_size; |
| if (aws_size_t_from_java(env, &window_size, jni_window_size, "Initial window size")) { |
| goto cleanup; |
| } |
| |
| if (jni_max_conns <= 0) { |
| aws_jni_throw_runtime_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 http_connection_manager_binding)); |
| AWS_FATAL_ASSERT(binding); |
| binding->java_http_conn_manager = (*env)->NewWeakGlobalRef(env, conn_manager_jobject); |
| |
| jint jvmresult = (*env)->GetJavaVM(env, &binding->jvm); |
| (void)jvmresult; |
| AWS_FATAL_ASSERT(jvmresult == 0); |
| |
| struct aws_http_connection_manager_options manager_options; |
| AWS_ZERO_STRUCT(manager_options); |
| |
| manager_options.bootstrap = client_bootstrap; |
| manager_options.initial_window_size = window_size; |
| manager_options.socket_options = socket_options; |
| manager_options.tls_connection_options = tls_connection_options; |
| manager_options.host = endpoint; |
| manager_options.port = port; |
| manager_options.max_connections = (size_t)jni_max_conns; |
| manager_options.shutdown_complete_callback = &s_on_http_conn_manager_shutdown_complete_callback; |
| manager_options.shutdown_complete_user_data = binding; |
| manager_options.monitoring_options = NULL; |
| /* TODO: this variable needs to be renamed in aws-c-http. Come back and change it next revision. */ |
| manager_options.enable_read_back_pressure = jni_manual_window_management; |
| manager_options.max_connection_idle_in_milliseconds = jni_max_connection_idle_in_milliseconds; |
| |
| 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; |
| } |
| |
| struct proxy_env_var_settings proxy_ev_settings; |
| AWS_ZERO_STRUCT(proxy_ev_settings); |
| |
| aws_http_proxy_environment_variable_setting_jni_init( |
| &proxy_ev_settings, |
| jni_environment_variable_proxy_connection_type, |
| jni_environment_variable_type, |
| (struct aws_tls_connection_options *)jni_environment_variable_proxy_tls_connection_options); |
| |
| manager_options.proxy_ev_settings = &proxy_ev_settings; |
| |
| binding->manager = aws_http_connection_manager_new(allocator, &manager_options); |
| if (binding->manager == NULL) { |
| aws_jni_throw_runtime_exception( |
| env, "Failed to create connection 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->manager == NULL) { |
| s_destroy_manager_binding(binding, env); |
| binding = NULL; |
| } |
| |
| return (jlong)binding; |
| } |
| |
| JNIEXPORT void JNICALL |
| Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpClientConnectionManagerRelease( |
| JNIEnv *env, |
| jclass jni_class, |
| jlong jni_conn_manager_binding) { |
| |
| (void)jni_class; |
| aws_cache_jni_ids(env); |
| |
| struct http_connection_manager_binding *binding = |
| (struct http_connection_manager_binding *)jni_conn_manager_binding; |
| struct aws_http_connection_manager *conn_manager = binding->manager; |
| |
| if (!conn_manager) { |
| aws_jni_throw_runtime_exception(env, "Connection Manager can't be null"); |
| return; |
| } |
| |
| AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION, "Releasing ConnManager: id: %p", (void *)conn_manager); |
| aws_http_connection_manager_release(conn_manager); |
| } |
| |
| /********************************************************************************************************************/ |
| |
| static void s_destroy_connection_binding(struct aws_http_connection_binding *binding, JNIEnv *env) { |
| if (binding == NULL || env == NULL) { |
| return; |
| } |
| |
| if (binding->java_acquire_connection_future != NULL) { |
| (*env)->DeleteGlobalRef(env, binding->java_acquire_connection_future); |
| } |
| |
| if (binding->manager != NULL && binding->connection != NULL) { |
| aws_http_connection_manager_release_connection(binding->manager, binding->connection); |
| } |
| |
| aws_mem_release(aws_jni_get_allocator(), binding); |
| } |
| |
| static void s_on_http_conn_acquisition_callback( |
| struct aws_http_connection *connection, |
| int error_code, |
| void *user_data) { |
| |
| struct aws_http_connection_binding *binding = (struct aws_http_connection_binding *)user_data; |
| binding->connection = connection; |
| |
| /********** 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; |
| } |
| |
| jint jni_error_code = (jint)error_code; |
| |
| AWS_LOGF_DEBUG( |
| AWS_LS_HTTP_CONNECTION, |
| "ConnManager Acquired Conn: conn: %p, manager: %p, err_code: %d, err_str: %s", |
| (void *)connection, |
| (void *)binding->manager, |
| error_code, |
| aws_error_str(error_code)); |
| |
| (*env)->CallStaticVoidMethod( |
| env, |
| http_client_connection_properties.http_client_connection_class, |
| http_client_connection_properties.on_connection_acquired_method_id, |
| binding->java_acquire_connection_future, |
| (jlong)binding, |
| jni_error_code); |
| |
| AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env)); |
| |
| JavaVM *jvm = binding->jvm; |
| if (error_code) { |
| s_destroy_connection_binding(binding, env); |
| } |
| |
| aws_jni_release_thread_env(jvm, env); |
| /********** JNI ENV RELEASE **********/ |
| } |
| |
| JNIEXPORT void JNICALL |
| Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpClientConnectionManagerAcquireConnection( |
| JNIEnv *env, |
| jclass jni_class, |
| jlong jni_conn_manager_binding, |
| jobject acquire_future) { |
| |
| (void)jni_class; |
| aws_cache_jni_ids(env); |
| |
| struct http_connection_manager_binding *manager_binding = |
| (struct http_connection_manager_binding *)jni_conn_manager_binding; |
| struct aws_http_connection_manager *conn_manager = manager_binding->manager; |
| |
| if (!conn_manager) { |
| aws_jni_throw_runtime_exception(env, "Connection Manager can't be null"); |
| return; |
| } |
| |
| jobject future_ref = (*env)->NewGlobalRef(env, acquire_future); |
| if (future_ref == NULL) { |
| aws_jni_throw_runtime_exception( |
| env, "httpClientConnectionManagerAcquireConnection: failed to obtain ref to future"); |
| return; |
| } |
| |
| AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION, "Requesting a new connection from conn_manager: %p", (void *)conn_manager); |
| |
| struct aws_allocator *allocator = aws_jni_get_allocator(); |
| struct aws_http_connection_binding *connection_binding = |
| aws_mem_calloc(allocator, 1, sizeof(struct aws_http_connection_binding)); |
| connection_binding->java_acquire_connection_future = future_ref; |
| connection_binding->manager = conn_manager; |
| |
| jint jvmresult = (*env)->GetJavaVM(env, &connection_binding->jvm); |
| (void)jvmresult; |
| AWS_FATAL_ASSERT(jvmresult == 0); |
| |
| aws_http_connection_manager_acquire_connection( |
| conn_manager, &s_on_http_conn_acquisition_callback, (void *)connection_binding); |
| } |
| |
| JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionReleaseManaged( |
| JNIEnv *env, |
| jclass jni_class, |
| jlong jni_connection_binding) { |
| |
| (void)jni_class; |
| aws_cache_jni_ids(env); |
| |
| struct aws_http_connection_binding *binding = (struct aws_http_connection_binding *)jni_connection_binding; |
| |
| struct aws_http_connection_manager *conn_manager = binding->manager; |
| struct aws_http_connection *conn = binding->connection; |
| |
| if (!conn_manager) { |
| aws_jni_throw_runtime_exception(env, "Connection Manager can't be null"); |
| return; |
| } |
| |
| if (!conn) { |
| aws_jni_throw_runtime_exception(env, "Connection can't be null"); |
| return; |
| } |
| |
| AWS_LOGF_DEBUG( |
| AWS_LS_HTTP_CONNECTION, |
| "ConnManager Releasing Conn: manager: %p, conn: %p", |
| (void *)conn_manager, |
| (void *)conn); |
| |
| s_destroy_connection_binding(binding, env); |
| } |
| |
| JNIEXPORT jobject JNICALL |
| Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpConnectionManagerFetchMetrics( |
| JNIEnv *env, |
| jclass jni_class, |
| jlong jni_conn_manager_binding) { |
| (void)jni_class; |
| aws_cache_jni_ids(env); |
| |
| struct http_connection_manager_binding *manager_binding = |
| (struct http_connection_manager_binding *)jni_conn_manager_binding; |
| struct aws_http_connection_manager *conn_manager = manager_binding->manager; |
| |
| if (!conn_manager) { |
| aws_jni_throw_runtime_exception(env, "Connection Manager can't be null"); |
| return NULL; |
| } |
| |
| struct aws_http_manager_metrics metrics; |
| aws_http_connection_manager_fetch_metrics(conn_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); |
| } |
| |
| #if UINTPTR_MAX == 0xffffffff |
| # if defined(_MSC_VER) |
| # pragma warning(pop) |
| # else |
| # pragma GCC diagnostic pop |
| # endif |
| #endif |