From dd9bfbe37e615ff936cb6063bb22fb93d512c317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sat, 26 Dec 2015 01:39:08 +0100 Subject: [PATCH] Refactor backends That was fucking ugly. --- json-rpc-shell.c | 563 +++++++++++++++++++++++------------------------ 1 file changed, 270 insertions(+), 293 deletions(-) diff --git a/json-rpc-shell.c b/json-rpc-shell.c index 16bc31e..0e90e01 100644 --- a/json-rpc-shell.c +++ b/json-rpc-shell.c @@ -792,124 +792,6 @@ input_el_new (void) // --- Main program ------------------------------------------------------------ -// HTTP/S and WS/S require significantly different handling. While for HTTP we -// can just use the cURL easy interface, with WebSockets it gets a bit more -// complicated and we implement it all by ourselves. -// -// Luckily on a higher level the application doesn't need to bother itself with -// the details and the backend API can be very simple. - -struct app_context; - -struct backend_iface -{ - /// Prepare the backend for RPC calls - void (*init) (struct app_context *ctx, - const char *endpoint, struct http_parser_url *url); - - /// Add an HTTP header to send with requests - void (*add_header) (struct app_context *ctx, const char *header); - - /// Make an RPC call - bool (*make_call) (struct app_context *ctx, - const char *request, bool expect_content, - struct str *buf, struct error **e); - - /// Do everything necessary to deal with ev_break(EVBREAK_ALL) - void (*on_quit) (struct app_context *ctx); - - /// Free any resources - void (*destroy) (struct app_context *ctx); -}; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -enum ws_handler_state -{ - WS_HANDLER_CONNECTING, ///< Parsing HTTP - WS_HANDLER_OPEN, ///< Parsing WebSockets frames - WS_HANDLER_CLOSING, ///< Closing the connection - WS_HANDLER_CLOSED ///< Dead -}; - -#define BACKEND_WS_MAX_PAYLOAD_LEN UINT32_MAX - -struct ws_context -{ - // Configuration: - - char *endpoint; ///< Endpoint URL - struct http_parser_url url; ///< Parsed URL - struct str_vector extra_headers; ///< Extra headers for the handshake - - // Events: - - bool waiting_for_event; ///< Running a separate loop to wait? - struct error *e; ///< Error while waiting for event - - ev_timer timeout_watcher; ///< Connection timeout watcher - struct str *response_buffer; ///< Buffer for the incoming messages - - // The TCP transport: - - int server_fd; ///< Socket FD of the server - ev_io read_watcher; ///< Server FD read watcher - SSL_CTX *ssl_ctx; ///< SSL context - SSL *ssl; ///< SSL connection - - // WebSockets protocol handling: - - enum ws_handler_state state; ///< State - char *key; ///< Key for the current handshake - - http_parser hp; ///< HTTP parser - bool parsing_header_value; ///< Parsing header value or field? - struct str field; ///< Field part buffer - struct str value; ///< Value part buffer - struct str_map headers; ///< HTTP Headers - - struct ws_parser parser; ///< Protocol frame parser - bool expecting_continuation; ///< For non-control traffic - - enum ws_opcode message_opcode; ///< Opcode for the current message - struct str message_data; ///< Concatenated message data -}; - -static void -ws_context_init (struct ws_context *self) -{ - memset (self, 0, sizeof *self); - ev_timer_init (&self->timeout_watcher, NULL, 0, 0); - self->server_fd = -1; - ev_io_init (&self->read_watcher, NULL, 0, 0); - http_parser_init (&self->hp, HTTP_RESPONSE); - str_init (&self->field); - str_init (&self->value); - str_map_init (&self->headers); - self->headers.key_xfrm = tolower_ascii_strxfrm; - self->headers.free = free; - ws_parser_init (&self->parser); - str_init (&self->message_data); - str_vector_init (&self->extra_headers); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -struct curl_context -{ - CURL *curl; ///< cURL handle - char curl_error[CURL_ERROR_SIZE]; ///< cURL error info buffer - struct curl_slist *headers; ///< Headers -}; - -static void -curl_context_init (struct curl_context *self) -{ - memset (self, 0, sizeof *self); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - enum color_mode { COLOR_AUTO, ///< Autodetect if colours are available @@ -923,10 +805,7 @@ static struct app_context char *attrs_defaults[ATTR_COUNT]; ///< Default terminal attributes char *attrs[ATTR_COUNT]; ///< Terminal attributes - struct backend_iface *backend; ///< Our current backend - - struct ws_context ws; ///< WebSockets backend data - struct curl_context curl; ///< cURL backend data + struct backend *backend; ///< Our current backend struct config config; ///< Program configuration enum color_mode color_mode; ///< Colour output mode @@ -942,6 +821,37 @@ static struct app_context } g_ctx; +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// HTTP/S and WS/S require significantly different handling. While for HTTP we +// can just use the cURL easy interface, with WebSockets it gets a bit more +// complicated and we implement it all by ourselves. +// +// Luckily on a higher level the application doesn't need to bother itself with +// the details and the backend API can be very simple. + +struct backend +{ + struct backend_vtable *vtable; ///< Virtual methods +}; + +struct backend_vtable +{ + /// Add an HTTP header to send with requests + void (*add_header) (struct backend *backend, const char *header); + + /// Make an RPC call + bool (*make_call) (struct backend *backend, + const char *request, bool expect_content, + struct str *buf, struct error **e); + + /// Do everything necessary to deal with ev_break(EVBREAK_ALL) + void (*on_quit) (struct backend *backend); + + /// Free any resources + void (*destroy) (struct backend *backend); +}; + // --- Configuration ----------------------------------------------------------- static void on_config_attribute_change (struct config_item *item); @@ -1227,25 +1137,65 @@ on_config_attribute_change (struct config_item *item) // --- WebSockets backend ------------------------------------------------------ -static void -backend_ws_init (struct app_context *ctx, - const char *endpoint, struct http_parser_url *url) +enum ws_handler_state { - struct ws_context *self = &ctx->ws; - ws_context_init (self); - self->endpoint = xstrdup (endpoint); - self->url = *url; + WS_HANDLER_CONNECTING, ///< Parsing HTTP + WS_HANDLER_OPEN, ///< Parsing WebSockets frames + WS_HANDLER_CLOSING, ///< Closing the connection + WS_HANDLER_CLOSED ///< Dead +}; - SSL_library_init (); - atexit (EVP_cleanup); - SSL_load_error_strings (); - atexit (ERR_free_strings); -} +#define BACKEND_WS_MAX_PAYLOAD_LEN UINT32_MAX + +struct ws_context +{ + struct backend super; ///< Parent class + struct app_context *ctx; ///< Application context + + // Configuration: + + char *endpoint; ///< Endpoint URL + struct http_parser_url url; ///< Parsed URL + struct str_vector extra_headers; ///< Extra headers for the handshake + + // Events: + + bool waiting_for_event; ///< Running a separate loop to wait? + struct error *e; ///< Error while waiting for event + + ev_timer timeout_watcher; ///< Connection timeout watcher + struct str *response_buffer; ///< Buffer for the incoming messages + + // The TCP transport: + + int server_fd; ///< Socket FD of the server + ev_io read_watcher; ///< Server FD read watcher + SSL_CTX *ssl_ctx; ///< SSL context + SSL *ssl; ///< SSL connection + + // WebSockets protocol handling: + + enum ws_handler_state state; ///< State + char *key; ///< Key for the current handshake + + http_parser hp; ///< HTTP parser + bool parsing_header_value; ///< Parsing header value or field? + struct str field; ///< Field part buffer + struct str value; ///< Value part buffer + struct str_map headers; ///< HTTP Headers + + struct ws_parser parser; ///< Protocol frame parser + bool expecting_continuation; ///< For non-control traffic + + enum ws_opcode message_opcode; ///< Opcode for the current message + struct str message_data; ///< Concatenated message data +}; static void -backend_ws_add_header (struct app_context *ctx, const char *header) +backend_ws_add_header (struct backend *backend, const char *header) { - str_vector_add (&ctx->ws.extra_headers, header); + struct ws_context *self = (struct ws_context *) backend; + str_vector_add (&self->extra_headers, header); } enum ws_read_result @@ -1258,10 +1208,9 @@ enum ws_read_result static enum ws_read_result backend_ws_fill_read_buffer_tls - (struct app_context *ctx, void *buf, size_t *len) + (struct ws_context *self, void *buf, size_t *len) { int n_read; - struct ws_context *self = &ctx->ws; start: n_read = SSL_read (self->ssl, buf, *len); @@ -1293,10 +1242,9 @@ start: static enum ws_read_result backend_ws_fill_read_buffer - (struct app_context *ctx, void *buf, size_t *len) + (struct ws_context *self, void *buf, size_t *len) { ssize_t n_read; - struct ws_context *self = &ctx->ws; start: n_read = recv (self->server_fd, buf, *len, 0); if (n_read > 0) @@ -1391,9 +1339,8 @@ backend_ws_on_headers_complete (http_parser *parser) } static bool -backend_ws_finish_handshake (struct app_context *ctx, struct error **e) +backend_ws_finish_handshake (struct ws_context *self, struct error **e) { - struct ws_context *self = &ctx->ws; if (self->hp.http_major != 1 || self->hp.http_minor < 1) FAIL ("incompatible HTTP version: %d.%d", self->hp.http_major, self->hp.http_minor); @@ -1429,9 +1376,8 @@ backend_ws_finish_handshake (struct app_context *ctx, struct error **e) } static bool -backend_ws_on_data (struct app_context *ctx, const void *data, size_t len) +backend_ws_on_data (struct ws_context *self, const void *data, size_t len) { - struct ws_context *self = &ctx->ws; if (self->state != WS_HANDLER_CONNECTING) return ws_parser_push (&self->parser, data, len); @@ -1449,7 +1395,7 @@ backend_ws_on_data (struct app_context *ctx, const void *data, size_t len) if (self->hp.upgrade) { struct error *e = NULL; - if (!backend_ws_finish_handshake (ctx, &e)) + if (!backend_ws_finish_handshake (self, &e)) { print_error ("WS handshake failed: %s", e->message); error_free (e); @@ -1482,9 +1428,8 @@ backend_ws_on_data (struct app_context *ctx, const void *data, size_t len) } static void -backend_ws_close_connection (struct app_context *ctx) +backend_ws_close_connection (struct ws_context *self) { - struct ws_context *self = &ctx->ws; if (self->server_fd == -1) return; @@ -1519,10 +1464,9 @@ backend_ws_on_fd_ready (EV_P_ ev_io *handle, int revents) (void) loop; (void) revents; - struct app_context *ctx = handle->data; - struct ws_context *self = &ctx->ws; + struct ws_context *self = handle->data; - enum ws_read_result (*fill_buffer)(struct app_context *, void *, size_t *) + enum ws_read_result (*fill_buffer)(struct ws_context *, void *, size_t *) = self->ssl ? backend_ws_fill_read_buffer_tls : backend_ws_fill_read_buffer; @@ -1534,7 +1478,7 @@ backend_ws_on_fd_ready (EV_P_ ev_io *handle, int revents) // Try to read some data in a non-blocking manner size_t n_read = sizeof buf; (void) set_blocking (self->server_fd, false); - enum ws_read_result result = fill_buffer (ctx, buf, &n_read); + enum ws_read_result result = fill_buffer (self, buf, &n_read); (void) set_blocking (self->server_fd, true); switch (result) @@ -1550,7 +1494,7 @@ backend_ws_on_fd_ready (EV_P_ ev_io *handle, int revents) close_connection = true; goto end; case WS_READ_OK: - if (backend_ws_on_data (ctx, buf, n_read)) + if (backend_ws_on_data (self, buf, n_read)) break; // XXX: maybe we should wait until we receive an EOF @@ -1561,26 +1505,26 @@ backend_ws_on_fd_ready (EV_P_ ev_io *handle, int revents) end: if (close_connection) - backend_ws_close_connection (ctx); + backend_ws_close_connection (self); } static bool -backend_ws_write (struct app_context *ctx, const void *data, size_t len) +backend_ws_write (struct ws_context *self, const void *data, size_t len) { - if (!soft_assert (ctx->ws.server_fd != -1)) + if (!soft_assert (self->server_fd != -1)) return false; - if (ctx->ws.ssl) + if (self->ssl) { // TODO: call SSL_get_error() to detect if a clean shutdown has occured - if (SSL_write (ctx->ws.ssl, data, len) != (int) len) + if (SSL_write (self->ssl, data, len) != (int) len) { print_debug ("%s: %s: %s", __func__, "SSL_write", ERR_error_string (ERR_get_error (), NULL)); return false; } } - else if (write (ctx->ws.server_fd, data, len) != (ssize_t) len) + else if (write (self->server_fd, data, len) != (ssize_t) len) { print_debug ("%s: %s: %s", __func__, "write", strerror (errno)); return false; @@ -1589,7 +1533,7 @@ backend_ws_write (struct app_context *ctx, const void *data, size_t len) } static bool -backend_ws_establish_connection (struct app_context *ctx, +backend_ws_establish_connection (struct ws_context *self, const char *host, const char *port, struct error **e) { struct addrinfo gai_hints, *gai_result, *gai_iter; @@ -1626,7 +1570,7 @@ backend_ws_establish_connection (struct app_context *ctx, else real_host = buf; - if (ctx->verbose) + if (self->ctx->verbose) { char *address = format_host_port_pair (real_host, port); print_status ("connecting to %s...", address); @@ -1644,15 +1588,14 @@ backend_ws_establish_connection (struct app_context *ctx, if (!gai_iter) FAIL ("connection failed"); - ctx->ws.server_fd = sockfd; + self->server_fd = sockfd; return true; } static bool -backend_ws_set_up_ssl_ctx (struct app_context *ctx) +backend_ws_set_up_ssl_ctx (struct ws_context *self) { - struct ws_context *self = &ctx->ws; - if (ctx->trust_all) + if (self->ctx->trust_all) { SSL_CTX_set_verify (self->ssl_ctx, SSL_VERIFY_NONE, NULL); return true; @@ -1660,9 +1603,9 @@ backend_ws_set_up_ssl_ctx (struct app_context *ctx) // TODO: try to resolve filenames relative to configuration directories const char *ca_file = get_config_string - (ctx->config.root, "connection.tls_ca_file"); + (self->ctx->config.root, "connection.tls_ca_file"); const char *ca_path = get_config_string - (ctx->config.root, "connection.tls_ca_path"); + (self->ctx->config.root, "connection.tls_ca_path"); if (ca_file || ca_path) { if (SSL_CTX_load_verify_locations (self->ssl_ctx, ca_file, ca_path)) @@ -1675,16 +1618,15 @@ backend_ws_set_up_ssl_ctx (struct app_context *ctx) } static bool -backend_ws_initialize_tls (struct app_context *ctx, +backend_ws_initialize_tls (struct ws_context *self, const char *server_name, struct error **e) { - struct ws_context *self = &ctx->ws; const char *error_info = NULL; if (!self->ssl_ctx) { if (!(self->ssl_ctx = SSL_CTX_new (SSLv23_client_method ()))) goto error_ssl_1; - if (!backend_ws_set_up_ssl_ctx (ctx)) + if (!backend_ws_set_up_ssl_ctx (self)) goto error_ssl_2; } @@ -1730,7 +1672,7 @@ error_ssl_1: } static bool -backend_ws_send_message (struct app_context *ctx, +backend_ws_send_message (struct ws_context *self, enum ws_opcode opcode, const void *data, size_t len) { struct str header; @@ -1755,7 +1697,7 @@ backend_ws_send_message (struct app_context *ctx, return false; str_pack_u32 (&header, mask); - bool result = backend_ws_write (ctx, header.str, header.len); + bool result = backend_ws_write (self, header.str, header.len); str_free (&header); while (result && len) { @@ -1763,7 +1705,7 @@ backend_ws_send_message (struct app_context *ctx, char masked[block_size]; memcpy (masked, data, block_size); ws_parser_unmask (masked, block_size, mask); - result = backend_ws_write (ctx, masked, block_size); + result = backend_ws_write (self, masked, block_size); len -= block_size; data = (const uint8_t *) data + block_size; @@ -1772,7 +1714,7 @@ backend_ws_send_message (struct app_context *ctx, } static bool -backend_ws_send_control (struct app_context *ctx, +backend_ws_send_control (struct ws_context *self, enum ws_opcode opcode, const void *data, size_t len) { if (len > WS_MAX_CONTROL_PAYLOAD_LEN) @@ -1782,16 +1724,14 @@ backend_ws_send_control (struct app_context *ctx, len = WS_MAX_CONTROL_PAYLOAD_LEN; } - return backend_ws_send_message (ctx, opcode, data, len); + return backend_ws_send_message (self, opcode, data, len); } static bool -backend_ws_fail (struct app_context *ctx, enum ws_status reason) +backend_ws_fail (struct ws_context *self, enum ws_status reason) { - struct ws_context *self = &ctx->ws; - uint8_t payload[2] = { reason << 8, reason }; - (void) backend_ws_send_control (ctx, WS_OPCODE_CLOSE, + (void) backend_ws_send_control (self, WS_OPCODE_CLOSE, payload, sizeof payload); // The caller should immediately proceed to close the TCP connection, @@ -1803,8 +1743,7 @@ backend_ws_fail (struct app_context *ctx, enum ws_status reason) static bool backend_ws_on_frame_header (void *user_data, const struct ws_parser *parser) { - struct app_context *ctx = user_data; - struct ws_context *self = &ctx->ws; + struct ws_context *self = user_data; // Note that we aren't expected to send any close frame before closing the // connection when the frame is unmasked @@ -1816,15 +1755,15 @@ backend_ws_on_frame_header (void *user_data, const struct ws_parser *parser) || (!ws_is_control_frame (parser->opcode) && (self->expecting_continuation && parser->opcode != WS_OPCODE_CONT)) || parser->payload_len >= 0x8000000000000000ULL) - return backend_ws_fail (ctx, WS_STATUS_PROTOCOL_ERROR); + return backend_ws_fail (self, WS_STATUS_PROTOCOL_ERROR); else if (parser->payload_len > BACKEND_WS_MAX_PAYLOAD_LEN) - return backend_ws_fail (ctx, WS_STATUS_MESSAGE_TOO_BIG); + return backend_ws_fail (self, WS_STATUS_MESSAGE_TOO_BIG); return true; } static bool backend_ws_finish_closing_handshake - (struct app_context *ctx, const struct ws_parser *parser) + (struct ws_context *self, const struct ws_parser *parser) { struct str reason; str_init (&reason); @@ -1842,31 +1781,29 @@ backend_ws_finish_closing_handshake parser->input.str + 2, parser->payload_len - 2); } - char *s = iconv_xstrdup (ctx->term_from_utf8, + char *s = iconv_xstrdup (self->ctx->term_from_utf8, reason.str, reason.len, NULL); print_status ("server closed the connection (%s)", s); str_free (&reason); free (s); - return backend_ws_send_control (ctx, WS_OPCODE_CLOSE, + return backend_ws_send_control (self, WS_OPCODE_CLOSE, parser->input.str, parser->payload_len); } static bool backend_ws_on_control_frame - (struct app_context *ctx, const struct ws_parser *parser) + (struct ws_context *self, const struct ws_parser *parser) { - struct ws_context *self = &ctx->ws; switch (parser->opcode) { case WS_OPCODE_CLOSE: // We've received an unsolicited server close if (self->state != WS_HANDLER_CLOSING) - (void) backend_ws_finish_closing_handshake (ctx, parser); - + (void) backend_ws_finish_closing_handshake (self, parser); return false; case WS_OPCODE_PING: - if (!backend_ws_send_control (ctx, WS_OPCODE_PONG, + if (!backend_ws_send_control (self, WS_OPCODE_PONG, parser->input.str, parser->payload_len)) return false; break; @@ -1875,19 +1812,17 @@ backend_ws_on_control_frame break; default: // Unknown control frame - return backend_ws_fail (ctx, WS_STATUS_PROTOCOL_ERROR); + return backend_ws_fail (self, WS_STATUS_PROTOCOL_ERROR); } return true; } static bool -backend_ws_on_message (struct app_context *ctx, +backend_ws_on_message (struct ws_context *self, enum ws_opcode type, const void *data, size_t len) { - struct ws_context *self = &ctx->ws; - if (type != WS_OPCODE_TEXT) - return backend_ws_fail (ctx, WS_STATUS_UNSUPPORTED_DATA); + return backend_ws_fail (self, WS_STATUS_UNSUPPORTED_DATA); if (!self->waiting_for_event || !self->response_buffer) { @@ -1903,15 +1838,14 @@ backend_ws_on_message (struct app_context *ctx, static bool backend_ws_on_frame (void *user_data, const struct ws_parser *parser) { - struct app_context *ctx = user_data; - struct ws_context *self = &ctx->ws; + struct ws_context *self = user_data; if (ws_is_control_frame (parser->opcode)) - return backend_ws_on_control_frame (ctx, parser); + return backend_ws_on_control_frame (self, parser); // TODO: do this rather in "on_frame_header" if (self->message_data.len + parser->payload_len > BACKEND_WS_MAX_PAYLOAD_LEN) - return backend_ws_fail (ctx, WS_STATUS_MESSAGE_TOO_BIG); + return backend_ws_fail (self, WS_STATUS_MESSAGE_TOO_BIG); if (!self->expecting_continuation) self->message_opcode = parser->opcode; @@ -1925,9 +1859,9 @@ backend_ws_on_frame (void *user_data, const struct ws_parser *parser) if (self->message_opcode == WS_OPCODE_TEXT && !utf8_validate (self->parser.input.str, self->parser.input.len)) - return backend_ws_fail (ctx, WS_STATUS_INVALID_PAYLOAD_DATA); + return backend_ws_fail (self, WS_STATUS_INVALID_PAYLOAD_DATA); - bool result = backend_ws_on_message (ctx, self->message_opcode, + bool result = backend_ws_on_message (self, self->message_opcode, self->message_data.str, self->message_data.len); str_reset (&self->message_data); return result; @@ -1939,18 +1873,15 @@ backend_ws_on_connection_timeout (EV_P_ ev_timer *handle, int revents) (void) loop; (void) revents; - struct app_context *ctx = handle->data; - struct ws_context *self = &ctx->ws; - + struct ws_context *self = handle->data; hard_assert (self->waiting_for_event); error_set (&self->e, "connection timeout"); - backend_ws_close_connection (ctx); + backend_ws_close_connection (self); } static bool -backend_ws_connect (struct app_context *ctx, struct error **e) +backend_ws_connect (struct ws_context *self, struct error **e) { - struct ws_context *self = &ctx->ws; bool result = false; char *url_schema = xstrndup (self->endpoint + @@ -1984,10 +1915,10 @@ backend_ws_connect (struct app_context *ctx, struct error **e) self->url.field_data[UF_QUERY].len); } - if (!backend_ws_establish_connection (ctx, url_host, url_port, e)) + if (!backend_ws_establish_connection (self, url_host, url_port, e)) goto fail_1; - if (use_tls && !backend_ws_initialize_tls (ctx, url_host, e)) + if (use_tls && !backend_ws_initialize_tls (self, url_host, e)) goto fail_2; unsigned char key[16]; @@ -2017,7 +1948,7 @@ backend_ws_connect (struct app_context *ctx, struct error **e) str_append_printf (&request, "%s\r\n", self->extra_headers.vector[i]); str_append_printf (&request, "\r\n"); - bool written = backend_ws_write (ctx, request.str, request.len); + bool written = backend_ws_write (self, request.str, request.len); str_free (&request); if (!written) { @@ -2033,11 +1964,11 @@ backend_ws_connect (struct app_context *ctx, struct error **e) ws_parser_init (&self->parser); self->parser.on_frame_header = backend_ws_on_frame_header; self->parser.on_frame = backend_ws_on_frame; - self->parser.user_data = ctx; + self->parser.user_data = self; ev_io_init (&self->read_watcher, backend_ws_on_fd_ready, self->server_fd, EV_READ); - self->read_watcher.data = ctx; + self->read_watcher.data = self; ev_io_start (EV_DEFAULT_ &self->read_watcher); // XXX: we should do everything non-blocking and include establishing @@ -2077,22 +2008,22 @@ fail_1: } static bool -backend_ws_make_call (struct app_context *ctx, +backend_ws_make_call (struct backend *backend, const char *request, bool expect_content, struct str *buf, struct error **e) { - struct ws_context *self = &ctx->ws; + struct ws_context *self = (struct ws_context *) backend; if (self->server_fd == -1) - if (!backend_ws_connect (ctx, e)) + if (!backend_ws_connect (self, e)) return false; while (true) { - if (backend_ws_send_message (ctx, + if (backend_ws_send_message (self, WS_OPCODE_TEXT, request, strlen (request))) break; print_status ("connection failed, reconnecting"); - if (!backend_ws_connect (ctx, e)) + if (!backend_ws_connect (self, e)) return false; } @@ -2118,9 +2049,9 @@ backend_ws_make_call (struct app_context *ctx, } static void -backend_ws_on_quit (struct app_context *ctx) +backend_ws_on_quit (struct backend *backend) { - struct ws_context *self = &ctx->ws; + struct ws_context *self = (struct ws_context *) backend; if (self->waiting_for_event && !self->e) error_set (&self->e, "aborted by user"); @@ -2128,14 +2059,14 @@ backend_ws_on_quit (struct app_context *ctx) } static void -backend_ws_destroy (struct app_context *ctx) +backend_ws_destroy (struct backend *backend) { - struct ws_context *self = &ctx->ws; + struct ws_context *self = (struct ws_context *) backend; // TODO: maybe attempt a graceful shutdown, but for that there should // probably be another backend method that runs an event loop if (self->server_fd != -1) - backend_ws_close_connection (ctx); + backend_ws_close_connection (self); free (self->endpoint); str_vector_free (&self->extra_headers); @@ -2152,17 +2083,59 @@ backend_ws_destroy (struct app_context *ctx) str_free (&self->message_data); } -static struct backend_iface g_backend_ws = +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static struct backend_vtable backend_ws_vtable = { - .init = backend_ws_init, .add_header = backend_ws_add_header, .make_call = backend_ws_make_call, .on_quit = backend_ws_on_quit, .destroy = backend_ws_destroy, }; +static struct backend * +backend_ws_new (struct app_context *ctx, + const char *endpoint, struct http_parser_url *url) +{ + struct ws_context *self = xcalloc (1, sizeof *self); + self->super.vtable = &backend_ws_vtable; + self->ctx = ctx; + + ev_timer_init (&self->timeout_watcher, NULL, 0, 0); + self->server_fd = -1; + ev_io_init (&self->read_watcher, NULL, 0, 0); + http_parser_init (&self->hp, HTTP_RESPONSE); + str_init (&self->field); + str_init (&self->value); + str_map_init (&self->headers); + self->headers.key_xfrm = tolower_ascii_strxfrm; + self->headers.free = free; + ws_parser_init (&self->parser); + str_init (&self->message_data); + str_vector_init (&self->extra_headers); + + self->endpoint = xstrdup (endpoint); + self->url = *url; + + SSL_library_init (); + atexit (EVP_cleanup); + SSL_load_error_strings (); + atexit (ERR_free_strings); + return &self->super; +} + // --- cURL backend ------------------------------------------------------------ +struct curl_context +{ + struct backend super; ///< Parent class + struct app_context *ctx; ///< Application context + + CURL *curl; ///< cURL handle + char curl_error[CURL_ERROR_SIZE]; ///< cURL error info buffer + struct curl_slist *headers; ///< Headers +}; + static size_t write_callback (char *ptr, size_t size, size_t nmemb, void *user_data) { @@ -2206,73 +2179,34 @@ end: } static void -backend_curl_init (struct app_context *ctx, - const char *endpoint, struct http_parser_url *url) +backend_curl_add_header (struct backend *backend, const char *header) { - (void) url; - curl_context_init (&ctx->curl); - - CURL *curl; - if (!(ctx->curl.curl = curl = curl_easy_init ())) - exit_fatal ("cURL initialization failed"); - - ctx->curl.headers = NULL; - ctx->curl.headers = curl_slist_append - (ctx->curl.headers, "Content-Type: application/json"); - - if (curl_easy_setopt (curl, CURLOPT_POST, 1L) - || curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1L) - || curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, ctx->curl.curl_error) - || curl_easy_setopt (curl, CURLOPT_HTTPHEADER, ctx->curl.headers) - || curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, - ctx->trust_all ? 0L : 1L) - || curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, - ctx->trust_all ? 0L : 2L) - || curl_easy_setopt (curl, CURLOPT_URL, endpoint)) - exit_fatal ("cURL setup failed"); - - if (!ctx->trust_all) - { - // TODO: try to resolve filenames relative to configuration directories - const char *ca_file = get_config_string - (ctx->config.root, "connection.tls_ca_file"); - const char *ca_path = get_config_string - (ctx->config.root, "connection.tls_ca_path"); - if ((ca_file && curl_easy_setopt (curl, CURLOPT_CAINFO, ca_file)) - || (ca_path && curl_easy_setopt (curl, CURLOPT_CAPATH, ca_path))) - exit_fatal ("cURL setup failed"); - } -} - -static void -backend_curl_add_header (struct app_context *ctx, const char *header) -{ - ctx->curl.headers = curl_slist_append (ctx->curl.headers, header); - if (curl_easy_setopt (ctx->curl.curl, - CURLOPT_HTTPHEADER, ctx->curl.headers)) + struct curl_context *self = (struct curl_context *) backend; + self->headers = curl_slist_append (self->headers, header); + if (curl_easy_setopt (self->curl, CURLOPT_HTTPHEADER, self->headers)) exit_fatal ("cURL setup failed"); } static bool -backend_curl_make_call (struct app_context *ctx, +backend_curl_make_call (struct backend *backend, const char *request, bool expect_content, struct str *buf, struct error **e) { - CURL *curl = ctx->curl.curl; - if (curl_easy_setopt (curl, CURLOPT_POSTFIELDS, request) - || curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE_LARGE, + struct curl_context *self = (struct curl_context *) backend; + if (curl_easy_setopt (self->curl, CURLOPT_POSTFIELDS, request) + || curl_easy_setopt (self->curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) -1) - || curl_easy_setopt (curl, CURLOPT_WRITEDATA, buf) - || curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_callback)) + || curl_easy_setopt (self->curl, CURLOPT_WRITEDATA, buf) + || curl_easy_setopt (self->curl, CURLOPT_WRITEFUNCTION, write_callback)) FAIL ("cURL setup failed"); CURLcode ret; - if ((ret = curl_easy_perform (curl))) - FAIL ("HTTP request failed: %s", ctx->curl.curl_error); + if ((ret = curl_easy_perform (self->curl))) + FAIL ("HTTP request failed: %s", self->curl_error); long code; char *type; - if (curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &code) - || curl_easy_getinfo (curl, CURLINFO_CONTENT_TYPE, &type)) + if (curl_easy_getinfo (self->curl, CURLINFO_RESPONSE_CODE, &code) + || curl_easy_getinfo (self->curl, CURLINFO_CONTENT_TYPE, &type)) FAIL ("cURL info retrieval failed"); if (code != 200) @@ -2288,27 +2222,69 @@ backend_curl_make_call (struct app_context *ctx, } static void -backend_curl_destroy (struct app_context *ctx) +backend_curl_destroy (struct backend *backend) { - curl_slist_free_all (ctx->curl.headers); - curl_easy_cleanup (ctx->curl.curl); + struct curl_context *self = (struct curl_context *) backend; + curl_slist_free_all (self->headers); + curl_easy_cleanup (self->curl); } -static struct backend_iface g_backend_curl = +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static struct backend_vtable backend_curl_vtable = { - .init = backend_curl_init, .add_header = backend_curl_add_header, .make_call = backend_curl_make_call, .destroy = backend_curl_destroy, }; +static struct backend * +backend_curl_new (struct app_context *ctx, const char *endpoint) +{ + struct curl_context *self = xcalloc (1, sizeof *self); + self->super.vtable = &backend_curl_vtable; + self->ctx = ctx; + + CURL *curl; + if (!(self->curl = curl = curl_easy_init ())) + exit_fatal ("cURL initialization failed"); + + self->headers = NULL; + self->headers = curl_slist_append + (self->headers, "Content-Type: application/json"); + + if (curl_easy_setopt (curl, CURLOPT_POST, 1L) + || curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1L) + || curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, self->curl_error) + || curl_easy_setopt (curl, CURLOPT_HTTPHEADER, self->headers) + || curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, + self->ctx->trust_all ? 0L : 1L) + || curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, + self->ctx->trust_all ? 0L : 2L) + || curl_easy_setopt (curl, CURLOPT_URL, endpoint)) + exit_fatal ("cURL setup failed"); + + if (!self->ctx->trust_all) + { + // TODO: try to resolve filenames relative to configuration directories + const char *ca_file = get_config_string + (self->ctx->config.root, "connection.tls_ca_file"); + const char *ca_path = get_config_string + (self->ctx->config.root, "connection.tls_ca_path"); + if ((ca_file && curl_easy_setopt (curl, CURLOPT_CAINFO, ca_file)) + || (ca_path && curl_easy_setopt (curl, CURLOPT_CAPATH, ca_path))) + exit_fatal ("cURL setup failed"); + } + return &self->super; +} + // --- Main program ------------------------------------------------------------ static void quit (struct app_context *ctx) { - if (ctx->backend->on_quit) - ctx->backend->on_quit (ctx); + if (ctx->backend->vtable->on_quit) + ctx->backend->vtable->on_quit (ctx->backend); ev_break (EV_DEFAULT_ EVBREAK_ALL); ctx->input->vtable->hide (ctx->input); @@ -2462,7 +2438,8 @@ make_json_rpc_call (struct app_context *ctx, str_init (&buf); struct error *e = NULL; - if (!ctx->backend->make_call (ctx, req_utf8, id != NULL, &buf, &e)) + if (!ctx->backend->vtable->make_call + (ctx->backend, req_utf8, id != NULL, &buf, &e)) { print_error ("%s", e->message); error_free (e); @@ -2734,27 +2711,27 @@ main (int argc, char *argv[]) url.field_data[UF_SCHEMA].off, url.field_data[UF_SCHEMA].len); + // TODO: try to avoid the need to pass application context to backends if (!strcasecmp_ascii (url_schema, "http") || !strcasecmp_ascii (url_schema, "https")) - g_ctx.backend = &g_backend_curl; + g_ctx.backend = backend_curl_new (&g_ctx, endpoint); else if (!strcasecmp_ascii (url_schema, "ws") || !strcasecmp_ascii (url_schema, "wss")) { print_warning ("WebSocket support is experimental" " and most likely completely broken"); - g_ctx.backend = &g_backend_ws; + g_ctx.backend = backend_ws_new (&g_ctx, endpoint, &url); } else exit_fatal ("unsupported protocol"); - free (url_schema); - g_ctx.backend->init (&g_ctx, endpoint, &url); if (origin) { origin = xstrdup_printf ("Origin: %s", origin); - g_ctx.backend->add_header (&g_ctx, origin); + g_ctx.backend->vtable->add_header (g_ctx.backend, origin); } + free (origin); // We only need to convert to and from the terminal encoding setlocale (LC_CTYPE, ""); @@ -2849,13 +2826,13 @@ main (int argc, char *argv[]) free (dir); free (history_path); + g_ctx.backend->vtable->destroy (g_ctx.backend); + g_ctx.input->vtable->destroy (g_ctx.input); + iconv_close (g_ctx.term_from_utf8); iconv_close (g_ctx.term_to_utf8); - g_ctx.backend->destroy (&g_ctx); - free (origin); config_free (&g_ctx.config); free_terminal (); - g_ctx.input->vtable->destroy (g_ctx.input); ev_loop_destroy (loop); return EXIT_SUCCESS; }