feat: Parse HTTP request on listener core

master
Sean McBride 3 years ago
parent 160b38e4dd
commit 3c6477857e

@ -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

@ -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];

@ -1,11 +1,19 @@
#pragma once
#include <sys/socket.h>
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#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;
}

@ -1,20 +1,7 @@
#pragma once
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#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);
}

@ -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);

@ -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;

@ -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++) {

@ -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;
}

Loading…
Cancel
Save