xref: /aosp_15_r20/external/aws-crt-java/src/native/http_connection_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 "java_class_ids.h"
8 
9 #include <http_proxy_options.h>
10 #include <jni.h>
11 #include <string.h>
12 
13 #include <aws/common/condition_variable.h>
14 #include <aws/common/string.h>
15 
16 #include <aws/io/channel_bootstrap.h>
17 #include <aws/io/event_loop.h>
18 #include <aws/io/logging.h>
19 #include <aws/io/socket.h>
20 #include <aws/io/tls_channel_handler.h>
21 
22 #include <aws/http/connection.h>
23 #include <aws/http/connection_manager.h>
24 #include <aws/http/http.h>
25 #include <aws/http/proxy.h>
26 #include <http_proxy_options_environment_variable.h>
27 
28 #include "http_connection_manager.h"
29 
30 /* on 32-bit platforms, casting pointers to longs throws a warning we don't need */
31 #if UINTPTR_MAX == 0xffffffff
32 #    if defined(_MSC_VER)
33 #        pragma warning(push)
34 #        pragma warning(disable : 4305) /* 'type cast': truncation from 'jlong' to 'jni_tls_ctx_options *' */
35 #    else
36 #        pragma GCC diagnostic push
37 #        pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
38 #        pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
39 #    endif
40 #endif
41 
42 /*
43  * Connection manager binding, persists across the lifetime of the native object.
44  */
45 struct http_connection_manager_binding {
46     JavaVM *jvm;
47     jweak java_http_conn_manager;
48     struct aws_http_connection_manager *manager;
49 };
50 
s_destroy_manager_binding(struct http_connection_manager_binding * binding,JNIEnv * env)51 static void s_destroy_manager_binding(struct http_connection_manager_binding *binding, JNIEnv *env) {
52     if (binding == NULL || env == NULL) {
53         return;
54     }
55 
56     if (binding->java_http_conn_manager != NULL) {
57         (*env)->DeleteWeakGlobalRef(env, binding->java_http_conn_manager);
58     }
59 
60     aws_mem_release(aws_jni_get_allocator(), binding);
61 }
62 
s_on_http_conn_manager_shutdown_complete_callback(void * user_data)63 static void s_on_http_conn_manager_shutdown_complete_callback(void *user_data) {
64 
65     struct http_connection_manager_binding *binding = (struct http_connection_manager_binding *)user_data;
66 
67     /********** JNI ENV ACQUIRE **********/
68     JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm);
69     if (env == NULL) {
70         /* If we can't get an environment, then the JVM is probably shutting down.  Don't crash. */
71         return;
72     }
73 
74     AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION_MANAGER, "ConnManager Shutdown Complete");
75     jobject java_http_conn_manager = (*env)->NewLocalRef(env, binding->java_http_conn_manager);
76     if (java_http_conn_manager != NULL) {
77         (*env)->CallVoidMethod(
78             env, java_http_conn_manager, http_client_connection_manager_properties.onShutdownComplete);
79 
80         AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env));
81         (*env)->DeleteLocalRef(env, java_http_conn_manager);
82     }
83 
84     // We're done with this wrapper, free it.
85     JavaVM *jvm = binding->jvm;
86     s_destroy_manager_binding(binding, env);
87 
88     aws_jni_release_thread_env(jvm, env);
89     /********** JNI ENV RELEASE **********/
90 }
91 
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)92 JNIEXPORT jlong JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpClientConnectionManagerNew(
93     JNIEnv *env,
94     jclass jni_class,
95     jobject conn_manager_jobject,
96     jlong jni_client_bootstrap,
97     jlong jni_socket_options,
98     jlong jni_tls_ctx,
99     jlong jni_tls_connection_options,
100     jlong jni_window_size,
101     jbyteArray jni_endpoint,
102     jint jni_port,
103     jint jni_max_conns,
104     jint jni_proxy_connection_type,
105     jbyteArray jni_proxy_host,
106     jint jni_proxy_port,
107     jlong jni_proxy_tls_context,
108     jint jni_proxy_authorization_type,
109     jbyteArray jni_proxy_authorization_username,
110     jbyteArray jni_proxy_authorization_password,
111     jint jni_environment_variable_proxy_connection_type,
112     jlong jni_environment_variable_proxy_tls_connection_options,
113     jint jni_environment_variable_type,
114     jboolean jni_manual_window_management,
115     jlong jni_max_connection_idle_in_milliseconds,
116     jlong jni_monitoring_throughput_threshold_in_bytes_per_second,
117     jint jni_monitoring_failure_interval_in_seconds,
118     jint jni_expected_protocol_version) {
119 
120     (void)jni_class;
121     (void)jni_expected_protocol_version;
122     aws_cache_jni_ids(env);
123 
124     struct aws_client_bootstrap *client_bootstrap = (struct aws_client_bootstrap *)jni_client_bootstrap;
125     struct aws_socket_options *socket_options = (struct aws_socket_options *)jni_socket_options;
126     struct aws_tls_ctx *tls_ctx = (struct aws_tls_ctx *)jni_tls_ctx;
127     struct aws_tls_connection_options *tls_connection_options =
128         (struct aws_tls_connection_options *)jni_tls_connection_options;
129     struct http_connection_manager_binding *binding = NULL;
130 
131     if (!client_bootstrap) {
132         aws_jni_throw_runtime_exception(env, "ClientBootstrap can't be null");
133         return (jlong)NULL;
134     }
135 
136     if (!socket_options) {
137         aws_jni_throw_runtime_exception(env, "SocketOptions can't be null");
138         return (jlong)NULL;
139     }
140 
141     struct aws_allocator *allocator = aws_jni_get_allocator();
142     struct aws_byte_cursor endpoint = aws_jni_byte_cursor_from_jbyteArray_acquire(env, jni_endpoint);
143 
144     size_t window_size;
145     if (aws_size_t_from_java(env, &window_size, jni_window_size, "Initial window size")) {
146         goto cleanup;
147     }
148 
149     if (jni_max_conns <= 0) {
150         aws_jni_throw_runtime_exception(env, "Max Connections must be > 0");
151         goto cleanup;
152     }
153 
154     uint32_t port = (uint32_t)jni_port;
155 
156     bool new_tls_conn_opts = (jni_tls_ctx != 0 && !tls_connection_options);
157 
158     struct aws_tls_connection_options tls_conn_options;
159     AWS_ZERO_STRUCT(tls_conn_options);
160     if (new_tls_conn_opts) {
161         aws_tls_connection_options_init_from_ctx(&tls_conn_options, tls_ctx);
162         aws_tls_connection_options_set_server_name(&tls_conn_options, allocator, &endpoint);
163         tls_connection_options = &tls_conn_options;
164     }
165 
166     binding = aws_mem_calloc(allocator, 1, sizeof(struct http_connection_manager_binding));
167     AWS_FATAL_ASSERT(binding);
168     binding->java_http_conn_manager = (*env)->NewWeakGlobalRef(env, conn_manager_jobject);
169 
170     jint jvmresult = (*env)->GetJavaVM(env, &binding->jvm);
171     (void)jvmresult;
172     AWS_FATAL_ASSERT(jvmresult == 0);
173 
174     struct aws_http_connection_manager_options manager_options;
175     AWS_ZERO_STRUCT(manager_options);
176 
177     manager_options.bootstrap = client_bootstrap;
178     manager_options.initial_window_size = window_size;
179     manager_options.socket_options = socket_options;
180     manager_options.tls_connection_options = tls_connection_options;
181     manager_options.host = endpoint;
182     manager_options.port = port;
183     manager_options.max_connections = (size_t)jni_max_conns;
184     manager_options.shutdown_complete_callback = &s_on_http_conn_manager_shutdown_complete_callback;
185     manager_options.shutdown_complete_user_data = binding;
186     manager_options.monitoring_options = NULL;
187     /* TODO: this variable needs to be renamed in aws-c-http. Come back and change it next revision. */
188     manager_options.enable_read_back_pressure = jni_manual_window_management;
189     manager_options.max_connection_idle_in_milliseconds = jni_max_connection_idle_in_milliseconds;
190 
191     struct aws_http_connection_monitoring_options monitoring_options;
192     AWS_ZERO_STRUCT(monitoring_options);
193     if (jni_monitoring_throughput_threshold_in_bytes_per_second >= 0 &&
194         jni_monitoring_failure_interval_in_seconds >= 2) {
195         monitoring_options.minimum_throughput_bytes_per_second =
196             jni_monitoring_throughput_threshold_in_bytes_per_second;
197         monitoring_options.allowable_throughput_failure_interval_seconds = jni_monitoring_failure_interval_in_seconds;
198 
199         manager_options.monitoring_options = &monitoring_options;
200     }
201 
202     struct aws_http_proxy_options proxy_options;
203     AWS_ZERO_STRUCT(proxy_options);
204 
205     struct aws_tls_connection_options proxy_tls_conn_options;
206     AWS_ZERO_STRUCT(proxy_tls_conn_options);
207 
208     aws_http_proxy_options_jni_init(
209         env,
210         &proxy_options,
211         jni_proxy_connection_type,
212         &proxy_tls_conn_options,
213         jni_proxy_host,
214         jni_proxy_port,
215         jni_proxy_authorization_username,
216         jni_proxy_authorization_password,
217         jni_proxy_authorization_type,
218         (struct aws_tls_ctx *)jni_proxy_tls_context);
219 
220     if (jni_proxy_host != NULL) {
221         manager_options.proxy_options = &proxy_options;
222     }
223 
224     struct proxy_env_var_settings proxy_ev_settings;
225     AWS_ZERO_STRUCT(proxy_ev_settings);
226 
227     aws_http_proxy_environment_variable_setting_jni_init(
228         &proxy_ev_settings,
229         jni_environment_variable_proxy_connection_type,
230         jni_environment_variable_type,
231         (struct aws_tls_connection_options *)jni_environment_variable_proxy_tls_connection_options);
232 
233     manager_options.proxy_ev_settings = &proxy_ev_settings;
234 
235     binding->manager = aws_http_connection_manager_new(allocator, &manager_options);
236     if (binding->manager == NULL) {
237         aws_jni_throw_runtime_exception(
238             env, "Failed to create connection manager: %s", aws_error_str(aws_last_error()));
239     }
240 
241     aws_http_proxy_options_jni_clean_up(
242         env, &proxy_options, jni_proxy_host, jni_proxy_authorization_username, jni_proxy_authorization_password);
243 
244     if (new_tls_conn_opts) {
245         aws_tls_connection_options_clean_up(&tls_conn_options);
246     }
247 
248 cleanup:
249     aws_jni_byte_cursor_from_jbyteArray_release(env, jni_endpoint, endpoint);
250 
251     if (binding->manager == NULL) {
252         s_destroy_manager_binding(binding, env);
253         binding = NULL;
254     }
255 
256     return (jlong)binding;
257 }
258 
259 JNIEXPORT void JNICALL
Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpClientConnectionManagerRelease(JNIEnv * env,jclass jni_class,jlong jni_conn_manager_binding)260     Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpClientConnectionManagerRelease(
261         JNIEnv *env,
262         jclass jni_class,
263         jlong jni_conn_manager_binding) {
264 
265     (void)jni_class;
266     aws_cache_jni_ids(env);
267 
268     struct http_connection_manager_binding *binding =
269         (struct http_connection_manager_binding *)jni_conn_manager_binding;
270     struct aws_http_connection_manager *conn_manager = binding->manager;
271 
272     if (!conn_manager) {
273         aws_jni_throw_runtime_exception(env, "Connection Manager can't be null");
274         return;
275     }
276 
277     AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION, "Releasing ConnManager: id: %p", (void *)conn_manager);
278     aws_http_connection_manager_release(conn_manager);
279 }
280 
281 /********************************************************************************************************************/
282 
s_destroy_connection_binding(struct aws_http_connection_binding * binding,JNIEnv * env)283 static void s_destroy_connection_binding(struct aws_http_connection_binding *binding, JNIEnv *env) {
284     if (binding == NULL || env == NULL) {
285         return;
286     }
287 
288     if (binding->java_acquire_connection_future != NULL) {
289         (*env)->DeleteGlobalRef(env, binding->java_acquire_connection_future);
290     }
291 
292     if (binding->manager != NULL && binding->connection != NULL) {
293         aws_http_connection_manager_release_connection(binding->manager, binding->connection);
294     }
295 
296     aws_mem_release(aws_jni_get_allocator(), binding);
297 }
298 
s_on_http_conn_acquisition_callback(struct aws_http_connection * connection,int error_code,void * user_data)299 static void s_on_http_conn_acquisition_callback(
300     struct aws_http_connection *connection,
301     int error_code,
302     void *user_data) {
303 
304     struct aws_http_connection_binding *binding = (struct aws_http_connection_binding *)user_data;
305     binding->connection = connection;
306 
307     /********** JNI ENV ACQUIRE **********/
308     JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm);
309     if (env == NULL) {
310         /* If we can't get an environment, then the JVM is probably shutting down.  Don't crash. */
311         return;
312     }
313 
314     jint jni_error_code = (jint)error_code;
315 
316     AWS_LOGF_DEBUG(
317         AWS_LS_HTTP_CONNECTION,
318         "ConnManager Acquired Conn: conn: %p, manager: %p, err_code: %d,  err_str: %s",
319         (void *)connection,
320         (void *)binding->manager,
321         error_code,
322         aws_error_str(error_code));
323 
324     (*env)->CallStaticVoidMethod(
325         env,
326         http_client_connection_properties.http_client_connection_class,
327         http_client_connection_properties.on_connection_acquired_method_id,
328         binding->java_acquire_connection_future,
329         (jlong)binding,
330         jni_error_code);
331 
332     AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env));
333 
334     JavaVM *jvm = binding->jvm;
335     if (error_code) {
336         s_destroy_connection_binding(binding, env);
337     }
338 
339     aws_jni_release_thread_env(jvm, env);
340     /********** JNI ENV RELEASE **********/
341 }
342 
343 JNIEXPORT void JNICALL
Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpClientConnectionManagerAcquireConnection(JNIEnv * env,jclass jni_class,jlong jni_conn_manager_binding,jobject acquire_future)344     Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpClientConnectionManagerAcquireConnection(
345         JNIEnv *env,
346         jclass jni_class,
347         jlong jni_conn_manager_binding,
348         jobject acquire_future) {
349 
350     (void)jni_class;
351     aws_cache_jni_ids(env);
352 
353     struct http_connection_manager_binding *manager_binding =
354         (struct http_connection_manager_binding *)jni_conn_manager_binding;
355     struct aws_http_connection_manager *conn_manager = manager_binding->manager;
356 
357     if (!conn_manager) {
358         aws_jni_throw_runtime_exception(env, "Connection Manager can't be null");
359         return;
360     }
361 
362     jobject future_ref = (*env)->NewGlobalRef(env, acquire_future);
363     if (future_ref == NULL) {
364         aws_jni_throw_runtime_exception(
365             env, "httpClientConnectionManagerAcquireConnection: failed to obtain ref to future");
366         return;
367     }
368 
369     AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION, "Requesting a new connection from conn_manager: %p", (void *)conn_manager);
370 
371     struct aws_allocator *allocator = aws_jni_get_allocator();
372     struct aws_http_connection_binding *connection_binding =
373         aws_mem_calloc(allocator, 1, sizeof(struct aws_http_connection_binding));
374     connection_binding->java_acquire_connection_future = future_ref;
375     connection_binding->manager = conn_manager;
376 
377     jint jvmresult = (*env)->GetJavaVM(env, &connection_binding->jvm);
378     (void)jvmresult;
379     AWS_FATAL_ASSERT(jvmresult == 0);
380 
381     aws_http_connection_manager_acquire_connection(
382         conn_manager, &s_on_http_conn_acquisition_callback, (void *)connection_binding);
383 }
384 
Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionReleaseManaged(JNIEnv * env,jclass jni_class,jlong jni_connection_binding)385 JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionReleaseManaged(
386     JNIEnv *env,
387     jclass jni_class,
388     jlong jni_connection_binding) {
389 
390     (void)jni_class;
391     aws_cache_jni_ids(env);
392 
393     struct aws_http_connection_binding *binding = (struct aws_http_connection_binding *)jni_connection_binding;
394 
395     struct aws_http_connection_manager *conn_manager = binding->manager;
396     struct aws_http_connection *conn = binding->connection;
397 
398     if (!conn_manager) {
399         aws_jni_throw_runtime_exception(env, "Connection Manager can't be null");
400         return;
401     }
402 
403     if (!conn) {
404         aws_jni_throw_runtime_exception(env, "Connection can't be null");
405         return;
406     }
407 
408     AWS_LOGF_DEBUG(
409         AWS_LS_HTTP_CONNECTION,
410         "ConnManager Releasing Conn: manager: %p, conn: %p",
411         (void *)conn_manager,
412         (void *)conn);
413 
414     s_destroy_connection_binding(binding, env);
415 }
416 
417 JNIEXPORT jobject JNICALL
Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpConnectionManagerFetchMetrics(JNIEnv * env,jclass jni_class,jlong jni_conn_manager_binding)418     Java_software_amazon_awssdk_crt_http_HttpClientConnectionManager_httpConnectionManagerFetchMetrics(
419         JNIEnv *env,
420         jclass jni_class,
421         jlong jni_conn_manager_binding) {
422     (void)jni_class;
423     aws_cache_jni_ids(env);
424 
425     struct http_connection_manager_binding *manager_binding =
426         (struct http_connection_manager_binding *)jni_conn_manager_binding;
427     struct aws_http_connection_manager *conn_manager = manager_binding->manager;
428 
429     if (!conn_manager) {
430         aws_jni_throw_runtime_exception(env, "Connection Manager can't be null");
431         return NULL;
432     }
433 
434     struct aws_http_manager_metrics metrics;
435     aws_http_connection_manager_fetch_metrics(conn_manager, &metrics);
436 
437     return (*env)->NewObject(
438         env,
439         http_manager_metrics_properties.http_manager_metrics_class,
440         http_manager_metrics_properties.constructor_method_id,
441         (jlong)metrics.available_concurrency,
442         (jlong)metrics.pending_concurrency_acquires,
443         (jlong)metrics.leased_concurrency);
444 }
445 
446 #if UINTPTR_MAX == 0xffffffff
447 #    if defined(_MSC_VER)
448 #        pragma warning(pop)
449 #    else
450 #        pragma GCC diagnostic pop
451 #    endif
452 #endif
453