refactor: http cleanup

master
Sean McBride 3 years ago
parent 768816934e
commit c0a375b7ee

@ -21,9 +21,9 @@ struct http_request {
char full_url[HTTP_MAX_FULL_URL_LENGTH]; char full_url[HTTP_MAX_FULL_URL_LENGTH];
struct http_header headers[HTTP_MAX_HEADER_COUNT]; struct http_header headers[HTTP_MAX_HEADER_COUNT];
int header_count; int header_count;
uint32_t method;
struct http_query_param query_params[HTTP_MAX_QUERY_PARAM_COUNT]; struct http_query_param query_params[HTTP_MAX_QUERY_PARAM_COUNT];
int query_params_count; int query_params_count;
int header_length;
char *body; char *body;
int body_length; int body_length;
int body_length_read; /* Amount read into buffer from socket */ int body_length_read; /* Amount read into buffer from socket */

@ -196,124 +196,132 @@ err:
goto done; goto done;
} }
static inline bool
typedef void (*http_session_receive_request_egain_cb)(struct http_session *); http_session_request_buffer_is_full(struct http_session *session)
{
return session->request_buffer.length == session->request_buffer.capacity;
}
/** /**
* Receive and Parse the Request for the current sandbox * Initalize state associated with an http parser
* @return 0 if message parsing complete, -1 on error, -2 if buffers run out of space * Because the http_parser structure uses pointers to the request buffer, if realloc moves the request
* buffer, this should be called to clear stale state to force parsing to restart
*/ */
static inline void
http_session_parser_init(struct http_session *session)
{
memset(&session->http_request, 0, sizeof(struct http_request));
http_parser_init(&session->http_parser, HTTP_REQUEST);
/* Set the session as the data the http-parser has access to */
session->http_parser.data = &session->http_request;
}
static inline int static inline int
http_session_receive_request(struct http_session *session, http_session_receive_request_egain_cb on_eagain) http_session_request_buffer_grow(struct http_session *session)
{ {
assert(session != NULL); /* We have not yet fully parsed the header, so we don't know content-length, so just grow
* (double) the buffer */
uint8_t *old_buffer = session->request_buffer.buffer;
int rc = 0; if (vec_u8_grow(&session->request_buffer) != 0) {
debuglog("Failed to grow request buffer\n");
return -1;
}
struct vec_u8 *request_buffer = &session->request_buffer; /* buffer moved, so invalidate to reparse */
assert(request_buffer->capacity > 0); if (old_buffer != session->request_buffer.buffer) { http_session_parser_init(session); }
assert(request_buffer->length <= request_buffer->capacity);
while (!session->http_request.message_end) { return 0;
/* Read from the Socket */ }
/* Structured to closely follow usage example at https://github.com/nodejs/http-parser */ static inline int
http_parser *parser = &session->http_parser; http_session_request_buffer_resize(struct http_session *session, int required_size)
const http_parser_settings *settings = http_parser_settings_get(); {
uint8_t *old_buffer = session->request_buffer.buffer;
if (vec_u8_resize(&session->request_buffer, required_size) != 0) {
debuglog("Failed to resize request vector to %d bytes\n", required_size);
return -1;
}
/* If header parsing is complete and the header specified a content-length, resize */ /* buffer moved, so invalidate to reparse */
/* If http_request.body is NULL, then the request has no body (i.e. a GET request) */ if (old_buffer != session->request_buffer.buffer) { http_session_parser_init(session); }
if (session->http_request.header_end && session->http_request.body != NULL) {
int header_size = (uint8_t *)session->http_request.body - session->request_buffer.buffer;
assert(header_size > 0);
int required_size = header_size + session->http_request.body_length;
if (required_size > request_buffer->capacity) { return 0;
uint8_t *old_buffer = request_buffer->buffer; }
if (vec_u8_resize(request_buffer, required_size) != 0) {
debuglog("Failed to resize request vector to %d bytes\n", required_size);
goto err_nobufs;
}
if (old_buffer != request_buffer->buffer) {
/* buffer moved, so invalidate to reparse */
memset(&session->http_request, 0, sizeof(struct http_request));
http_parser_init(&session->http_parser, HTTP_REQUEST);
/* Set the session as the data the http-parser has access to */
session->http_parser.data = &session->http_request;
}
}
} else if (request_buffer->length == request_buffer->capacity) {
/* When the length of our buffer is equal to capacity, the buffer is full */
/* We have not yet fully parsed the header, so we don't know content-length, so just grow
* (double) the buffer */
uint8_t *old_buffer = request_buffer->buffer;
if (vec_u8_grow(request_buffer) != 0) {
debuglog("Failed to grow request buffer\n");
goto err_nobufs;
}
if (old_buffer != request_buffer->buffer) { typedef void (*http_session_receive_request_egain_cb)(struct http_session *);
/* buffer moved, so invalidate to reparse */
memset(&session->http_request, 0, sizeof(struct http_request));
http_parser_init(&session->http_parser, HTTP_REQUEST);
/* Set the session as the data the http-parser has access to */
session->http_parser.data = &session->http_request;
}
}
ssize_t bytes_received = recv(session->socket, &request_buffer->buffer[request_buffer->length], static inline ssize_t
request_buffer->capacity - request_buffer->length, 0); http_session_receive_raw(struct http_session *session, http_session_receive_request_egain_cb on_eagain)
{
assert(session->request_buffer.capacity > session->request_buffer.length);
ssize_t bytes_received = recv(session->socket, &session->request_buffer.buffer[session->request_buffer.length],
session->request_buffer.capacity - session->request_buffer.length, 0);
if (bytes_received < 0) {
if (errno == EAGAIN) {
on_eagain(session);
return -EAGAIN;
} else {
debuglog("Error reading socket %d - %s\n", session->socket, strerror(errno));
return -1;
}
}
if (bytes_received < 0) { /* If we received an EOF before we were able to parse a complete HTTP message, request is malformed */
if (errno == EAGAIN) { if (bytes_received == 0 && !session->http_request.message_end) {
on_eagain(session); char client_address_text[INET6_ADDRSTRLEN] = {};
goto err_eagain; if (unlikely(inet_ntop(AF_INET, &session->client_address, client_address_text, INET6_ADDRSTRLEN)
} else { == NULL)) {
debuglog("Error reading socket %d - %s\n", session->socket, strerror(errno)); debuglog("Failed to log client_address: %s", strerror(errno));
goto err;
}
} }
/* If we received an EOF before we were able to parse a complete HTTP header, request is malformed */ debuglog("recv returned 0 before a complete request was received: socket: %d. Address: "
if (bytes_received == 0 && !session->http_request.message_end) { "%s\n",
char client_address_text[INET6_ADDRSTRLEN] = {}; session->socket, client_address_text);
if (unlikely(inet_ntop(AF_INET, &session->client_address, client_address_text, INET6_ADDRSTRLEN) http_request_print(&session->http_request);
== NULL)) { return -1;
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->request_buffer.length += bytes_received;
session->socket, client_address_text); return bytes_received;
http_request_print(&session->http_request); }
goto err;
} static inline ssize_t
http_session_parse(struct http_session *session, ssize_t bytes_received)
{
assert(session != 0);
assert(bytes_received > 0);
assert(bytes_received > 0); const http_parser_settings *settings = http_parser_settings_get();
request_buffer->length += bytes_received;
#ifdef LOG_HTTP_PARSER #ifdef LOG_HTTP_PARSER
debuglog("http_parser_execute(%p, %p, %p, %zu\n)", parser, settings, debuglog("http_parser_execute(%p, %p, %p, %zu\n)", &session->http_parser, settings,
&session->request_buffer.buffer[session->request_buffer.length], bytes_received); &session->request_buffer.buffer[session->request_buffer.length], bytes_received);
#endif #endif
size_t bytes_parsed = size_t bytes_parsed =
http_parser_execute(parser, settings, http_parser_execute(&session->http_parser, settings,
(const char *)&request_buffer->buffer[session->http_request.length_parsed], (const char *)&session->request_buffer.buffer[session->http_request.length_parsed],
(size_t)request_buffer->length - session->http_request.length_parsed); (size_t)session->request_buffer.length - session->http_request.length_parsed);
if (bytes_parsed < (size_t)bytes_received) { if (bytes_parsed < (size_t)bytes_received) {
debuglog("Error: %s, Description: %s\n", debuglog("Error: %s, Description: %s\n",
http_errno_name((enum http_errno)session->http_parser.http_errno), http_errno_name((enum http_errno)session->http_parser.http_errno),
http_errno_description((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("Length Parsed %zu, Length Read %zu\n", bytes_parsed, (size_t)bytes_received);
debuglog("Error parsing socket %d\n", session->socket); debuglog("Error parsing socket %d\n", session->socket);
goto err; return -1;
}
session->http_request.length_parsed += bytes_parsed;
} }
session->http_request.length_parsed += bytes_parsed;
return (ssize_t)bytes_parsed;
}
static inline void
http_session_log_query_params(struct http_session *session)
{
#ifdef LOG_HTTP_PARSER #ifdef LOG_HTTP_PARSER
for (int i = 0; i < session->http_request.query_params_count; i++) { 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, debuglog("Argument %d, Len: %d, %.*s\n", i, session->http_request.query_params[i].value_length,
@ -321,6 +329,49 @@ http_session_receive_request(struct http_session *session, http_session_receive_
session->http_request.query_params[i].value); session->http_request.query_params[i].value);
} }
#endif #endif
}
/**
* 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_request(struct http_session *session, http_session_receive_request_egain_cb on_eagain)
{
assert(session != NULL);
assert(session->request_buffer.capacity > 0);
assert(session->request_buffer.length <= session->request_buffer.capacity);
int rc = 0;
http_session_parser_init(session);
while (!session->http_request.message_end) {
/* If we know the header size and content-length, resize exactly. Otherwise double */
if (session->http_request.header_end && session->http_request.body) {
int header_size = (uint8_t *)session->http_request.body - session->request_buffer.buffer;
int required_size = header_size + session->http_request.body_length;
if (required_size > session->request_buffer.capacity) {
rc = http_session_request_buffer_resize(session, required_size);
if (rc != 0) goto err_nobufs;
}
} else if (http_session_request_buffer_is_full(session)) {
rc = http_session_request_buffer_grow(session);
if (rc != 0) goto err_nobufs;
}
ssize_t bytes_received = http_session_receive_raw(session, on_eagain);
if (bytes_received == -EAGAIN) goto err_eagain;
if (bytes_received == -1) goto err;
ssize_t bytes_parsed = http_session_parse(session, bytes_received);
if (bytes_parsed == -1) goto err;
}
assert(session->http_request.message_end == true);
http_session_log_query_params(session);
rc = 0; rc = 0;
done: done:

@ -34,7 +34,7 @@ typedef void (*void_cb)(void);
/** /**
* Writes buffer to the client socket * Writes buffer to the client socket
* @param client_socket - the client we are rejecting * @param client_socket - the client
* @param buffer - buffer to write to socket * @param buffer - buffer to write to socket
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out * @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
* @returns 0 on success, -1 on error. * @returns 0 on success, -1 on error.

@ -181,6 +181,9 @@ http_parser_settings_on_header_value(http_parser *parser, const char *at, size_t
/** /**
* http-parser callback called when header parsing is complete * http-parser callback called when header parsing is complete
* Just sets the HTTP Request's header_end flag to true * Just sets the HTTP Request's header_end flag to true
*
* Should return 1 to tell parser not to expect a body
* Should return 2 to tell parser not to expect a body nor any further responses
* @param parser * @param parser
*/ */
int int
@ -196,14 +199,16 @@ http_parser_settings_on_header_end(http_parser *parser)
#endif #endif
http_request->header_end = true; http_request->header_end = true;
http_request->method = parser->method;
/* Ignore the body of HTTP messages other than PUT and POST */
if (parser->method == HTTP_PUT || parser->method == HTTP_POST) { if (parser->method == HTTP_PUT || parser->method == HTTP_POST) {
http_request->body_length = parser->content_length; http_request->body_length = parser->content_length;
return 0;
} else { } else {
http_request->body_length = 0; http_request->body_length = 0;
return 2;
} }
return 0;
} }
const char *http_methods[] = { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT" }; const char *http_methods[] = { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT" };

@ -161,6 +161,9 @@ handle_tcp_requests(struct epoll_event *evt)
continue; continue;
} }
assert(session->http_request.message_end);
assert(session->http_request.body);
/* Route to sandbox */ /* Route to sandbox */
struct route *route = http_router_match_route(&tenant->router, session->http_request.full_url); struct route *route = http_router_match_route(&tenant->router, session->http_request.full_url);
if (route == NULL) { if (route == NULL) {
@ -222,6 +225,8 @@ resume_blocked_read(struct epoll_event *evt)
return; return;
} }
assert(session->http_request.message_end);
/* We read session to completion, so can remove from epoll */ /* We read session to completion, so can remove from epoll */
listener_thread_unregister_http_session(session); listener_thread_unregister_http_session(session);

Loading…
Cancel
Save