diff options
Diffstat (limited to 'src/plugins/http/http1.c')
-rw-r--r-- | src/plugins/http/http1.c | 1750 |
1 files changed, 1750 insertions, 0 deletions
diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c new file mode 100644 index 00000000000..44dd099ccee --- /dev/null +++ b/src/plugins/http/http1.c @@ -0,0 +1,1750 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#include <vnet/session/application.h> + +#include <http/http.h> +#include <http/http_header_names.h> +#include <http/http_private.h> +#include <http/http_status_codes.h> +#include <http/http_timer.h> + +const char *http1_upgrade_proto_str[] = { "", +#define _(sym, str) str, + foreach_http_upgrade_proto +#undef _ +}; + +/** + * http error boilerplate + */ +static const char *error_template = "HTTP/1.1 %s\r\n" + "Date: %U GMT\r\n" + "Connection: close\r\n" + "Content-Length: 0\r\n\r\n"; + +/** + * http response boilerplate + */ +static const char *response_template = "HTTP/1.1 %s\r\n" + "Date: %U GMT\r\n" + "Server: %v\r\n"; + +static const char *content_len_template = "Content-Length: %llu\r\n"; + +static const char *connection_upgrade_template = "Connection: upgrade\r\n" + "Upgrade: %s\r\n"; + +/** + * http request boilerplate + */ +static const char *get_request_template = "GET %s HTTP/1.1\r\n" + "Host: %v\r\n" + "User-Agent: %v\r\n"; + +static const char *post_request_template = "POST %s HTTP/1.1\r\n" + "Host: %v\r\n" + "User-Agent: %v\r\n" + "Content-Length: %llu\r\n"; + +static void +http1_send_error (http_conn_t *hc, http_status_code_t ec, + transport_send_params_t *sp) +{ + u8 *data; + + if (ec >= HTTP_N_STATUS) + ec = HTTP_STATUS_INTERNAL_ERROR; + + data = format (0, error_template, http_status_code_str[ec], + format_http_time_now, hc); + HTTP_DBG (3, "%v", data); + http_io_ts_write (hc, data, vec_len (data), sp); + vec_free (data); + http_io_ts_after_write (hc, sp, 0, 1); +} + +static int +http1_read_message (http_conn_t *hc, u8 *rx_buf) +{ + u32 max_deq; + + max_deq = http_io_ts_max_read (hc); + if (PREDICT_FALSE (max_deq == 0)) + return -1; + + vec_validate (rx_buf, max_deq - 1); + http_io_ts_read (hc, rx_buf, max_deq, 1); + + return 0; +} + +static void +http1_identify_optional_query (http_req_t *req, u8 *rx_buf) +{ + int i; + for (i = req->target_path_offset; + i < (req->target_path_offset + req->target_path_len); i++) + { + if (rx_buf[i] == '?') + { + req->target_query_offset = i + 1; + req->target_query_len = req->target_path_offset + + req->target_path_len - + req->target_query_offset; + req->target_path_len = + req->target_path_len - req->target_query_len - 1; + break; + } + } +} + +static int +http1_parse_target (http_req_t *req, u8 *rx_buf) +{ + int i; + u8 *p, *end; + + /* asterisk-form = "*" */ + if ((rx_buf[req->target_path_offset] == '*') && (req->target_path_len == 1)) + { + req->target_form = HTTP_TARGET_ASTERISK_FORM; + /* we do not support OPTIONS request */ + return -1; + } + + /* origin-form = 1*( "/" segment ) [ "?" query ] */ + if (rx_buf[req->target_path_offset] == '/') + { + /* drop leading slash */ + req->target_path_len--; + req->target_path_offset++; + req->target_form = HTTP_TARGET_ORIGIN_FORM; + http1_identify_optional_query (req, rx_buf); + /* can't be CONNECT method */ + return req->method == HTTP_REQ_CONNECT ? -1 : 0; + } + + /* absolute-form = + * scheme "://" host [ ":" port ] *( "/" segment ) [ "?" query ] */ + if (req->target_path_len > 8 && + !memcmp (rx_buf + req->target_path_offset, "http", 4)) + { + req->scheme = HTTP_URL_SCHEME_HTTP; + p = rx_buf + req->target_path_offset + 4; + if (*p == 's') + { + p++; + req->scheme = HTTP_URL_SCHEME_HTTPS; + } + if (*p++ == ':') + { + expect_char ('/'); + expect_char ('/'); + req->target_form = HTTP_TARGET_ABSOLUTE_FORM; + req->target_authority_offset = p - rx_buf; + req->target_authority_len = 0; + end = rx_buf + req->target_path_offset + req->target_path_len; + while (p < end) + { + if (*p == '/') + { + p++; /* drop leading slash */ + req->target_path_offset = p - rx_buf; + req->target_path_len = end - p; + break; + } + req->target_authority_len++; + p++; + } + if (!req->target_path_len) + { + clib_warning ("zero length host"); + return -1; + } + http1_identify_optional_query (req, rx_buf); + /* can't be CONNECT method */ + return req->method == HTTP_REQ_CONNECT ? -1 : 0; + } + } + + /* authority-form = host ":" port */ + for (i = req->target_path_offset; + i < (req->target_path_offset + req->target_path_len); i++) + { + if ((rx_buf[i] == ':') && (isdigit (rx_buf[i + 1]))) + { + req->target_authority_len = req->target_path_len; + req->target_path_len = 0; + req->target_authority_offset = req->target_path_offset; + req->target_path_offset = 0; + req->target_form = HTTP_TARGET_AUTHORITY_FORM; + /* "authority-form" is only used for CONNECT requests */ + return req->method == HTTP_REQ_CONNECT ? 0 : -1; + } + } + + return -1; +} + +static int +http1_parse_request_line (http_req_t *req, u8 *rx_buf, http_status_code_t *ec) +{ + int i, target_len; + u32 next_line_offset, method_offset; + + /* request-line = method SP request-target SP HTTP-version CRLF */ + i = http_v_find_index (rx_buf, 8, 0, "\r\n"); + if (i < 0) + { + clib_warning ("request line incomplete"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + HTTP_DBG (2, "request line length: %d", i); + req->control_data_len = i + 2; + next_line_offset = req->control_data_len; + + /* there should be at least one more CRLF */ + if (vec_len (rx_buf) < (next_line_offset + 2)) + { + clib_warning ("malformed message, too short"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + /* + * RFC9112 2.2: + * In the interest of robustness, a server that is expecting to receive and + * parse a request-line SHOULD ignore at least one empty line (CRLF) + * received prior to the request-line. + */ + method_offset = rx_buf[0] == '\r' && rx_buf[1] == '\n' ? 2 : 0; + /* parse method */ + if (!memcmp (rx_buf + method_offset, "GET ", 4)) + { + HTTP_DBG (0, "GET method"); + req->method = HTTP_REQ_GET; + req->target_path_offset = method_offset + 4; + } + else if (!memcmp (rx_buf + method_offset, "POST ", 5)) + { + HTTP_DBG (0, "POST method"); + req->method = HTTP_REQ_POST; + req->target_path_offset = method_offset + 5; + } + else if (!memcmp (rx_buf + method_offset, "CONNECT ", 8)) + { + HTTP_DBG (0, "CONNECT method"); + req->method = HTTP_REQ_CONNECT; + req->upgrade_proto = HTTP_UPGRADE_PROTO_NA; + req->target_path_offset = method_offset + 8; + req->is_tunnel = 1; + } + else + { + if (rx_buf[method_offset] - 'A' <= 'Z' - 'A') + { + *ec = HTTP_STATUS_NOT_IMPLEMENTED; + return -1; + } + else + { + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + } + + /* find version */ + i = http_v_find_index (rx_buf, next_line_offset - 11, 11, " HTTP/"); + if (i < 0) + { + clib_warning ("HTTP version not present"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + /* verify major version */ + if (isdigit (rx_buf[i + 6])) + { + if (rx_buf[i + 6] != '1') + { + clib_warning ("HTTP major version '%c' not supported", + rx_buf[i + 6]); + *ec = HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED; + return -1; + } + } + else + { + clib_warning ("HTTP major version '%c' is not digit", rx_buf[i + 6]); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + /* parse request-target */ + HTTP_DBG (2, "http at %d", i); + target_len = i - req->target_path_offset; + HTTP_DBG (2, "target_len %d", target_len); + if (target_len < 1) + { + clib_warning ("request-target not present"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + req->target_path_len = target_len; + req->target_query_offset = 0; + req->target_query_len = 0; + req->target_authority_len = 0; + req->target_authority_offset = 0; + if (http1_parse_target (req, rx_buf)) + { + clib_warning ("invalid target"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + HTTP_DBG (2, "request-target path length: %u", req->target_path_len); + HTTP_DBG (2, "request-target path offset: %u", req->target_path_offset); + HTTP_DBG (2, "request-target query length: %u", req->target_query_len); + HTTP_DBG (2, "request-target query offset: %u", req->target_query_offset); + + /* set buffer offset to nex line start */ + req->rx_buf_offset = next_line_offset; + + return 0; +} + +static int +http1_parse_status_line (http_req_t *req, u8 *rx_buf) +{ + int i; + u32 next_line_offset; + u8 *p, *end; + u16 status_code = 0; + + i = http_v_find_index (rx_buf, 0, 0, "\r\n"); + /* status-line = HTTP-version SP status-code SP [ reason-phrase ] CRLF */ + if (i < 0) + { + clib_warning ("status line incomplete"); + return -1; + } + HTTP_DBG (2, "status line length: %d", i); + if (i < 12) + { + clib_warning ("status line too short (%d)", i); + return -1; + } + req->control_data_len = i + 2; + next_line_offset = req->control_data_len; + p = rx_buf; + end = rx_buf + i; + + /* there should be at least one more CRLF */ + if (vec_len (rx_buf) < (next_line_offset + 2)) + { + clib_warning ("malformed message, too short"); + return -1; + } + + /* parse version */ + expect_char ('H'); + expect_char ('T'); + expect_char ('T'); + expect_char ('P'); + expect_char ('/'); + expect_char ('1'); + expect_char ('.'); + if (!isdigit (*p++)) + { + clib_warning ("invalid HTTP minor version"); + return -1; + } + + /* skip space(s) */ + if (*p != ' ') + { + clib_warning ("no space after HTTP version"); + return -1; + } + do + { + p++; + if (p == end) + { + clib_warning ("no status code"); + return -1; + } + } + while (*p == ' '); + + /* parse status code */ + if ((end - p) < 3) + { + clib_warning ("not enough characters for status code"); + return -1; + } + parse_int (status_code, 100); + parse_int (status_code, 10); + parse_int (status_code, 1); + if (status_code < 100 || status_code > 599) + { + clib_warning ("invalid status code %d", status_code); + return -1; + } + req->status_code = http_sc_by_u16 (status_code); + HTTP_DBG (0, "status code: %d", status_code); + + /* set buffer offset to nex line start */ + req->rx_buf_offset = next_line_offset; + + return 0; +} + +always_inline int +http1_parse_field_name (u8 **pos, u8 *end, u8 **field_name_start, + u32 *field_name_len) +{ + u32 name_len = 0; + u8 *p; + + static uword tchar[4] = { + /* !#$%'*+-.0123456789 */ + 0x03ff6cba00000000, + /* ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~ */ + 0x57ffffffc7fffffe, + 0x0000000000000000, + 0x0000000000000000, + }; + + p = *pos; + + *field_name_start = p; + while (p != end) + { + if (clib_bitmap_get_no_check (tchar, *p)) + { + name_len++; + p++; + } + else if (*p == ':') + { + if (name_len == 0) + { + clib_warning ("empty field name"); + return -1; + } + *field_name_len = name_len; + p++; + *pos = p; + return 0; + } + else + { + clib_warning ("invalid character %d", *p); + return -1; + } + } + clib_warning ("field name end not found"); + return -1; +} + +always_inline int +http1_parse_field_value (u8 **pos, u8 *end, u8 **field_value_start, + u32 *field_value_len) +{ + u32 value_len = 0; + u8 *p; + + p = *pos; + + /* skip leading whitespace */ + while (1) + { + if (p == end) + { + clib_warning ("field value not found"); + return -1; + } + else if (*p != ' ' && *p != '\t') + { + break; + } + p++; + } + + *field_value_start = p; + while (p != end) + { + if (*p == '\r') + { + if ((end - p) < 1) + { + clib_warning ("incorrect field line end"); + return -1; + } + p++; + if (*p == '\n') + { + if (value_len == 0) + { + clib_warning ("empty field value"); + return -1; + } + p++; + *pos = p; + /* skip trailing whitespace */ + p = *field_value_start + value_len - 1; + while (*p == ' ' || *p == '\t') + { + p--; + value_len--; + } + *field_value_len = value_len; + return 0; + } + clib_warning ("CR without LF"); + return -1; + } + if (*p < ' ' && *p != '\t') + { + clib_warning ("invalid character %d", *p); + return -1; + } + p++; + value_len++; + } + + clib_warning ("field value end not found"); + return -1; +} + +static int +http1_identify_headers (http_req_t *req, u8 *rx_buf, http_status_code_t *ec) +{ + int rv; + u8 *p, *end, *name_start, *value_start; + u32 name_len, value_len; + http_field_line_t *field_line; + uword header_index; + + vec_reset_length (req->headers); + req->content_len_header_index = ~0; + req->connection_header_index = ~0; + req->upgrade_header_index = ~0; + req->host_header_index = ~0; + req->headers_offset = req->rx_buf_offset; + + /* check if we have any header */ + if ((rx_buf[req->rx_buf_offset] == '\r') && + (rx_buf[req->rx_buf_offset + 1] == '\n')) + { + /* just another CRLF -> no headers */ + HTTP_DBG (2, "no headers"); + req->headers_len = 0; + req->control_data_len += 2; + return 0; + } + + end = vec_end (rx_buf); + p = rx_buf + req->rx_buf_offset; + + while (1) + { + rv = http1_parse_field_name (&p, end, &name_start, &name_len); + if (rv != 0) + { + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + rv = http1_parse_field_value (&p, end, &value_start, &value_len); + if (rv != 0 || (end - p) < 2) + { + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + vec_add2 (req->headers, field_line, 1); + field_line->name_offset = (name_start - rx_buf) - req->headers_offset; + field_line->name_len = name_len; + field_line->value_offset = (value_start - rx_buf) - req->headers_offset; + field_line->value_len = value_len; + header_index = field_line - req->headers; + + /* find headers that will be used later in preprocessing */ + /* names are case-insensitive (RFC9110 section 5.1) */ + if (req->content_len_header_index == ~0 && + http_token_is_case ( + (const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_CONTENT_LENGTH))) + req->content_len_header_index = header_index; + else if (req->connection_header_index == ~0 && + http_token_is_case ( + (const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_CONNECTION))) + req->connection_header_index = header_index; + else if (req->upgrade_header_index == ~0 && + http_token_is_case ( + (const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_UPGRADE))) + req->upgrade_header_index = header_index; + else if (req->host_header_index == ~0 && + http_token_is_case ((const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_HOST))) + req->host_header_index = header_index; + + /* are we done? */ + if (*p == '\r' && *(p + 1) == '\n') + break; + } + + req->headers_len = p - (rx_buf + req->headers_offset); + req->control_data_len += (req->headers_len + 2); + HTTP_DBG (2, "headers length: %u", req->headers_len); + HTTP_DBG (2, "headers offset: %u", req->headers_offset); + + return 0; +} + +static int +http1_identify_message_body (http_req_t *req, u8 *rx_buf, + http_status_code_t *ec) +{ + int i; + u8 *p; + u64 body_len = 0, digit; + http_field_line_t *field_line; + + req->body_len = 0; + + if (req->headers_len == 0) + { + HTTP_DBG (2, "no header, no message-body"); + return 0; + } + if (req->is_tunnel) + { + HTTP_DBG (2, "tunnel, no message-body"); + return 0; + } + + /* TODO check for chunked transfer coding */ + + if (req->content_len_header_index == ~0) + { + HTTP_DBG (2, "Content-Length header not present, no message-body"); + return 0; + } + field_line = vec_elt_at_index (req->headers, req->content_len_header_index); + + p = rx_buf + req->headers_offset + field_line->value_offset; + for (i = 0; i < field_line->value_len; i++) + { + /* check for digit */ + if (!isdigit (*p)) + { + clib_warning ("expected digit"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + digit = *p - '0'; + u64 new_body_len = body_len * 10 + digit; + /* check for overflow */ + if (new_body_len < body_len) + { + clib_warning ("too big number, overflow"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + body_len = new_body_len; + p++; + } + + req->body_len = body_len; + + req->body_offset = req->headers_offset + req->headers_len + 2; + HTTP_DBG (2, "body length: %llu", req->body_len); + HTTP_DBG (2, "body offset: %u", req->body_offset); + + return 0; +} + +static void +http1_check_connection_upgrade (http_req_t *req, u8 *rx_buf) +{ + http_field_line_t *connection, *upgrade; + u8 skip; + + skip = (req->method != HTTP_REQ_GET) + (req->connection_header_index == ~0) + + (req->upgrade_header_index == ~0); + if (skip) + return; + + connection = vec_elt_at_index (req->headers, req->connection_header_index); + /* connection options are case-insensitive (RFC9110 7.6.1) */ + if (http_token_is_case ( + http_field_line_value_token (connection, req, rx_buf), + http_token_lit ("upgrade"))) + { + upgrade = vec_elt_at_index (req->headers, req->upgrade_header_index); + + /* check upgrade protocol, we want to ignore something like upgrade to + * newer HTTP version, only tunnels are supported */ + if (0) + ; +#define _(sym, str) \ + else if (http_token_is_case ( \ + http_field_line_value_token (upgrade, req, rx_buf), \ + http_token_lit (str))) req->upgrade_proto = \ + HTTP_UPGRADE_PROTO_##sym; + foreach_http_upgrade_proto +#undef _ + else return; + + req->is_tunnel = 1; + req->method = HTTP_REQ_CONNECT; + } +} + +static void +http1_target_fixup (http_conn_t *hc, http_req_t *req) +{ + http_field_line_t *host; + + if (req->target_form == HTTP_TARGET_ABSOLUTE_FORM) + return; + + /* scheme fixup */ + req->scheme = http_get_transport_proto (hc) == TRANSPORT_PROTO_TLS ? + HTTP_URL_SCHEME_HTTPS : + HTTP_URL_SCHEME_HTTP; + + if (req->target_form == HTTP_TARGET_AUTHORITY_FORM || + req->connection_header_index == ~0) + return; + + /* authority fixup */ + host = vec_elt_at_index (req->headers, req->connection_header_index); + req->target_authority_offset = host->value_offset; + req->target_authority_len = host->value_len; +} + +static void +http1_write_app_headers (http_conn_t *hc, http_msg_t *msg, u8 **tx_buf) +{ + u8 *app_headers, *p, *end; + u32 *tmp; + + /* read app header list */ + app_headers = http_get_app_header_list (hc, msg); + + /* serialize app headers to tx_buf */ + end = app_headers + msg->data.headers_len; + while (app_headers < end) + { + /* custom header name? */ + tmp = (u32 *) app_headers; + if (PREDICT_FALSE (*tmp & HTTP_CUSTOM_HEADER_NAME_BIT)) + { + http_custom_token_t *name, *value; + name = (http_custom_token_t *) app_headers; + u32 name_len = name->len & ~HTTP_CUSTOM_HEADER_NAME_BIT; + app_headers += sizeof (http_custom_token_t) + name_len; + value = (http_custom_token_t *) app_headers; + app_headers += sizeof (http_custom_token_t) + value->len; + vec_add2 (*tx_buf, p, name_len + value->len + 4); + clib_memcpy (p, name->token, name_len); + p += name_len; + *p++ = ':'; + *p++ = ' '; + clib_memcpy (p, value->token, value->len); + p += value->len; + *p++ = '\r'; + *p++ = '\n'; + } + else + { + http_app_header_t *header; + header = (http_app_header_t *) app_headers; + app_headers += sizeof (http_app_header_t) + header->value.len; + http_token_t name = { http_header_name_token (header->name) }; + vec_add2 (*tx_buf, p, name.len + header->value.len + 4); + clib_memcpy (p, name.base, name.len); + p += name.len; + *p++ = ':'; + *p++ = ' '; + clib_memcpy (p, header->value.token, header->value.len); + p += header->value.len; + *p++ = '\r'; + *p++ = '\n'; + } + } +} + +/*************************************/ +/* request state machine handlers RX */ +/*************************************/ + +static http_sm_result_t +http1_req_state_wait_transport_reply (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + int rv; + http_msg_t msg = {}; + u32 len, max_enq, body_sent; + http_status_code_t ec; + u8 *rx_buf; + + rx_buf = http_get_rx_buf (hc); + rv = http1_read_message (hc, rx_buf); + + /* Nothing yet, wait for data or timer expire */ + if (rv) + { + HTTP_DBG (1, "no data to deq"); + return HTTP_SM_STOP; + } + + HTTP_DBG (3, "%v", rx_buf); + + if (vec_len (rx_buf) < 8) + { + clib_warning ("response buffer too short"); + goto error; + } + + rv = http1_parse_status_line (req, rx_buf); + if (rv) + goto error; + + rv = http1_identify_headers (req, rx_buf, &ec); + if (rv) + goto error; + + rv = http1_identify_message_body (req, rx_buf, &ec); + if (rv) + goto error; + + /* send at least "control data" which is necessary minimum, + * if there is some space send also portion of body */ + max_enq = http_io_as_max_write (req); + max_enq -= sizeof (msg); + if (max_enq < req->control_data_len) + { + clib_warning ("not enough room for control data in app's rx fifo"); + goto error; + } + len = clib_min (max_enq, vec_len (rx_buf)); + + msg.type = HTTP_MSG_REPLY; + msg.code = req->status_code; + msg.data.headers_offset = req->headers_offset; + msg.data.headers_len = req->headers_len; + msg.data.body_offset = req->body_offset; + msg.data.body_len = req->body_len; + msg.data.type = HTTP_MSG_DATA_INLINE; + msg.data.len = len; + msg.data.headers_ctx = pointer_to_uword (req->headers); + + svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { rx_buf, len } }; + + http_io_as_write_segs (req, segs, 2); + + body_sent = len - req->control_data_len; + req->to_recv = req->body_len - body_sent; + if (req->to_recv == 0) + { + /* all sent, we are done */ + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD); + } + else + { + /* stream rest of the response body */ + http_req_state_change (req, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA); + } + + http_io_ts_drain (hc, len); + http_io_ts_after_read (hc, 1); + http_app_worker_rx_notify (req); + return HTTP_SM_STOP; + +error: + http_io_ts_drain_all (hc); + http_io_ts_after_read (hc, 1); + session_transport_closing_notify (&hc->connection); + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); + return HTTP_SM_ERROR; +} + +static http_sm_result_t +http1_req_state_wait_transport_method (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + http_status_code_t ec; + http_msg_t msg; + int rv; + u32 len, max_enq, body_sent; + u64 max_deq; + u8 *rx_buf; + + rx_buf = http_get_rx_buf (hc); + rv = http1_read_message (hc, rx_buf); + + /* Nothing yet, wait for data or timer expire */ + if (rv) + return HTTP_SM_STOP; + + HTTP_DBG (3, "%v", rx_buf); + + if (vec_len (rx_buf) < 8) + { + ec = HTTP_STATUS_BAD_REQUEST; + goto error; + } + + rv = http1_parse_request_line (req, rx_buf, &ec); + if (rv) + goto error; + + rv = http1_identify_headers (req, rx_buf, &ec); + if (rv) + goto error; + + http1_target_fixup (hc, req); + http1_check_connection_upgrade (req, rx_buf); + + rv = http1_identify_message_body (req, rx_buf, &ec); + if (rv) + goto error; + + /* send at least "control data" which is necessary minimum, + * if there is some space send also portion of body */ + max_enq = http_io_as_max_write (req); + if (max_enq < req->control_data_len) + { + clib_warning ("not enough room for control data in app's rx fifo"); + ec = HTTP_STATUS_INTERNAL_ERROR; + goto error; + } + /* do not dequeue more than one HTTP request, we do not support pipelining */ + max_deq = clib_min (req->control_data_len + req->body_len, vec_len (rx_buf)); + len = clib_min (max_enq, max_deq); + + msg.type = HTTP_MSG_REQUEST; + msg.method_type = req->method; + msg.data.type = HTTP_MSG_DATA_INLINE; + msg.data.len = len; + msg.data.scheme = req->scheme; + msg.data.target_authority_offset = req->target_authority_offset; + msg.data.target_authority_len = req->target_authority_len; + msg.data.target_path_offset = req->target_path_offset; + msg.data.target_path_len = req->target_path_len; + msg.data.target_query_offset = req->target_query_offset; + msg.data.target_query_len = req->target_query_len; + msg.data.headers_offset = req->headers_offset; + msg.data.headers_len = req->headers_len; + msg.data.body_offset = req->body_offset; + msg.data.body_len = req->body_len; + msg.data.headers_ctx = pointer_to_uword (req->headers); + msg.data.upgrade_proto = req->upgrade_proto; + + svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { rx_buf, len } }; + + http_io_as_write_segs (req, segs, 2); + + body_sent = len - req->control_data_len; + req->to_recv = req->body_len - body_sent; + if (req->to_recv == 0) + { + /* drop everything, we do not support pipelining */ + http_io_ts_drain_all (hc); + /* all sent, we are done */ + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_REPLY); + } + else + { + http_io_ts_drain (hc, len); + /* stream rest of the response body */ + http_req_state_change (req, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA); + } + + http_app_worker_rx_notify (req); + http_io_ts_after_read (hc, 1); + + return HTTP_SM_STOP; + +error: + http_io_ts_drain_all (hc); + http_io_ts_after_read (hc, 1); + http1_send_error (hc, ec, 0); + session_transport_closing_notify (&hc->connection); + http_disconnect_transport (hc); + + return HTTP_SM_ERROR; +} + +static http_sm_result_t +http1_req_state_transport_io_more_data (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 max_len, max_deq, max_enq, n_segs = 2; + svm_fifo_seg_t segs[n_segs]; + int n_written; + + max_deq = http_io_ts_max_read (hc); + if (max_deq == 0) + { + HTTP_DBG (1, "no data to deq"); + return HTTP_SM_STOP; + } + + max_enq = http_io_as_max_write (req); + if (max_enq == 0) + { + HTTP_DBG (1, "app's rx fifo full"); + http_io_as_want_deq_ntf (req); + return HTTP_SM_STOP; + } + + max_len = clib_min (max_enq, max_deq); + http_io_ts_read_segs (hc, segs, &n_segs, max_len); + + n_written = http_io_as_write_segs (req, segs, n_segs); + + if (n_written > req->to_recv) + { + clib_warning ("http protocol error: received more data than expected"); + session_transport_closing_notify (&hc->connection); + http_disconnect_transport (hc); + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD); + return HTTP_SM_ERROR; + } + req->to_recv -= n_written; + http_io_ts_drain (hc, n_written); + HTTP_DBG (1, "drained %d from ts; remains %lu", n_written, req->to_recv); + + /* Finished transaction: + * server back to HTTP_REQ_STATE_WAIT_APP_REPLY + * client to HTTP_REQ_STATE_WAIT_APP_METHOD */ + if (req->to_recv == 0) + http_req_state_change (req, hc->is_server ? + HTTP_REQ_STATE_WAIT_APP_REPLY : + HTTP_REQ_STATE_WAIT_APP_METHOD); + + http_app_worker_rx_notify (req); + + http_io_ts_after_read (hc, 0); + + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_tunnel_rx (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 max_deq, max_enq, max_read, n_segs = 2; + svm_fifo_seg_t segs[n_segs]; + int n_written = 0; + + HTTP_DBG (1, "tunnel received data from client"); + + max_deq = http_io_ts_max_read (hc); + if (PREDICT_FALSE (max_deq == 0)) + { + HTTP_DBG (1, "max_deq == 0"); + return HTTP_SM_STOP; + } + max_enq = http_io_as_max_write (req); + if (max_enq == 0) + { + HTTP_DBG (1, "app's rx fifo full"); + http_io_as_want_deq_ntf (req); + return HTTP_SM_STOP; + } + max_read = clib_min (max_enq, max_deq); + http_io_ts_read_segs (hc, segs, &n_segs, max_read); + n_written = http_io_as_write_segs (req, segs, n_segs); + http_io_ts_drain (hc, n_written); + HTTP_DBG (1, "transfered %u bytes", n_written); + http_app_worker_rx_notify (req); + http_io_ts_after_read (hc, 0); + + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_udp_tunnel_rx (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 to_deq, capsule_size, dgram_size, n_read, n_written = 0; + int rv; + u8 payload_offset = 0; + u64 payload_len = 0; + session_dgram_hdr_t hdr; + u8 *buf = 0; + + HTTP_DBG (1, "udp tunnel received data from client"); + + buf = http_get_rx_buf (hc); + to_deq = http_io_ts_max_read (hc); + + while (to_deq > 0) + { + /* some bytes remaining to skip? */ + if (PREDICT_FALSE (req->to_skip)) + { + if (req->to_skip >= to_deq) + { + http_io_ts_drain (hc, to_deq); + req->to_skip -= to_deq; + goto done; + } + else + { + http_io_ts_drain (hc, req->to_skip); + req->to_skip = 0; + } + } + n_read = http_io_ts_read (hc, buf, HTTP_CAPSULE_HEADER_MAX_SIZE, 1); + rv = http_decap_udp_payload_datagram (buf, n_read, &payload_offset, + &payload_len); + HTTP_DBG (1, "rv=%d, payload_offset=%u, payload_len=%llu", rv, + payload_offset, payload_len); + if (PREDICT_FALSE (rv != 0)) + { + if (rv < 0) + { + /* capsule datagram is invalid (session need to be aborted) */ + http_io_ts_drain_all (hc); + session_transport_closing_notify (&hc->connection); + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); + return HTTP_SM_STOP; + } + else + { + /* unknown capsule should be skipped */ + if (payload_len <= to_deq) + { + http_io_ts_drain (hc, payload_len); + to_deq -= payload_len; + continue; + } + else + { + http_io_ts_drain (hc, to_deq); + req->to_skip = payload_len - to_deq; + goto done; + } + } + } + capsule_size = payload_offset + payload_len; + /* check if we have the full capsule */ + if (PREDICT_FALSE (to_deq < capsule_size)) + { + HTTP_DBG (1, "capsule not complete"); + goto done; + } + + dgram_size = sizeof (hdr) + payload_len; + if (http_io_as_max_write (req) < dgram_size) + { + HTTP_DBG (1, "app's rx fifo full"); + http_io_as_want_deq_ntf (req); + goto done; + } + + http_io_ts_drain (hc, payload_offset); + + /* read capsule payload */ + http_io_ts_read (hc, buf, payload_len, 0); + + hdr.data_length = payload_len; + hdr.data_offset = 0; + + /* send datagram header and payload */ + svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) }, + { buf, payload_len } }; + http_io_as_write_segs (req, segs, 2); + + n_written += dgram_size; + to_deq -= capsule_size; + } + +done: + HTTP_DBG (1, "written %lu bytes", n_written); + + if (n_written) + http_app_worker_rx_notify (req); + + http_io_ts_after_read (hc, 0); + + return HTTP_SM_STOP; +} + +/*************************************/ +/* request state machine handlers TX */ +/*************************************/ + +static http_sm_result_t +http1_req_state_wait_app_reply (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u8 *response; + u32 max_enq; + http_status_code_t sc; + http_msg_t msg; + http_sm_result_t sm_result = HTTP_SM_ERROR; + http_req_state_t next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD; + + http_get_app_msg (req, &msg); + + if (msg.data.type > HTTP_MSG_DATA_PTR) + { + clib_warning ("no data"); + sc = HTTP_STATUS_INTERNAL_ERROR; + goto error; + } + + if (msg.type != HTTP_MSG_REPLY) + { + clib_warning ("unexpected message type %d", msg.type); + sc = HTTP_STATUS_INTERNAL_ERROR; + goto error; + } + + if (msg.code >= HTTP_N_STATUS) + { + clib_warning ("unsupported status code: %d", msg.code); + return HTTP_SM_ERROR; + } + + response = http_get_tx_buf (hc); + /* + * Add "protocol layer" headers: + * - current time + * - server name + * - data length + */ + response = + format (response, response_template, http_status_code_str[msg.code], + /* Date */ + format_http_time_now, hc, + /* Server */ + hc->app_name); + + /* RFC9110 8.6: A server MUST NOT send Content-Length header field in a + * 2xx (Successful) response to CONNECT or with a status code of 101 + * (Switching Protocols). */ + if (req->is_tunnel && (http_status_code_str[msg.code][0] == '2' || + msg.code == HTTP_STATUS_SWITCHING_PROTOCOLS)) + { + ASSERT (msg.data.body_len == 0); + next_state = HTTP_REQ_STATE_TUNNEL; + if (req->upgrade_proto > HTTP_UPGRADE_PROTO_NA) + { + response = format (response, connection_upgrade_template, + http1_upgrade_proto_str[req->upgrade_proto]); + if (req->upgrade_proto == HTTP_UPGRADE_PROTO_CONNECT_UDP && + hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM) + next_state = HTTP_REQ_STATE_UDP_TUNNEL; + } + /* cleanup some stuff we don't need anymore in tunnel mode */ + vec_free (req->headers); + http_buffer_free (&req->tx_buf); + req->to_skip = 0; + } + else + response = format (response, content_len_template, msg.data.body_len); + + /* Add headers from app (if any) */ + if (msg.data.headers_len) + { + HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len); + http1_write_app_headers (hc, &msg, &response); + } + /* Add empty line after headers */ + response = format (response, "\r\n"); + HTTP_DBG (3, "%v", response); + + max_enq = http_io_ts_max_write (hc, sp); + if (max_enq < vec_len (response)) + { + clib_warning ("sending status-line and headers failed!"); + sc = HTTP_STATUS_INTERNAL_ERROR; + goto error; + } + http_io_ts_write (hc, response, vec_len (response), sp); + + if (msg.data.body_len) + { + /* Start sending the actual data */ + http_req_tx_buffer_init (req, &msg); + next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA; + sm_result = HTTP_SM_CONTINUE; + } + else + { + /* No response body, we are done */ + sm_result = HTTP_SM_STOP; + } + + http_req_state_change (req, next_state); + + http_io_ts_after_write (hc, sp, 0, 1); + return sm_result; + +error: + http1_send_error (hc, sc, sp); + session_transport_closing_notify (&hc->connection); + http_disconnect_transport (hc); + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + http_msg_t msg; + u8 *request = 0, *target; + u32 max_enq; + http_sm_result_t sm_result = HTTP_SM_ERROR; + http_req_state_t next_state; + + http_get_app_msg (req, &msg); + + if (msg.data.type > HTTP_MSG_DATA_PTR) + { + clib_warning ("no data"); + goto error; + } + + if (msg.type != HTTP_MSG_REQUEST) + { + clib_warning ("unexpected message type %d", msg.type); + goto error; + } + + /* read request target */ + target = http_get_app_target (req, &msg); + + request = http_get_tx_buf (hc); + /* currently we support only GET and POST method */ + if (msg.method_type == HTTP_REQ_GET) + { + if (msg.data.body_len) + { + clib_warning ("GET request shouldn't include data"); + goto error; + } + /* + * Add "protocol layer" headers: + * - host + * - user agent + */ + request = format (request, get_request_template, + /* target */ + target, + /* Host */ + hc->host, + /* User-Agent */ + hc->app_name); + + next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY; + sm_result = HTTP_SM_STOP; + } + else if (msg.method_type == HTTP_REQ_POST) + { + if (!msg.data.body_len) + { + clib_warning ("POST request should include data"); + goto error; + } + /* + * Add "protocol layer" headers: + * - host + * - user agent + * - content length + */ + request = format (request, post_request_template, + /* target */ + target, + /* Host */ + hc->host, + /* User-Agent */ + hc->app_name, + /* Content-Length */ + msg.data.body_len); + + http_req_tx_buffer_init (req, &msg); + + next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA; + sm_result = HTTP_SM_CONTINUE; + } + else + { + clib_warning ("unsupported method %d", msg.method_type); + goto error; + } + + /* Add headers from app (if any) */ + if (msg.data.headers_len) + { + HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len); + http1_write_app_headers (hc, &msg, &request); + } + /* Add empty line after headers */ + request = format (request, "\r\n"); + HTTP_DBG (3, "%v", request); + + max_enq = http_io_ts_max_write (hc, sp); + if (max_enq < vec_len (request)) + { + clib_warning ("sending request-line and headers failed!"); + sm_result = HTTP_SM_ERROR; + goto error; + } + http_io_ts_write (hc, request, vec_len (request), sp); + + http_req_state_change (req, next_state); + + http_io_ts_after_write (hc, sp, 0, 1); + goto done; + +error: + http_io_as_drain_all (req); + session_transport_closing_notify (&hc->connection); + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); + +done: + return sm_result; +} + +static http_sm_result_t +http1_req_state_app_io_more_data (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 max_write, n_segs, n_written = 0; + http_buffer_t *hb = &req->tx_buf; + svm_fifo_seg_t *seg; + u8 finished = 0; + + ASSERT (!http_buffer_is_drained (hb)); + max_write = http_io_ts_max_write (hc, sp); + if (max_write == 0) + { + HTTP_DBG (1, "ts tx fifo full"); + goto check_fifo; + } + + seg = http_buffer_get_segs (hb, max_write, &n_segs); + if (!seg) + { + HTTP_DBG (1, "no data to deq"); + goto check_fifo; + } + + n_written = http_io_ts_write_segs (hc, seg, n_segs, sp); + + http_buffer_drain (hb, n_written); + finished = http_buffer_is_drained (hb); + + if (finished) + { + /* Finished transaction: + * server back to HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD + * client to HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY */ + http_req_state_change (req, hc->is_server ? + HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD : + HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); + http_buffer_free (hb); + } + +check_fifo: + http_io_ts_after_write (hc, sp, finished, !!n_written); + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_tunnel_tx (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 max_deq, max_enq, max_read, n_segs = 2; + svm_fifo_seg_t segs[n_segs]; + int n_written = 0; + + HTTP_DBG (1, "tunnel received data from target"); + + max_deq = http_io_as_max_read (req); + if (PREDICT_FALSE (max_deq == 0)) + { + HTTP_DBG (1, "max_deq == 0"); + goto check_fifo; + } + max_enq = http_io_ts_max_write (hc, sp); + if (max_enq == 0) + { + HTTP_DBG (1, "ts tx fifo full"); + goto check_fifo; + } + max_read = clib_min (max_enq, max_deq); + http_io_as_read_segs (req, segs, &n_segs, max_read); + n_written = http_io_ts_write_segs (hc, segs, n_segs, sp); + http_io_as_drain (req, n_written); + +check_fifo: + http_io_ts_after_write (hc, sp, 0, !!n_written); + + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_udp_tunnel_tx (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 to_deq, capsule_size, dgram_size; + u8 written = 0; + session_dgram_hdr_t hdr; + u8 *buf; + u8 *payload; + + HTTP_DBG (1, "udp tunnel received data from target"); + + buf = http_get_tx_buf (hc); + to_deq = http_io_as_max_read (req); + + while (to_deq > 0) + { + /* read datagram header */ + http_io_as_read (req, (u8 *) &hdr, sizeof (hdr), 1); + ASSERT (hdr.data_length <= HTTP_UDP_PAYLOAD_MAX_LEN); + dgram_size = hdr.data_length + SESSION_CONN_HDR_LEN; + ASSERT (to_deq >= dgram_size); + + if (http_io_ts_max_write (hc, sp) < + (hdr.data_length + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD)) + { + HTTP_DBG (1, "ts tx fifo full"); + goto done; + } + + /* create capsule header */ + payload = http_encap_udp_payload_datagram (buf, hdr.data_length); + capsule_size = (payload - buf) + hdr.data_length; + /* read payload */ + http_io_as_read (req, payload, hdr.data_length, 1); + http_io_as_drain (req, dgram_size); + /* send capsule */ + http_io_ts_write (hc, buf, capsule_size, sp); + + written = 1; + to_deq -= dgram_size; + } + +done: + http_io_ts_after_write (hc, sp, 0, written); + + return HTTP_SM_STOP; +} + +/*************************/ +/* request state machine */ +/*************************/ + +static http_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = { + 0, /* idle */ + http1_req_state_wait_app_method, + 0, /* wait transport reply */ + 0, /* transport io more data */ + 0, /* wait transport method */ + http1_req_state_wait_app_reply, + http1_req_state_app_io_more_data, + http1_req_state_tunnel_tx, + http1_req_state_udp_tunnel_tx, +}; + +static http_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = { + 0, /* idle */ + 0, /* wait app method */ + http1_req_state_wait_transport_reply, + http1_req_state_transport_io_more_data, + http1_req_state_wait_transport_method, + 0, /* wait app reply */ + 0, /* app io more data */ + http1_req_state_tunnel_rx, + http1_req_state_udp_tunnel_rx, +}; + +static_always_inline int +http1_req_state_is_tx_valid (http_req_t *req) +{ + return tx_state_funcs[req->state] ? 1 : 0; +} + +static_always_inline int +http1_req_state_is_rx_valid (http_req_t *req) +{ + return rx_state_funcs[req->state] ? 1 : 0; +} + +static_always_inline void +http1_req_run_state_machine (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp, u8 is_tx) +{ + http_sm_result_t res; + + do + { + if (is_tx) + res = tx_state_funcs[req->state](hc, req, sp); + else + res = rx_state_funcs[req->state](hc, req, 0); + if (res == HTTP_SM_ERROR) + { + HTTP_DBG (1, "error in state machine %d", res); + return; + } + } + while (res == HTTP_SM_CONTINUE); + + /* Reset the session expiration timer */ + http_conn_timer_update (hc); +} + +/*****************/ +/* http core VFT */ +/*****************/ + +static void +http1_app_tx_callback (http_conn_t *hc, transport_send_params_t *sp) +{ + http_req_t *req; + + req = http_get_req_if_valid (hc, 0); + if (!req) + { + http_alloc_req (hc); + req = http_get_req (hc, 0); + req->app_session_handle = hc->h_pa_session_handle; + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD); + } + + if (!http1_req_state_is_tx_valid (req)) + { + /* Sometimes the server apps can send the response earlier + * than expected (e.g when rejecting a bad request)*/ + if (req->state == HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA && hc->is_server) + { + http_io_ts_drain_all (hc); + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_REPLY); + } + else + { + clib_warning ("hc [%u]%x invalid tx state: http req state " + "'%U', session state '%U'", + hc->c_thread_index, hc->h_hc_index, + format_http_req_state, req->state, + format_http_conn_state, hc); + http_io_as_drain_all (req); + return; + } + } + + HTTP_DBG (1, "run state machine"); + http1_req_run_state_machine (hc, req, sp, 1); +} + +static void +http1_app_rx_evt_callback (http_conn_t *hc) +{ + http_req_t *req; + + req = http_get_req (hc, 0); + + if (req->state == HTTP_REQ_STATE_TUNNEL) + http1_req_state_tunnel_rx (hc, req, 0); +} + +static void +http1_app_close_callback (http_conn_t *hc) +{ + http_req_t *req; + + req = http_get_req_if_valid (hc, 0); + /* Nothing more to send, confirm close */ + if (!req || !http_io_as_max_read (req)) + { + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); + } + else + { + /* Wait for all data to be written to ts */ + hc->state = HTTP_CONN_STATE_APP_CLOSED; + } +} + +static void +http1_app_reset_callback (http_conn_t *hc) +{ + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); +} + +static void +http1_transport_rx_callback (http_conn_t *hc) +{ + http_req_t *req; + + req = http_get_req_if_valid (hc, 0); + if (!req) + { + http_alloc_req (hc); + req = http_get_req (hc, 0); + req->app_session_handle = hc->h_pa_session_handle; + http_req_state_change (req, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD); + } + + if (!http1_req_state_is_rx_valid (req)) + { + clib_warning ("hc [%u]%x invalid rx state: http req state " + "'%U', session state '%U'", + hc->c_thread_index, hc->h_hc_index, format_http_req_state, + req->state, format_http_conn_state, hc); + http_io_ts_drain_all (hc); + return; + } + + HTTP_DBG (1, "run state machine"); + http1_req_run_state_machine (hc, req, 0, 0); +} + +static void +http1_transport_close_callback (http_conn_t *hc) +{ + /* Nothing more to rx, propagate to app */ + if (!http_io_ts_max_read (hc)) + session_transport_closing_notify (&hc->connection); +} + +const static http_engine_vft_t http1_engine = { + .app_tx_callback = http1_app_tx_callback, + .app_rx_evt_callback = http1_app_rx_evt_callback, + .app_close_callback = http1_app_close_callback, + .app_reset_callback = http1_app_reset_callback, + .transport_rx_callback = http1_transport_rx_callback, + .transport_close_callback = http1_transport_close_callback, +}; + +static clib_error_t * +http1_init (vlib_main_t *vm) +{ + http_register_engine (&http1_engine, HTTP_VERSION_1); + return 0; +} + +VLIB_INIT_FUNCTION (http1_init) = { + .runs_after = VLIB_INITS ("http_transport_init"), +}; |