365 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			365 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/**
 | 
						|
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 | 
						|
 * SPDX-License-Identifier: Apache-2.0.
 | 
						|
 */
 | 
						|
 | 
						|
#include "http_connection_manager.h"
 | 
						|
#include "http_connection.h"
 | 
						|
#include "io.h"
 | 
						|
 | 
						|
#include <aws/http/connection_manager.h>
 | 
						|
#include <aws/http/proxy.h>
 | 
						|
#include <aws/io/socket.h>
 | 
						|
#include <aws/io/tls_channel_handler.h>
 | 
						|
 | 
						|
struct http_connection_manager_binding {
 | 
						|
    struct aws_http_connection_manager *manager;
 | 
						|
    struct aws_allocator *allocator;
 | 
						|
    napi_env env;
 | 
						|
    napi_ref node_external;
 | 
						|
    napi_threadsafe_function on_shutdown;
 | 
						|
};
 | 
						|
 | 
						|
struct aws_http_connection_manager *aws_napi_get_http_connection_manager(
 | 
						|
    struct http_connection_manager_binding *binding) {
 | 
						|
    return binding->manager;
 | 
						|
}
 | 
						|
 | 
						|
static void s_http_connection_manager_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
 | 
						|
    (void)finalize_hint;
 | 
						|
    struct http_connection_manager_binding *binding = finalize_data;
 | 
						|
    if (binding->node_external != NULL) {
 | 
						|
        napi_delete_reference(env, binding->node_external);
 | 
						|
    }
 | 
						|
    aws_mem_release(binding->allocator, binding);
 | 
						|
}
 | 
						|
 | 
						|
static void s_http_connection_manager_shutdown_call(
 | 
						|
    napi_env env,
 | 
						|
    napi_value on_shutdown,
 | 
						|
    void *context,
 | 
						|
    void *user_data) {
 | 
						|
    struct http_connection_manager_binding *binding = context;
 | 
						|
    (void)user_data;
 | 
						|
    if (env) {
 | 
						|
        AWS_NAPI_ENSURE(
 | 
						|
            env, aws_napi_dispatch_threadsafe_function(env, binding->on_shutdown, NULL, on_shutdown, 0, NULL));
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void s_http_connection_manager_shutdown_complete(void *user_data) {
 | 
						|
    struct http_connection_manager_binding *binding = user_data;
 | 
						|
    if (binding->on_shutdown) {
 | 
						|
        AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(binding->on_shutdown, NULL));
 | 
						|
    }
 | 
						|
    AWS_NAPI_ENSURE(binding->env, aws_napi_release_threadsafe_function(binding->on_shutdown, napi_tsfn_abort));
 | 
						|
}
 | 
						|
 | 
						|
napi_value aws_napi_http_connection_manager_new(napi_env env, napi_callback_info info) {
 | 
						|
 | 
						|
    napi_value result = NULL;
 | 
						|
 | 
						|
    napi_value node_args[9];
 | 
						|
    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, "Unable to get callback info");
 | 
						|
        return NULL;
 | 
						|
    });
 | 
						|
    if (num_args != AWS_ARRAY_SIZE(node_args)) {
 | 
						|
        napi_throw_error(env, NULL, "http_connection_manager_new takes exactly 8 arguments");
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    struct aws_allocator *allocator = aws_napi_get_allocator();
 | 
						|
    struct aws_http_connection_manager_options options;
 | 
						|
    AWS_ZERO_STRUCT(options);
 | 
						|
    struct aws_byte_buf host_buf;
 | 
						|
    AWS_ZERO_STRUCT(host_buf);
 | 
						|
    struct aws_tls_connection_options tls_connection_options;
 | 
						|
    AWS_ZERO_STRUCT(tls_connection_options);
 | 
						|
 | 
						|
    napi_value node_bootstrap = *arg++;
 | 
						|
    struct client_bootstrap_binding *client_bootstrap_binding = NULL;
 | 
						|
    napi_get_value_external(env, node_bootstrap, (void **)&client_bootstrap_binding);
 | 
						|
    if (client_bootstrap_binding != NULL) {
 | 
						|
        options.bootstrap = aws_napi_get_client_bootstrap(client_bootstrap_binding);
 | 
						|
    } else {
 | 
						|
        options.bootstrap = aws_napi_get_default_client_bootstrap();
 | 
						|
    }
 | 
						|
 | 
						|
    napi_value node_host = *arg++;
 | 
						|
    if (aws_byte_buf_init_from_napi(&host_buf, env, node_host)) {
 | 
						|
        napi_throw_type_error(env, NULL, "host must be a string");
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    options.host = aws_byte_cursor_from_buf(&host_buf);
 | 
						|
 | 
						|
    struct http_connection_manager_binding *binding =
 | 
						|
        aws_mem_calloc(allocator, 1, sizeof(struct http_connection_manager_binding));
 | 
						|
    AWS_FATAL_ASSERT(binding);
 | 
						|
 | 
						|
    binding->allocator = allocator;
 | 
						|
    binding->env = env;
 | 
						|
 | 
						|
    napi_value node_port = *arg++;
 | 
						|
    uint32_t port = 0;
 | 
						|
    if (napi_get_value_uint32(env, node_port, &port)) {
 | 
						|
        napi_throw_type_error(env, NULL, "port must be a number between 0 and 4294967296");
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
    options.port = port;
 | 
						|
 | 
						|
    napi_value node_max_conns = *arg++;
 | 
						|
    uint32_t max_connections = 0;
 | 
						|
    AWS_NAPI_CALL(env, napi_get_value_uint32(env, node_max_conns, &max_connections), {
 | 
						|
        napi_throw_type_error(env, NULL, "max_connections must be a number");
 | 
						|
        goto cleanup;
 | 
						|
    });
 | 
						|
    options.max_connections = (size_t)max_connections;
 | 
						|
 | 
						|
    napi_value node_window_size = *arg++;
 | 
						|
    uint32_t window_size = 16 * 1024;
 | 
						|
    AWS_NAPI_CALL(env, napi_get_value_uint32(env, node_window_size, &window_size), {
 | 
						|
        napi_throw_type_error(env, NULL, "initial_window_size must be a number");
 | 
						|
        goto cleanup;
 | 
						|
    });
 | 
						|
    options.initial_window_size = (size_t)window_size;
 | 
						|
 | 
						|
    napi_value node_socket_options = *arg++;
 | 
						|
    const struct aws_socket_options *socket_options = NULL;
 | 
						|
    if (!aws_napi_is_null_or_undefined(env, node_socket_options)) {
 | 
						|
        AWS_NAPI_CALL(env, napi_get_value_external(env, node_socket_options, (void **)&socket_options), {
 | 
						|
            napi_throw_type_error(env, NULL, "socket_options must be undefined or a valid SocketOptions");
 | 
						|
            goto cleanup;
 | 
						|
        });
 | 
						|
    }
 | 
						|
    options.socket_options = socket_options;
 | 
						|
 | 
						|
    napi_value node_tls_opts = *arg++;
 | 
						|
    struct aws_tls_connection_options *tls_opts = NULL;
 | 
						|
    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_type_error(env, NULL, "tls_opts must be undefined or a valid TlsConnectionOptions");
 | 
						|
            goto cleanup;
 | 
						|
        });
 | 
						|
    }
 | 
						|
    options.tls_connection_options = tls_opts;
 | 
						|
 | 
						|
    napi_value node_proxy_options = *arg++;
 | 
						|
    struct aws_http_proxy_options *proxy_options = NULL;
 | 
						|
    if (!aws_napi_is_null_or_undefined(env, node_proxy_options)) {
 | 
						|
        struct http_proxy_options_binding *proxy_binding = NULL;
 | 
						|
        AWS_NAPI_CALL(env, napi_get_value_external(env, node_proxy_options, (void **)&proxy_binding), {
 | 
						|
            napi_throw_type_error(env, NULL, "tls_opts must be undefined or a valid TlsConnectionOptions");
 | 
						|
            goto cleanup;
 | 
						|
        });
 | 
						|
        proxy_options = aws_napi_get_http_proxy_options(proxy_binding);
 | 
						|
    }
 | 
						|
    /* proxy_options are copied internally, no need to go nuts on copies */
 | 
						|
    options.proxy_options = proxy_options;
 | 
						|
 | 
						|
    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_manager_on_shutdown",
 | 
						|
                s_http_connection_manager_shutdown_call,
 | 
						|
                binding,
 | 
						|
                &binding->on_shutdown),
 | 
						|
            {
 | 
						|
                napi_throw_type_error(env, NULL, "on_shutdown must be a valid callback or undefined");
 | 
						|
                goto cleanup;
 | 
						|
            });
 | 
						|
    }
 | 
						|
 | 
						|
    options.shutdown_complete_callback = s_http_connection_manager_shutdown_complete;
 | 
						|
    options.shutdown_complete_user_data = binding;
 | 
						|
    binding->manager = aws_http_connection_manager_new(allocator, &options);
 | 
						|
    if (!binding->manager) {
 | 
						|
        aws_napi_throw_last_error(env);
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    napi_value node_external = NULL;
 | 
						|
    AWS_NAPI_CALL(env, napi_create_external(env, binding, s_http_connection_manager_finalize, NULL, &node_external), {
 | 
						|
        napi_throw_error(env, NULL, "Unable to create node external");
 | 
						|
        goto external_failed;
 | 
						|
    });
 | 
						|
    AWS_NAPI_CALL(env, napi_create_reference(env, node_external, 1, &binding->node_external), {
 | 
						|
        napi_throw_error(env, NULL, "Unable to create reference to node external");
 | 
						|
        goto external_failed;
 | 
						|
    });
 | 
						|
 | 
						|
    /* success, set the return value */
 | 
						|
    result = node_external;
 | 
						|
    goto done;
 | 
						|
 | 
						|
external_failed:
 | 
						|
    aws_http_connection_manager_release(binding->manager);
 | 
						|
 | 
						|
cleanup:
 | 
						|
done:
 | 
						|
    aws_tls_connection_options_clean_up(&tls_connection_options);
 | 
						|
    aws_byte_buf_clean_up(&host_buf);
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
napi_value aws_napi_http_connection_manager_close(napi_env env, napi_callback_info info) {
 | 
						|
 | 
						|
    napi_value node_args[1];
 | 
						|
    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, "Unable to get callback info");
 | 
						|
        return NULL;
 | 
						|
    });
 | 
						|
    if (num_args != AWS_ARRAY_SIZE(node_args)) {
 | 
						|
        napi_throw_error(env, NULL, "http_connection_manager_close takes exactly 1 argument");
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    napi_value node_external = *arg++;
 | 
						|
    struct http_connection_manager_binding *binding = NULL;
 | 
						|
    AWS_NAPI_CALL(env, napi_get_value_external(env, node_external, (void **)&binding), {
 | 
						|
        napi_throw_type_error(env, NULL, "connection_manager must be a valid HttpConnectionManager");
 | 
						|
        return NULL;
 | 
						|
    });
 | 
						|
 | 
						|
    aws_http_connection_manager_release(binding->manager);
 | 
						|
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
struct connection_acquired_args {
 | 
						|
    struct http_connection_manager_binding *binding;
 | 
						|
    napi_threadsafe_function on_acquired;
 | 
						|
    struct aws_http_connection *connection;
 | 
						|
    int error_code;
 | 
						|
};
 | 
						|
 | 
						|
static void s_http_connection_manager_on_acquired_call(
 | 
						|
    napi_env env,
 | 
						|
    napi_value on_acquired,
 | 
						|
    void *context,
 | 
						|
    void *user_data) {
 | 
						|
    struct http_connection_manager_binding *binding = context;
 | 
						|
    struct connection_acquired_args *args = user_data;
 | 
						|
 | 
						|
    if (env) {
 | 
						|
        napi_value connection_external = aws_napi_http_connection_from_manager(env, args->connection);
 | 
						|
        AWS_FATAL_ASSERT(connection_external);
 | 
						|
 | 
						|
        napi_value params[2];
 | 
						|
        const size_t num_params = AWS_ARRAY_SIZE(params);
 | 
						|
        params[0] = connection_external;
 | 
						|
        AWS_NAPI_ENSURE(env, napi_create_int32(env, args->error_code, ¶ms[1]));
 | 
						|
 | 
						|
        AWS_NAPI_ENSURE(
 | 
						|
            env, aws_napi_dispatch_threadsafe_function(env, args->on_acquired, NULL, on_acquired, num_params, params));
 | 
						|
 | 
						|
        AWS_NAPI_ENSURE(env, aws_napi_unref_threadsafe_function(env, args->on_acquired));
 | 
						|
    }
 | 
						|
 | 
						|
    aws_mem_release(binding->allocator, args);
 | 
						|
}
 | 
						|
 | 
						|
static void s_http_connection_manager_acquired(
 | 
						|
    struct aws_http_connection *connection,
 | 
						|
    int error_code,
 | 
						|
    void *user_data) {
 | 
						|
    struct connection_acquired_args *args = user_data;
 | 
						|
    args->connection = connection;
 | 
						|
    args->error_code = error_code;
 | 
						|
 | 
						|
    AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(args->on_acquired, args));
 | 
						|
}
 | 
						|
 | 
						|
napi_value aws_napi_http_connection_manager_acquire(napi_env env, napi_callback_info info) {
 | 
						|
    napi_value node_args[2];
 | 
						|
    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, "Unable to get callback info");
 | 
						|
        return NULL;
 | 
						|
    });
 | 
						|
    if (num_args != AWS_ARRAY_SIZE(node_args)) {
 | 
						|
        napi_throw_error(env, NULL, "http_connection_manager_acquire takes exactly 2 arguments");
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    napi_value node_external = *arg++;
 | 
						|
    struct http_connection_manager_binding *binding = NULL;
 | 
						|
    AWS_NAPI_CALL(env, napi_get_value_external(env, node_external, (void **)&binding), {
 | 
						|
        napi_throw_type_error(env, NULL, "connection_manager should be an external");
 | 
						|
        return NULL;
 | 
						|
    });
 | 
						|
 | 
						|
    struct connection_acquired_args *args =
 | 
						|
        aws_mem_calloc(binding->allocator, 1, sizeof(struct connection_acquired_args));
 | 
						|
    AWS_FATAL_ASSERT(args);
 | 
						|
    args->binding = binding;
 | 
						|
 | 
						|
    napi_value node_on_acquired = *arg++;
 | 
						|
    AWS_NAPI_CALL(
 | 
						|
        env,
 | 
						|
        aws_napi_create_threadsafe_function(
 | 
						|
            env,
 | 
						|
            node_on_acquired,
 | 
						|
            "aws_http_connection_manager_on_acquired",
 | 
						|
            s_http_connection_manager_on_acquired_call,
 | 
						|
            binding,
 | 
						|
            &args->on_acquired),
 | 
						|
        {
 | 
						|
            napi_throw_type_error(env, NULL, "on_acquired should be a valid callback");
 | 
						|
            goto failed;
 | 
						|
        });
 | 
						|
 | 
						|
    aws_http_connection_manager_acquire_connection(binding->manager, s_http_connection_manager_acquired, args);
 | 
						|
    return NULL;
 | 
						|
 | 
						|
failed:
 | 
						|
    aws_mem_release(binding->allocator, args);
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
napi_value aws_napi_http_connection_manager_release(napi_env env, napi_callback_info info) {
 | 
						|
    napi_value node_args[2];
 | 
						|
    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, "Unable to get callback info");
 | 
						|
        return NULL;
 | 
						|
    });
 | 
						|
    if (num_args != AWS_ARRAY_SIZE(node_args)) {
 | 
						|
        napi_throw_error(env, NULL, "http_connection_manager_release takes exactly 2 arguments");
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    napi_value node_external = *arg++;
 | 
						|
    struct http_connection_manager_binding *binding = NULL;
 | 
						|
    AWS_NAPI_CALL(env, napi_get_value_external(env, node_external, (void **)&binding), {
 | 
						|
        napi_throw_type_error(env, NULL, "connection_manager should be an external");
 | 
						|
        return NULL;
 | 
						|
    });
 | 
						|
 | 
						|
    napi_value node_connection = *arg++;
 | 
						|
    struct http_connection_binding *connection_binding = NULL;
 | 
						|
    AWS_NAPI_CALL(env, napi_get_value_external(env, node_connection, (void **)&connection_binding), {
 | 
						|
        napi_throw_type_error(env, NULL, "connection should be an external");
 | 
						|
        return NULL;
 | 
						|
    });
 | 
						|
 | 
						|
    struct aws_http_connection *connection = aws_napi_get_http_connection(connection_binding);
 | 
						|
    if (aws_http_connection_manager_release_connection(binding->manager, connection)) {
 | 
						|
        aws_napi_throw_last_error(env);
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    return NULL;
 | 
						|
}
 |