in source/websocket_bootstrap.c [78:237]
static void s_ws_bootstrap_cancel_setup_due_to_err(
struct aws_websocket_client_bootstrap *ws_bootstrap,
struct aws_http_connection *http_connection,
int error_code);
static void s_ws_bootstrap_on_http_setup(struct aws_http_connection *http_connection, int error_code, void *user_data);
static void s_ws_bootstrap_on_http_shutdown(
struct aws_http_connection *http_connection,
int error_code,
void *user_data);
static int s_ws_bootstrap_on_handshake_response_headers(
struct aws_http_stream *stream,
enum aws_http_header_block header_block,
const struct aws_http_header *header_array,
size_t num_headers,
void *user_data);
static int s_ws_bootstrap_on_handshake_response_header_block_done(
struct aws_http_stream *stream,
enum aws_http_header_block header_block,
void *user_data);
static void s_ws_bootstrap_on_stream_complete(struct aws_http_stream *stream, int error_code, void *user_data);
int aws_websocket_client_connect(const struct aws_websocket_client_connection_options *options) {
aws_http_fatal_assert_library_initialized();
AWS_ASSERT(options);
/* Validate options */
struct aws_byte_cursor path;
aws_http_message_get_request_path(options->handshake_request, &path);
if (!options->allocator || !options->bootstrap || !options->socket_options || !options->host.len || !path.len ||
!options->on_connection_setup) {
AWS_LOGF_ERROR(AWS_LS_HTTP_WEBSOCKET_SETUP, "id=static: Missing required websocket connection options.");
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
struct aws_byte_cursor method;
aws_http_message_get_request_method(options->handshake_request, &method);
if (aws_http_str_to_method(method) != AWS_HTTP_METHOD_GET) {
AWS_LOGF_ERROR(AWS_LS_HTTP_WEBSOCKET_SETUP, "id=static: Websocket request must have method be 'GET'.");
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
bool all_frame_callbacks_set =
options->on_incoming_frame_begin && options->on_incoming_frame_payload && options->on_incoming_frame_begin;
bool no_frame_callbacks_set =
!options->on_incoming_frame_begin && !options->on_incoming_frame_payload && !options->on_incoming_frame_begin;
if (!(all_frame_callbacks_set || no_frame_callbacks_set)) {
AWS_LOGF_ERROR(
AWS_LS_HTTP_WEBSOCKET_SETUP,
"id=static: Invalid websocket connection options,"
" either all frame-handling callbacks must be set, or none must be set.");
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
if (!options->handshake_request) {
AWS_LOGF_ERROR(
AWS_LS_HTTP_WEBSOCKET_SETUP,
"id=static: Invalid connection options, missing required request for websocket client handshake.");
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
/* Create bootstrap */
struct aws_websocket_client_bootstrap *ws_bootstrap =
aws_mem_calloc(options->allocator, 1, sizeof(struct aws_websocket_client_bootstrap));
if (!ws_bootstrap) {
goto error;
}
ws_bootstrap->alloc = options->allocator;
ws_bootstrap->initial_window_size = options->initial_window_size;
ws_bootstrap->manual_window_update = options->manual_window_management;
ws_bootstrap->user_data = options->user_data;
ws_bootstrap->websocket_setup_callback = options->on_connection_setup;
ws_bootstrap->websocket_shutdown_callback = options->on_connection_shutdown;
ws_bootstrap->websocket_frame_begin_callback = options->on_incoming_frame_begin;
ws_bootstrap->websocket_frame_payload_callback = options->on_incoming_frame_payload;
ws_bootstrap->websocket_frame_complete_callback = options->on_incoming_frame_complete;
ws_bootstrap->handshake_request = options->handshake_request;
ws_bootstrap->response_status = AWS_HTTP_STATUS_CODE_UNKNOWN;
/* Pre-allocate space for response headers */
/* Values are just guesses */
size_t estimated_response_headers = aws_http_message_get_header_count(ws_bootstrap->handshake_request) + 10;
size_t estimated_response_header_length = 64;
int err = aws_array_list_init_dynamic(
&ws_bootstrap->response_headers,
ws_bootstrap->alloc,
estimated_response_headers,
sizeof(struct aws_http_header));
if (err) {
goto error;
}
err = aws_byte_buf_init(
&ws_bootstrap->response_storage,
ws_bootstrap->alloc,
estimated_response_headers * estimated_response_header_length);
if (err) {
goto error;
}
/* Initiate HTTP connection */
struct aws_http_client_connection_options http_options = AWS_HTTP_CLIENT_CONNECTION_OPTIONS_INIT;
http_options.allocator = ws_bootstrap->alloc;
http_options.bootstrap = options->bootstrap;
http_options.host_name = options->host;
http_options.socket_options = options->socket_options;
http_options.tls_options = options->tls_options;
http_options.proxy_options = options->proxy_options;
http_options.initial_window_size = 1024; /* Adequate space for response data to trickle in */
/* TODO: websockets has issues if back-pressure is disabled on the whole channel. This should be fixed. */
http_options.manual_window_management = true;
http_options.user_data = ws_bootstrap;
http_options.on_setup = s_ws_bootstrap_on_http_setup;
http_options.on_shutdown = s_ws_bootstrap_on_http_shutdown;
/* Infer port, if not explicitly specified in URI */
http_options.port = options->port;
if (!http_options.port) {
http_options.port = options->tls_options ? 443 : 80;
}
err = s_system_vtable->aws_http_client_connect(&http_options);
if (err) {
AWS_LOGF_ERROR(
AWS_LS_HTTP_WEBSOCKET_SETUP,
"id=static: Websocket failed to initiate HTTP connection, error %d (%s)",
aws_last_error(),
aws_error_name(aws_last_error()));
goto error_already_logged;
}
/* Success! (so far) */
AWS_LOGF_TRACE(
AWS_LS_HTTP_WEBSOCKET_SETUP,
"id=%p: Websocket setup begun, connecting to " PRInSTR ":%" PRIu16 PRInSTR,
(void *)ws_bootstrap,
AWS_BYTE_CURSOR_PRI(options->host),
options->port,
AWS_BYTE_CURSOR_PRI(path));
return AWS_OP_SUCCESS;
error:
AWS_LOGF_ERROR(
AWS_LS_HTTP_WEBSOCKET_SETUP,
"id=static: Failed to initiate websocket connection, error %d (%s)",
aws_last_error(),
aws_error_name(aws_last_error()));
error_already_logged:
s_ws_bootstrap_destroy(ws_bootstrap);
return AWS_OP_ERR;
}