From 3c6477857e714d6202f93011fba6a08df2e88e49 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 20 Apr 2022 20:38:26 -0400 Subject: [PATCH] feat: Parse HTTP request on listener core --- runtime/include/http.h | 1 + runtime/include/http_request.h | 1 + runtime/include/http_session.h | 116 +++++++++++++++++++++- runtime/include/sandbox_receive_request.h | 103 +------------------ runtime/src/current_sandbox.c | 12 --- runtime/src/http_parser_settings.c | 8 ++ runtime/src/http_request.c | 1 + runtime/src/listener_thread.c | 21 +++- 8 files changed, 146 insertions(+), 117 deletions(-) diff --git a/runtime/include/http.h b/runtime/include/http.h index 6ea6779..d87220f 100644 --- a/runtime/include/http.h +++ b/runtime/include/http.h @@ -8,6 +8,7 @@ #define HTTP_MAX_HEADER_COUNT 16 #define HTTP_MAX_HEADER_LENGTH 32 #define HTTP_MAX_HEADER_VALUE_LENGTH 256 +#define HTTP_MAX_FULL_URL_LENGTH 256 #define HTTP_MAX_QUERY_PARAM_COUNT 16 #define HTTP_MAX_QUERY_PARAM_LENGTH 32 diff --git a/runtime/include/http_request.h b/runtime/include/http_request.h index 0eef208..1e2793e 100644 --- a/runtime/include/http_request.h +++ b/runtime/include/http_request.h @@ -18,6 +18,7 @@ struct http_query_param { }; struct http_request { + char full_url[HTTP_MAX_FULL_URL_LENGTH]; struct http_header headers[HTTP_MAX_HEADER_COUNT]; int header_count; struct http_query_param query_params[HTTP_MAX_QUERY_PARAM_COUNT]; diff --git a/runtime/include/http_session.h b/runtime/include/http_session.h index 6377280..475f31d 100644 --- a/runtime/include/http_session.h +++ b/runtime/include/http_session.h @@ -1,11 +1,19 @@ #pragma once -#include +#include +#include +#include +#include #include +#include +#include +#include #include "client_socket.h" +#include "debuglog.h" #include "http_request.h" #include "http_parser.h" +#include "http_parser_settings.h" #include "vec.h" #define u8 uint8_t @@ -143,3 +151,109 @@ http_session_close(struct http_session *session) { return client_socket_close(session->socket, &session->client_address); } + + +/** + * Receive and Parse the Request for the current sandbox + * @return 0 if message parsing complete, -1 on error, -2 if buffers run out of space + */ +static inline int +http_session_receive(struct http_session *session, void_cb on_eagain) +{ + assert(session != NULL); + + int rc = 0; + + struct vec_u8 *request = &session->request; + assert(request->capacity > 0); + assert(request->length < request->capacity); + + while (!session->http_request.message_end) { + /* Read from the Socket */ + + /* Structured to closely follow usage example at https://github.com/nodejs/http-parser */ + http_parser *parser = &session->http_parser; + const http_parser_settings *settings = http_parser_settings_get(); + + size_t request_length = request->length; + size_t request_capacity = request->capacity; + + if (request_length >= request_capacity) { + debuglog("Ran out of Request Buffer before message end\n"); + goto err_nobufs; + } + + ssize_t bytes_received = recv(session->socket, &request->buffer[request_length], + request_capacity - request_length, 0); + + if (bytes_received < 0) { + if (errno == EAGAIN) { + if (on_eagain == NULL) { + goto err_eagain; + } else { + on_eagain(); + continue; + } + } else { + debuglog("Error reading socket %d - %s\n", session->socket, strerror(errno)); + goto err; + } + } + + /* If we received an EOF before we were able to parse a complete HTTP header, request is malformed */ + if (bytes_received == 0 && !session->http_request.message_end) { + char client_address_text[INET6_ADDRSTRLEN] = {}; + if (unlikely(inet_ntop(AF_INET, &session->client_address, client_address_text, INET6_ADDRSTRLEN) + == NULL)) { + debuglog("Failed to log client_address: %s", strerror(errno)); + } + + debuglog("recv returned 0 before a complete request was received: socket: %d. Address: %s\n", + session->socket, client_address_text); + http_request_print(&session->http_request); + goto err; + } + + assert(bytes_received > 0); + +#ifdef LOG_HTTP_PARSER + debuglog("http_parser_execute(%p, %p, %p, %zu\n)", parser, settings, + &session->request.buffer[session->request.length], bytes_received); +#endif + size_t bytes_parsed = http_parser_execute(parser, settings, + (const char *)&request->buffer[request_length], + (size_t)bytes_received); + + if (bytes_parsed != (size_t)bytes_received) { + debuglog("Error: %s, Description: %s\n", + http_errno_name((enum http_errno)session->http_parser.http_errno), + http_errno_description((enum http_errno)session->http_parser.http_errno)); + debuglog("Length Parsed %zu, Length Read %zu\n", bytes_parsed, (size_t)bytes_received); + debuglog("Error parsing socket %d\n", session->socket); + goto err; + } + + request->length += bytes_parsed; + } + +#ifdef LOG_HTTP_PARSER + for (int i = 0; i < session->http_request.query_params_count; i++) { + debuglog("Argument %d, Len: %d, %.*s\n", i, session->http_request.query_params[i].value_length, + session->http_request.query_params[i].value_length, + session->http_request.query_params[i].value); + } +#endif + + rc = 0; +done: + return rc; +err_eagain: + rc = -3; + goto done; +err_nobufs: + rc = -2; + goto done; +err: + rc = -1; + goto done; +} diff --git a/runtime/include/sandbox_receive_request.h b/runtime/include/sandbox_receive_request.h index 11b3bef..19e3109 100644 --- a/runtime/include/sandbox_receive_request.h +++ b/runtime/include/sandbox_receive_request.h @@ -1,20 +1,7 @@ #pragma once -#include -#include -#include -#include -#include -#include - #include "current_sandbox.h" -#include "debuglog.h" -#include "http_parser.h" -#include "http_request.h" -#include "http_parser_settings.h" -#include "likely.h" #include "sandbox_types.h" -#include "scheduler.h" /** * Receive and Parse the Request for the current sandbox @@ -24,93 +11,7 @@ static inline int sandbox_receive_request(struct sandbox *sandbox) { assert(sandbox != NULL); + struct http_session *session = sandbox->http; - int rc = 0; - - struct vec_u8 *request = &sandbox->http->request; - assert(request->length == 0); - assert(request->capacity > 0); - - while (!sandbox->http->http_request.message_end) { - /* Read from the Socket */ - - /* Structured to closely follow usage example at https://github.com/nodejs/http-parser */ - http_parser *parser = &sandbox->http->http_parser; - const http_parser_settings *settings = http_parser_settings_get(); - - size_t request_length = request->length; - size_t request_capacity = request->capacity; - - if (request_length >= request_capacity) { - debuglog("Sandbox %lu: Ran out of Request Buffer before message end\n", sandbox->id); - goto err_nobufs; - } - - ssize_t bytes_received = recv(sandbox->http->socket, &request->buffer[request_length], - request_capacity - request_length, 0); - - if (bytes_received < 0) { - if (errno == EAGAIN) { - current_sandbox_sleep(); - continue; - } else { - debuglog("Error reading socket %d - %s\n", sandbox->http->socket, strerror(errno)); - goto err; - } - } - - /* If we received an EOF before we were able to parse a complete HTTP header, request is malformed */ - if (bytes_received == 0 && !sandbox->http->http_request.message_end) { - char client_address_text[INET6_ADDRSTRLEN] = {}; - if (unlikely( - inet_ntop(AF_INET, &sandbox->http->client_address, client_address_text, INET6_ADDRSTRLEN) - == NULL)) { - debuglog("Failed to log client_address: %s", strerror(errno)); - } - - debuglog("Sandbox %lu: recv returned 0 before a complete request was received\n", sandbox->id); - debuglog("Socket: %d. Address: %s\n", sandbox->http->socket, client_address_text); - http_request_print(&sandbox->http->http_request); - goto err; - } - - assert(bytes_received > 0); - -#ifdef LOG_HTTP_PARSER - debuglog("Sandbox: %lu http_parser_execute(%p, %p, %p, %zu\n)", sandbox->id, parser, settings, - &sandbox->http->request.buffer[sandbox->http->request.length], bytes_received); -#endif - size_t bytes_parsed = http_parser_execute(parser, settings, - (const char *)&request->buffer[request_length], - (size_t)bytes_received); - - if (bytes_parsed != (size_t)bytes_received) { - debuglog("Error: %s, Description: %s\n", - http_errno_name((enum http_errno)sandbox->http->http_parser.http_errno), - http_errno_description((enum http_errno)sandbox->http->http_parser.http_errno)); - debuglog("Length Parsed %zu, Length Read %zu\n", bytes_parsed, (size_t)bytes_received); - debuglog("Error parsing socket %d\n", sandbox->http->socket); - goto err; - } - - request->length += bytes_parsed; - } - -#ifdef LOG_HTTP_PARSER - for (int i = 0; i < sandbox->http->http_request.query_params_count; i++) { - debuglog("Argument %d, Len: %d, %.*s\n", i, sandbox->http->http_request.query_params[i].value_length, - sandbox->http->http_request.query_params[i].value_length, - sandbox->http->http_request.query_params[i].value); - } -#endif - - rc = 0; -done: - return rc; -err_nobufs: - rc = -2; - goto done; -err: - rc = -1; - goto done; + return http_session_receive(session, current_sandbox_sleep); } diff --git a/runtime/src/current_sandbox.c b/runtime/src/current_sandbox.c index dd1f0b3..792c121 100644 --- a/runtime/src/current_sandbox.c +++ b/runtime/src/current_sandbox.c @@ -136,18 +136,6 @@ current_sandbox_init() worker_thread_epoll_add_sandbox(sandbox); - /* TODO: Move to listener code */ - rc = sandbox_receive_request(sandbox); - if (rc == -2) { - error_message = "Request size exceeded Buffer\n"; - /* Request size exceeded Buffer, send 413 Payload Too Large */ - http_session_send_err(sandbox->http, 413, current_sandbox_sleep); - goto err; - } else if (rc == -1) { - http_session_send_err(sandbox->http, 400, current_sandbox_sleep); - goto err; - } - /* Initialize sandbox memory */ struct module *current_module = sandbox_get_module(sandbox); module_initialize_memory(current_module); diff --git a/runtime/src/http_parser_settings.c b/runtime/src/http_parser_settings.c index 156adf9..94dc08e 100644 --- a/runtime/src/http_parser_settings.c +++ b/runtime/src/http_parser_settings.c @@ -32,6 +32,14 @@ http_parser_settings_on_url(http_parser *parser, const char *at, size_t length) char *query_params = memchr(at, '?', length); + /* Full URL excludes query params if present */ + size_t full_url_length = query_params == NULL ? length : query_params - at; + + size_t to_copy = full_url_length < HTTP_MAX_FULL_URL_LENGTH - 1 ? full_url_length + : HTTP_MAX_FULL_URL_LENGTH - 1; + strncpy(http_request->full_url, at, to_copy); + http_request->full_url[to_copy] = '\0'; + if (query_params != NULL) { char *prev = query_params + 1; char *cur = NULL; diff --git a/runtime/src/http_request.c b/runtime/src/http_request.c index 50bbfdf..8995c9a 100644 --- a/runtime/src/http_request.c +++ b/runtime/src/http_request.c @@ -9,6 +9,7 @@ void http_request_print(struct http_request *http_request) { + printf("Full URL %s\n", http_request->full_url); printf("Header Count %d\n", http_request->header_count); printf("Header Content:\n"); for (int i = 0; i < http_request->header_count; i++) { diff --git a/runtime/src/listener_thread.c b/runtime/src/listener_thread.c index f5bc394..097ca5e 100644 --- a/runtime/src/listener_thread.c +++ b/runtime/src/listener_thread.c @@ -168,7 +168,23 @@ listener_thread_main(void *dummy) http_session_alloc(module->max_request_size, module->max_response_size, client_socket, (const struct sockaddr *)&client_address); - /* TODO: Read HTTP request */ + /* Read HTTP request */ + int rc = 0; + while ((rc = http_session_receive(session, NULL)) == -3) debuglog("Loop\n"); + + if (rc == -2) { + debuglog("Request size exceeded Buffer\n"); + /* Request size exceeded Buffer, send 413 Payload Too Large */ + http_session_send_err_oneshot(session, 413); + http_session_close(session); + continue; + } else if (rc == -1) { + http_session_send_err_oneshot(session, 400); + http_session_close(session); + continue; + } + + http_request_print(&session->http_request); /* * Perform admissions control. @@ -179,8 +195,7 @@ listener_thread_main(void *dummy) if (work_admitted == 0) { client_socket_send_oneshot(client_socket, http_header_build(429), http_header_len(429)); - if (unlikely(close(client_socket) < 0)) - debuglog("Error closing client socket - %s", strerror(errno)); + http_session_close(session); continue; }