460 lines
17 KiB
C
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, ¶ms[0]));
|
|
AWS_NAPI_ENSURE(env, napi_create_uint32(env, args->error_code, ¶ms[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, ¶ms[0]));
|
|
AWS_NAPI_ENSURE(env, napi_create_uint32(env, args->error_code, ¶ms[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;
|
|
}
|