django-vue3-admin-web/node_modules/aws-crt/source/http_connection.c
2025-10-20 21:21:14 +08:00

460 lines
17 KiB
C

/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include "http_connection.h"
#include "io.h"
#include <aws/http/connection.h>
#include <aws/http/proxy.h>
#include <aws/io/tls_channel_handler.h>
struct http_proxy_options_binding {
struct aws_http_proxy_options native;
struct aws_allocator *allocator;
struct aws_string *host_name;
struct aws_string *auth_username;
struct aws_string *auth_password;
};
void s_proxy_options_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
(void)env;
(void)finalize_hint;
struct http_proxy_options_binding *binding = finalize_data;
aws_string_destroy(binding->host_name);
aws_string_destroy(binding->auth_username);
aws_string_destroy(binding->auth_password);
aws_mem_release(binding->allocator, binding);
}
napi_value aws_napi_http_proxy_options_new(napi_env env, napi_callback_info info) {
napi_value node_args[7];
size_t num_args = AWS_ARRAY_SIZE(node_args);
napi_value *arg = &node_args[0];
if (napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL)) {
napi_throw_error(env, NULL, "Failed to retrieve callback information");
return NULL;
}
if (num_args != AWS_ARRAY_SIZE(node_args)) {
napi_throw_error(env, NULL, "http_proxy_options_new requires exactly 7 arguments");
return NULL;
}
napi_value node_external = NULL; /* return value, external that wraps the aws_tls_connection_options */
struct aws_allocator *allocator = aws_napi_get_allocator();
struct http_proxy_options_binding *binding =
aws_mem_calloc(allocator, 1, sizeof(struct http_proxy_options_binding));
AWS_FATAL_ASSERT(binding && "Failed to allocate new http_proxy_options_binding");
binding->allocator = allocator;
napi_value node_host_name = *arg++;
binding->host_name = aws_string_new_from_napi(env, node_host_name);
if (!binding->host_name) {
AWS_NAPI_ENSURE(env, napi_throw_type_error(env, NULL, "Unable to convert host_name to string"));
goto cleanup;
}
binding->native.host = aws_byte_cursor_from_string(binding->host_name);
napi_value node_port = *arg++;
uint32_t port;
AWS_NAPI_CALL(env, napi_get_value_uint32(env, node_port, &port), {
napi_throw_type_error(env, NULL, "port must be a number");
goto cleanup;
});
binding->native.port = port;
napi_value node_auth_method = *arg++;
if (!aws_napi_is_null_or_undefined(env, node_auth_method)) {
uint32_t auth_method = 0;
AWS_NAPI_CALL(env, napi_get_value_uint32(env, node_auth_method, &auth_method), {
napi_throw_type_error(env, NULL, "auth_method must be a number");
goto cleanup;
});
binding->native.auth_type = auth_method;
}
napi_value node_username = *arg++;
if (!aws_napi_is_null_or_undefined(env, node_username)) {
binding->auth_username = aws_string_new_from_napi(env, node_username);
if (!binding->auth_username) {
napi_throw_type_error(env, NULL, "Unable to convert auth_username to string");
goto cleanup;
}
binding->native.auth_username = aws_byte_cursor_from_string(binding->auth_username);
}
napi_value node_password = *arg++;
if (!aws_napi_is_null_or_undefined(env, node_password)) {
binding->auth_password = aws_string_new_from_napi(env, node_password);
if (!binding->auth_password) {
napi_throw_type_error(env, NULL, "Unable to convert auth_password to string");
goto cleanup;
}
binding->native.auth_password = aws_byte_cursor_from_string(binding->auth_password);
}
napi_value node_tls_opts = *arg++;
if (!aws_napi_is_null_or_undefined(env, node_tls_opts)) {
AWS_NAPI_CALL(env, napi_get_value_external(env, node_tls_opts, (void **)&binding->native.tls_options), {
napi_throw_error(env, NULL, "Failed to extract tls_ctx from external");
goto cleanup;
});
}
napi_value node_connection_type = *arg++;
if (!aws_napi_is_null_or_undefined(env, node_connection_type)) {
uint32_t connection_type = 0;
AWS_NAPI_CALL(env, napi_get_value_uint32(env, node_connection_type, &connection_type), {
napi_throw_type_error(env, NULL, "connection_type must be a number");
goto cleanup;
});
binding->native.connection_type = connection_type;
}
if (binding->native.connection_type == AWS_HPCT_HTTP_FORWARD && binding->native.tls_options != NULL) {
AWS_NAPI_ENSURE(env, napi_throw_type_error(env, NULL, "Forwarding proxy connections cannot use tls"));
goto cleanup;
}
AWS_NAPI_CALL(
env, napi_create_external(env, binding, s_proxy_options_finalize, NULL, &node_external), { goto cleanup; });
return node_external;
cleanup:
s_proxy_options_finalize(env, binding, NULL);
return NULL;
}
struct aws_http_proxy_options *aws_napi_get_http_proxy_options(struct http_proxy_options_binding *binding) {
return &binding->native;
}
struct http_connection_binding {
struct aws_http_connection *connection;
struct aws_allocator *allocator;
napi_ref node_external;
napi_env env;
napi_threadsafe_function on_setup;
napi_threadsafe_function on_shutdown;
};
/* finalizer called when node cleans up this object */
static void s_http_connection_from_manager_binding_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
(void)finalize_hint;
struct http_connection_binding *binding = finalize_data;
if (binding->node_external != NULL) {
napi_delete_reference(env, binding->node_external);
}
/* no release call, the http_client_connection_manager has already released it */
aws_mem_release(binding->allocator, binding);
}
struct aws_http_connection *aws_napi_get_http_connection(struct http_connection_binding *binding) {
return binding->connection;
}
napi_value aws_napi_http_connection_from_manager(napi_env env, struct aws_http_connection *connection) {
struct http_connection_binding *binding =
aws_mem_calloc(aws_napi_get_allocator(), 1, sizeof(struct http_connection_binding));
if (!binding) {
aws_napi_throw_last_error(env);
return NULL;
}
binding->env = env;
binding->connection = connection;
binding->allocator = aws_napi_get_allocator();
napi_value node_external = NULL;
AWS_NAPI_CALL(
env,
napi_create_external(env, binding, s_http_connection_from_manager_binding_finalize, NULL, &node_external),
{
napi_throw_error(env, NULL, "Unable to create external for managed connection");
aws_mem_release(aws_napi_get_allocator(), binding);
return NULL;
});
return node_external;
}
struct on_connection_args {
struct http_connection_binding *binding;
int error_code;
};
static void s_http_on_connection_setup_call(napi_env env, napi_value on_setup, void *context, void *user_data) {
struct http_connection_binding *binding = context;
struct on_connection_args *args = user_data;
if (env) {
napi_value params[2];
const size_t num_params = AWS_ARRAY_SIZE(params);
AWS_NAPI_ENSURE(env, napi_get_reference_value(env, args->binding->node_external, &params[0]));
AWS_NAPI_ENSURE(env, napi_create_uint32(env, args->error_code, &params[1]));
AWS_NAPI_ENSURE(
env, aws_napi_dispatch_threadsafe_function(env, binding->on_setup, NULL, on_setup, num_params, params));
}
AWS_NAPI_ENSURE(env, aws_napi_release_threadsafe_function(binding->on_setup, napi_tsfn_abort));
if (args->error_code) {
/* setup failed, shutdown will never get invoked. Clean up the functions here */
AWS_NAPI_ENSURE(env, aws_napi_release_threadsafe_function(binding->on_shutdown, napi_tsfn_abort));
}
aws_mem_release(binding->allocator, args);
}
static void s_http_on_connection_setup(struct aws_http_connection *connection, int error_code, void *user_data) {
struct http_connection_binding *binding = user_data;
binding->connection = connection;
if (binding->on_setup) {
struct on_connection_args *args = aws_mem_calloc(binding->allocator, 1, sizeof(struct on_connection_args));
args->binding = binding;
args->error_code = error_code;
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_setup, args));
}
}
static void s_http_on_connection_shutdown_call(napi_env env, napi_value on_shutdown, void *context, void *user_data) {
struct http_connection_binding *binding = context;
struct on_connection_args *args = user_data;
if (env) {
napi_value params[2];
const size_t num_params = AWS_ARRAY_SIZE(params);
AWS_NAPI_ENSURE(env, napi_get_reference_value(env, args->binding->node_external, &params[0]));
AWS_NAPI_ENSURE(env, napi_create_uint32(env, args->error_code, &params[1]));
AWS_NAPI_ENSURE(
env,
aws_napi_dispatch_threadsafe_function(env, binding->on_shutdown, NULL, on_shutdown, num_params, params));
}
AWS_NAPI_ENSURE(env, aws_napi_release_threadsafe_function(binding->on_shutdown, napi_tsfn_abort));
aws_mem_release(binding->allocator, args);
}
static void s_http_on_connection_shutdown(struct aws_http_connection *connection, int error_code, void *user_data) {
struct http_connection_binding *binding = user_data;
binding->connection = connection;
if (binding->on_shutdown) {
struct on_connection_args *args = aws_mem_calloc(binding->allocator, 1, sizeof(struct on_connection_args));
args->binding = binding;
args->error_code = error_code;
AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_shutdown, args));
}
}
/* finalizer called when node cleans up this object */
static void s_http_connection_binding_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
(void)env;
(void)finalize_hint;
struct http_connection_binding *binding = finalize_data;
aws_http_connection_release(binding->connection);
aws_mem_release(binding->allocator, binding);
}
napi_value aws_napi_http_connection_new(napi_env env, napi_callback_info info) {
struct aws_allocator *allocator = aws_napi_get_allocator();
napi_value result = NULL;
struct aws_tls_connection_options *tls_opts = NULL;
struct aws_http_proxy_options *proxy_opts = NULL;
struct aws_string *host_name = NULL;
struct aws_http_client_connection_options options = AWS_HTTP_CLIENT_CONNECTION_OPTIONS_INIT;
options.allocator = allocator;
/* parse/validate arguments */
napi_value node_args[8];
size_t num_args = AWS_ARRAY_SIZE(node_args);
napi_value *arg = &node_args[0];
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
napi_throw_error(env, NULL, "Failed to retrieve callback information");
return NULL;
});
if (num_args != AWS_ARRAY_SIZE(node_args)) {
napi_throw_error(env, NULL, "http_connection_new needs exactly 7 arguments");
return NULL;
}
napi_value node_bootstrap = *arg++;
struct aws_client_bootstrap *bootstrap = NULL;
struct client_bootstrap_binding *bootstrap_binding = NULL;
napi_get_value_external(env, node_bootstrap, (void **)&bootstrap_binding);
if (bootstrap_binding != NULL) {
bootstrap = aws_napi_get_client_bootstrap(bootstrap_binding);
} else {
bootstrap = aws_napi_get_default_client_bootstrap();
}
/* create node external to hold the connection wrapper, cleanup is required from here on out */
struct http_connection_binding *binding = aws_mem_calloc(allocator, 1, sizeof(struct http_connection_binding));
if (!binding) {
aws_napi_throw_last_error(env);
goto alloc_failed;
}
binding->allocator = allocator;
binding->env = env;
napi_value node_on_setup = *arg++;
if (aws_napi_is_null_or_undefined(env, node_on_setup)) {
napi_throw_error(env, NULL, "on_connection_setup must be a callback");
return NULL;
}
AWS_NAPI_CALL(
env,
aws_napi_create_threadsafe_function(
env,
node_on_setup,
"aws_http_connection_on_connection_setup",
s_http_on_connection_setup_call,
binding,
&binding->on_setup),
{ goto failed_callbacks; });
napi_value node_on_shutdown = *arg++;
if (!aws_napi_is_null_or_undefined(env, node_on_shutdown)) {
AWS_NAPI_CALL(
env,
aws_napi_create_threadsafe_function(
env,
node_on_shutdown,
"aws_http_connection_on_connection_shutdown",
s_http_on_connection_shutdown_call,
binding,
&binding->on_shutdown),
{ goto failed_callbacks; });
}
/* will be owned by tls_options */
napi_value node_host_name = *arg++;
host_name = aws_string_new_from_napi(env, node_host_name);
if (!host_name) {
napi_throw_type_error(env, NULL, "host_name must be a String");
goto argument_error;
}
napi_value node_port = *arg++;
uint32_t port = 0;
AWS_NAPI_CALL(env, napi_get_value_uint32(env, node_port, &port), {
napi_throw_type_error(env, NULL, "port must be a Number");
goto argument_error;
});
options.port = port;
napi_value node_socket_options = *arg++;
AWS_NAPI_CALL(env, napi_get_value_external(env, node_socket_options, (void **)&options.socket_options), {
napi_throw_error(env, NULL, "Unable to extract socket_options from external");
goto argument_error;
});
napi_value node_tls_opts = *arg++;
if (!aws_napi_is_null_or_undefined(env, node_tls_opts)) {
AWS_NAPI_CALL(env, napi_get_value_external(env, node_tls_opts, (void **)&tls_opts), {
napi_throw_error(env, NULL, "Failed to extract tls_ctx from external");
goto argument_error;
});
}
napi_value node_proxy_opts = *arg++;
if (!aws_napi_is_null_or_undefined(env, node_proxy_opts)) {
struct http_proxy_options_binding *proxy_binding = NULL;
AWS_NAPI_CALL(env, napi_get_value_external(env, node_proxy_opts, (void **)&proxy_binding), {
napi_throw_error(env, NULL, "Failed to extract tls_ctx from external");
goto argument_error;
});
/* proxy_options are copied internally, no need to go nuts on copies */
proxy_opts = &proxy_binding->native;
}
napi_value node_external = NULL;
AWS_NAPI_CALL(
env, napi_create_external(env, binding, s_http_connection_binding_finalize, binding, &node_external), {
napi_throw_error(env, NULL, "Failed to create napi external for http_connection_binding");
goto create_external_failed;
});
AWS_NAPI_CALL(env, napi_create_reference(env, node_external, 1, &binding->node_external), {
napi_throw_error(env, NULL, "Failed to reference node_external");
goto create_external_failed;
});
options.bootstrap = bootstrap;
options.host_name = aws_byte_cursor_from_string(host_name);
options.on_setup = s_http_on_connection_setup;
options.on_shutdown = s_http_on_connection_shutdown;
options.proxy_options = proxy_opts;
options.user_data = binding;
if (tls_opts) {
if (!tls_opts->server_name) {
struct aws_byte_cursor server_name_cursor = aws_byte_cursor_from_string(host_name);
aws_tls_connection_options_set_server_name(tls_opts, allocator, &server_name_cursor);
}
options.tls_options = tls_opts;
}
if (aws_http_client_connect(&options)) {
aws_napi_throw_last_error(env);
goto connect_failed;
}
result = node_external;
goto done;
connect_failed:
create_external_failed:
failed_callbacks:
if (binding) {
AWS_NAPI_ENSURE(env, aws_napi_release_threadsafe_function(binding->on_setup, napi_tsfn_abort));
AWS_NAPI_ENSURE(env, aws_napi_release_threadsafe_function(binding->on_shutdown, napi_tsfn_abort));
}
aws_mem_release(allocator, binding);
alloc_failed:
argument_error:
done:
aws_string_destroy(host_name);
return result;
}
napi_value aws_napi_http_connection_close(napi_env env, napi_callback_info info) {
napi_value node_args[1];
size_t num_args = AWS_ARRAY_SIZE(node_args);
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
napi_throw_error(env, NULL, "Failed to extract arguments");
return NULL;
});
if (num_args != AWS_ARRAY_SIZE(node_args)) {
napi_throw_error(env, NULL, "http_connection_close takes exactly 1 argument");
return NULL;
}
struct http_connection_binding *binding = NULL;
AWS_NAPI_CALL(env, napi_get_value_external(env, node_args[0], (void **)&binding), {
napi_throw_error(env, NULL, "Failed to extract http_connection_binding from external");
return NULL;
});
if (binding->connection) {
aws_http_connection_close(binding->connection);
}
/* the rest of cleanup happens in s_http_connection_binding_finalize() */
return NULL;
}