From 0927b7489dc5d9f453a4e5c13e14722008ab2763 Mon Sep 17 00:00:00 2001 From: Jeremy Williams Date: Fri, 17 Jan 2020 14:31:47 -0500 Subject: [PATCH 1/5] Reorginaize code --- httpserver.h | 464 +++++++++++++++++++++++++-------------------------- 1 file changed, 232 insertions(+), 232 deletions(-) diff --git a/httpserver.h b/httpserver.h index 3551c7b..68626f0 100644 --- a/httpserver.h +++ b/httpserver.h @@ -290,50 +290,86 @@ int main() { #include #include -void http_platform_add_server_sock_events(struct http_server_s* serv); -void http_platform_server_init(struct http_server_s* serv); -void http_platform_delete_events(struct http_request_s* request); -void http_platform_add_events(struct http_request_s* request); -void http_platform_add_write_event(struct http_request_s* request); - #ifdef KQUEUE #include - -void http_server_listen_cb(struct kevent* ev); -void http_session_io_cb(struct kevent* ev); #else #include #include +#endif -typedef void (*epoll_cb_t)(struct epoll_event*); - -void http_server_listen_cb(struct epoll_event* ev); -void http_session_io_cb(struct epoll_event* ev); -void http_server_timer_cb(struct epoll_event* ev); -void http_request_timer_cb(struct epoll_event* ev); +// *** macro definitions -#endif +// Application configurable +#define HTTP_REQUEST_BUF_SIZE 1024 +#define HTTP_RESPONSE_BUF_SIZE 512 +#define HTTP_REQUEST_TIMEOUT 20 +#define HTTP_KEEP_ALIVE_TIMEOUT 120 -/****************************************************************************** - * - * HTTP Parser - * - *****************************************************************************/ +#define HTTP_FLAG_SET(var, flag) var |= flag +#define HTTP_FLAG_CLEAR(var, flag) var &= ~flag +#define HTTP_FLAG_CHECK(var, flag) (var & flag) +// token types / parser states #define HTTP_METHOD 0 #define HTTP_TARGET 1 #define HTTP_VERSION 2 #define HTTP_HEADER_KEY 3 #define HTTP_HEADER_VALUE 4 +#define HTTP_HEADER_END 5 #define HTTP_NONE 6 #define HTTP_BODY 7 +// chunked token types / parser state #define HTTP_CHUNK_SIZE 8 #define HTTP_CHUNK_EXTN 9 #define HTTP_CHUNK_BODY 10 #define HTTP_CHUNK_BODY_END 11 #define HTTP_CHUNK_BODY_PARTIAL 12 +// parser flags +#define HS_PF_TRANSFER_ENCODING 0x1 +#define HS_PF_CONTENT_LENGTH 0x2 +#define HS_PF_CHUNKED 0x4 + +// parser sub states +#define HTTP_LWS 2 +#define HTTP_CR 3 +#define HTTP_CRLF 4 + +// parser comparisons +#define HS_CONTENT_LENGTH_LOW "content-length" +#define HS_CONTENT_LENGTH_UP "CONTENT-LENGTH" +#define HS_TRANSFER_ENCODING_LOW "transfer-encoding" +#define HS_TRANSFER_ENCODING_UP "TRANSFER-ENCODING" +#define HS_CHUNKED_LOW "chunked" +#define HS_CHUNKED_UP "CHUNKED" + +// parser sentinel lengths +#define HS_PAYLOAD_TOO_LARGE -1 +#define HTTP_CHUNKED_LEN -2 + +// http session states +#define HTTP_SESSION_INIT 0 +#define HTTP_SESSION_READ_HEADERS 1 +#define HTTP_SESSION_READ_BODY 2 +#define HTTP_SESSION_WRITE 3 +#define HTTP_SESSION_READ_CHUNK 4 +#define HTTP_SESSION_NOP 5 + +// http session flags +#define HTTP_RESPONSE_READY 0x4 +#define HTTP_AUTOMATIC 0x8 +#define HTTP_RESPONSE_PAUSED 0x10 +#define HTTP_CHUNKED_RESPONSE 0x20 + +// http version indicators +#define HTTP_1_0 0 +#define HTTP_1_1 1 + +// *** declarations *** + +// structs + typedef struct { int index; int len; @@ -390,29 +426,150 @@ typedef struct http_request_s { char flags; } http_request_t; -#define HS_PF_TRANSFER_ENCODING 0x1 -#define HS_PF_CONTENT_LENGTH 0x2 -#define HS_PF_CHUNKED 0x4 +typedef struct http_server_s { +#ifdef KQUEUE + void (*handler)(struct kevent* ev); +#else + epoll_cb_t handler; + epoll_cb_t timer_handler; +#endif + int socket; + int port; + int loop; + int timerfd; + socklen_t len; + void (*request_handler)(http_request_t*); + struct sockaddr_in addr; + char* date; +} http_server_t; -#define HTTP_LWS 2 -#define HTTP_CR 3 -#define HTTP_CRLF 4 +typedef struct http_header_s { + char const * key; + char const * value; + struct http_header_s* next; +} http_header_t; -#define HTTP_HEADER_END 5 +typedef struct http_response_s { + http_header_t* headers; + char const * body; + int content_length; + int status; +} http_response_t; -#define HS_CONTENT_LENGTH_LOW "content-length" -#define HS_CONTENT_LENGTH_UP "CONTENT-LENGTH" -#define HS_TRANSFER_ENCODING_LOW "transfer-encoding" -#define HS_TRANSFER_ENCODING_UP "TRANSFER-ENCODING" -#define HS_CHUNKED_LOW "chunked" -#define HS_CHUNKED_UP "CHUNKED" -#define HS_PAYLOAD_TOO_LARGE -1 +typedef struct http_string_s http_string_t; -#define HTTP_CHUNKED_LEN -1 +// prototypes -#define HTTP_FLAG_SET(var, flag) var |= flag -#define HTTP_FLAG_CLEAR(var, flag) var &= ~flag -#define HTTP_FLAG_CHECK(var, flag) (var & flag) +void hs_add_server_sock_events(struct http_server_s* serv); +void hs_server_init(struct http_server_s* serv); +void hs_delete_events(struct http_request_s* request); +void hs_add_events(struct http_request_s* request); +void hs_add_write_event(struct http_request_s* request); + +void hs_exec_response_handler(http_request_t* request, void (*handler)(http_request_t*)); + +#ifdef KQUEUE + +void hs_server_listen_cb(struct kevent* ev); +void hs_session_io_cb(struct kevent* ev); + +#else + +typedef void (*epoll_cb_t)(struct epoll_event*); + +void hs_server_listen_cb(struct epoll_event* ev); +void hs_session_io_cb(struct epoll_event* ev); +void hs_server_timer_cb(struct epoll_event* ev); +void hs_request_timer_cb(struct epoll_event* ev); + +#endif + +// constants + +char const * hs_status_text[] = { + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + + //100s + "Continue", "Switching Protocols", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + + //200s + "OK", "Created", "Accepted", "Non-Authoritative Information", "No Content", + "Reset Content", "Partial Content", "", "", "", + + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + + //300s + "Multiple Choices", "Moved Permanently", "Found", "See Other", "Not Modified", + "Use Proxy", "", "Temporary Redirect", "", "", + + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + + //400s + "Bad Request", "Unauthorized", "Payment Required", "Forbidden", "Not Found", + "Method Not Allowed", "Not Acceptable", "Proxy Authentication Required", + "Request Timeout", "Conflict", + + "Gone", "Length Required", "", "Payload Too Large", "", "", "", "", "", "", + + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + + //500s + "Internal Server Error", "Not Implemented", "Bad Gateway", "Service Unavailable", + "Gateway Timeout", "", "", "", "", "" + + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "" +}; + +// *** http parser *** #define HS_P_MATCH_HEADER(up, low, i) \ if ((c == up[(int)i] || c == low[(int)i]) && i < (char)(sizeof(up) - 1)) i++; @@ -669,36 +826,7 @@ void http_parse_start_chunk_mode(http_parser_t* parser) { parser->state = HTTP_CHUNK_SIZE; } -/****************************************************************************** - * - * HTTP Server - * - *****************************************************************************/ - -#define HTTP_RESPONSE_READY 0x4 -#define HTTP_AUTOMATIC 0x8 -#define HTTP_RESPONSE_PAUSED 0x10 -#define HTTP_CHUNKED 0x20 - -#define HTTP_REQUEST_TIMEOUT 20 -#define HTTP_KEEP_ALIVE_TIMEOUT 120 - -typedef struct http_server_s { -#ifdef KQUEUE - void (*handler)(struct kevent* ev); -#else - epoll_cb_t handler; - epoll_cb_t timer_handler; -#endif - int socket; - int port; - int loop; - int timerfd; - socklen_t len; - void (*request_handler)(http_request_t*); - struct sockaddr_in addr; - char* date; -} http_server_t; +// *** http server *** void http_token_dyn_push(http_token_dyn_t* dyn, http_token_t a) { if (dyn->size == dyn->capacity) { @@ -717,8 +845,6 @@ void http_token_dyn_init(http_token_dyn_t* dyn, int capacity) { dyn->capacity = capacity; } -#define HTTP_REQUEST_BUF_SIZE 1024 - void hs_bind_localhost(int s, struct sockaddr_in* addr, int port) { addr->sin_family = AF_INET; addr->sin_addr.s_addr = INADDR_ANY; @@ -813,19 +939,12 @@ int hs_reading_body(http_request_t* request) { } void hs_end_session(http_request_t* session) { - http_platform_delete_events(session); + hs_delete_events(session); close(session->socket); hs_free_buffer(session); free(session); } -#define HTTP_SESSION_INIT 0 -#define HTTP_SESSION_READ_HEADERS 1 -#define HTTP_SESSION_READ_BODY 2 -#define HTTP_SESSION_WRITE 3 -#define HTTP_SESSION_READ_CHUNK 4 -#define HTTP_SESSION_NOP 5 - void hs_reset_timeout(http_request_t* request, int time) { request->timeout = time; } @@ -837,10 +956,10 @@ void hs_write_response(http_request_t* request) { if (request->bytes != request->capacity) { // All bytes of the body were not written and we need to wait until the // socket is writable again to complete the write - http_platform_add_write_event(request); + hs_add_write_event(request); request->state = HTTP_SESSION_WRITE; hs_reset_timeout(request, HTTP_REQUEST_TIMEOUT); - } else if (HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED)) { + } else if (HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED_RESPONSE)) { // All bytes of the chunk were written and we need to get the next chunk // from the application. request->state = HTTP_SESSION_WRITE; @@ -976,10 +1095,10 @@ void hs_accept_connections(http_server_t* server) { session->socket = sock; session->server = server; session->timeout = HTTP_REQUEST_TIMEOUT; - session->handler = http_session_io_cb; + session->handler = hs_session_io_cb; int flags = fcntl(sock, F_GETFL, 0); fcntl(sock, F_SETFL, flags | O_NONBLOCK); - http_platform_add_events(session); + hs_add_events(session); http_session(session); } } while (sock > 0); @@ -997,8 +1116,8 @@ http_server_t* http_server_init(int port, void (*handler)(http_request_t*)) { http_server_t* serv = (http_server_t*)malloc(sizeof(http_server_t)); assert(serv != NULL); serv->port = port; - serv->handler = http_server_listen_cb; - http_platform_server_init(serv); + serv->handler = hs_server_listen_cb; + hs_server_init(serv); hs_generate_date_time(&serv->date); serv->request_handler = handler; return serv; @@ -1015,7 +1134,7 @@ void http_listen(http_server_t* serv) { int flags = fcntl(serv->socket, F_GETFL, 0); fcntl(serv->socket, F_SETFL, flags | O_NONBLOCK); listen(serv->socket, 128); - http_platform_add_server_sock_events(serv); + hs_add_server_sock_events(serv); } int http_server_listen_poll(http_server_t* serv) { @@ -1027,13 +1146,7 @@ int http_server_loop(http_server_t* server) { return server->loop; } -/****************************************************************************** - * - * HTTP Request - * - *****************************************************************************/ - -typedef struct http_string_s http_string_t; +// *** http request *** http_string_t http_get_token_string(http_request_t* request, int token_type) { http_string_t str = { 0, 0 }; @@ -1139,9 +1252,6 @@ void http_request_set_userdata(http_request_t* request, void* data) { request->data = data; } -#define HTTP_1_0 0 -#define HTTP_1_1 1 - void auto_detect_keep_alive(http_request_t* request) { http_string_t str = http_get_token_string(request, HTTP_VERSION); int version = str.buf[str.len - 1] == '1'; @@ -1173,109 +1283,7 @@ http_string_t http_request_chunk(struct http_request_s* request) { }; } -/****************************************************************************** - * - * HTTP Response - * - *****************************************************************************/ - -typedef struct http_header_s { - char const * key; - char const * value; - struct http_header_s* next; -} http_header_t; - -typedef struct http_response_s { - http_header_t* headers; - char const * body; - int content_length; - int status; -} http_response_t; - -#define HTTP_RESPONSE_BUF_SIZE 512 - -char const * status_text[] = { - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - - //100s - "Continue", "Switching Protocols", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - - //200s - "OK", "Created", "Accepted", "Non-Authoritative Information", "No Content", - "Reset Content", "Partial Content", "", "", "", - - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - - //300s - "Multiple Choices", "Moved Permanently", "Found", "See Other", "Not Modified", - "Use Proxy", "", "Temporary Redirect", "", "", - - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - - //400s - "Bad Request", "Unauthorized", "Payment Required", "Forbidden", "Not Found", - "Method Not Allowed", "Not Acceptable", "Proxy Authentication Required", - "Request Timeout", "Conflict", - - "Gone", "Length Required", "", "Payload Too Large", "", "", "", "", "", "", - - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - - //500s - "Internal Server Error", "Not Implemented", "Bad Gateway", "Service Unavailable", - "Gateway Timeout", "", "", "", "", "" - - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "" -}; +// *** http response *** http_response_t* http_response_init() { http_response_t* response = (http_response_t*)calloc(1, sizeof(http_response_t)); @@ -1352,7 +1360,7 @@ void http_buffer_headers( grwprintf(printctx, "%s: %s\r\n", header->key, header->value); header = header->next; } - if (!HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED)) { + if (!HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED_RESPONSE)) { grwprintf(printctx, "Content-Length: %d\r\n", response->content_length); } grwprintf(printctx, "\r\n"); @@ -1373,7 +1381,7 @@ void http_respond_headers( } grwprintf( printctx, "HTTP/1.1 %d %s\r\nDate: %.24s\r\n", - response->status, status_text[response->status], request->server->date + response->status, hs_status_text[response->status], request->server->date ); http_buffer_headers(request, response, printctx); } @@ -1417,8 +1425,8 @@ void http_respond_chunk( ) { grwprintf_t printctx; grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE); - if (!HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED)) { - HTTP_FLAG_SET(request->flags, HTTP_CHUNKED); + if (!HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED_RESPONSE)) { + HTTP_FLAG_SET(request->flags, HTTP_CHUNKED_RESPONSE); http_response_header(response, "Transfer-Encoding", "chunked"); http_respond_headers(request, response, &printctx); } @@ -1435,19 +1443,15 @@ void http_respond_chunk_end(http_request_t* request, http_response_t* response) grwprintf(&printctx, "0\r\n"); http_buffer_headers(request, response, &printctx); grwprintf(&printctx, "\r\n"); - HTTP_FLAG_CLEAR(request->flags, HTTP_CHUNKED); + HTTP_FLAG_CLEAR(request->flags, HTTP_CHUNKED_RESPONSE); http_end_response(request, response, &printctx); } -/****************************************************************************** - * - * kqueue platform specific - * - *****************************************************************************/ +// *** kqueue platform specific *** #ifdef KQUEUE -void http_server_listen_cb(struct kevent* ev) { +void hs_server_listen_cb(struct kevent* ev) { http_server_t* server = (http_server_t*)ev->udata; if (ev->filter == EVFILT_TIMER) { hs_generate_date_time(&server->date); @@ -1456,7 +1460,7 @@ void http_server_listen_cb(struct kevent* ev) { } } -void http_session_io_cb(struct kevent* ev) { +void hs_session_io_cb(struct kevent* ev) { http_request_t* request = (http_request_t*)ev->udata; if (ev->filter == EVFILT_TIMER) { request->timeout -= 1; @@ -1466,14 +1470,14 @@ void http_session_io_cb(struct kevent* ev) { } } -void http_platform_server_init(http_server_t* serv) { +void hs_server_init(http_server_t* serv) { serv->loop = kqueue(); struct kevent ev_set; EV_SET(&ev_set, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, NOTE_SECONDS, 1, serv); kevent(serv->loop, &ev_set, 1, NULL, 0, NULL); } -void http_platform_add_server_sock_events(http_server_t* serv) { +void hs_add_server_sock_events(http_server_t* serv) { struct kevent ev_set; EV_SET(&ev_set, serv->socket, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, serv); kevent(serv->loop, &ev_set, 1, NULL, 0, NULL); @@ -1494,7 +1498,7 @@ int http_server_listen(http_server_t* serv) { return 0; } -void http_platform_delete_events(http_request_t* request) { +void hs_delete_events(http_request_t* request) { struct kevent ev_set; EV_SET(&ev_set, request->socket, EVFILT_TIMER, EV_DELETE, 0, 0, request); kevent(request->server->loop, &ev_set, 1, NULL, 0, NULL); @@ -1511,14 +1515,14 @@ int http_server_poll(http_server_t* serv) { return nev; } -void http_platform_add_events(http_request_t* request) { +void hs_add_events(http_request_t* request) { struct kevent ev_set[2]; EV_SET(&ev_set[0], request->socket, EVFILT_READ, EV_ADD, 0, 0, request); EV_SET(&ev_set[1], request->socket, EVFILT_TIMER, EV_ADD | EV_ENABLE, NOTE_SECONDS, 1, request); kevent(request->server->loop, ev_set, 2, NULL, 0, NULL); } -void http_platform_add_write_event(http_request_t* request) { +void hs_add_write_event(http_request_t* request) { struct kevent ev_set[2]; EV_SET(&ev_set[0], request->socket, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, request); kevent(request->server->loop, ev_set, 2, NULL, 0, NULL); @@ -1526,21 +1530,17 @@ void http_platform_add_write_event(http_request_t* request) { #else -/****************************************************************************** - * - * epoll platform specific - * - *****************************************************************************/ +// *** epoll platform specific *** -void http_server_listen_cb(struct epoll_event* ev) { +void hs_server_listen_cb(struct epoll_event* ev) { hs_accept_connections((http_server_t*)ev->data.ptr); } -void http_session_io_cb(struct epoll_event* ev) { +void hs_session_io_cb(struct epoll_event* ev) { http_session((http_request_t*)ev->data.ptr); } -void http_server_timer_cb(struct epoll_event* ev) { +void hs_server_timer_cb(struct epoll_event* ev) { http_server_t* server = (http_server_t*)((char*)ev->data.ptr - sizeof(epoll_cb_t)); uint64_t res; int bytes = read(server->timerfd, &res, sizeof(res)); @@ -1548,7 +1548,7 @@ void http_server_timer_cb(struct epoll_event* ev) { hs_generate_date_time(&server->date); } -void http_request_timer_cb(struct epoll_event* ev) { +void hs_request_timer_cb(struct epoll_event* ev) { http_request_t* request = (http_request_t*)((char*)ev->data.ptr - sizeof(epoll_cb_t)); uint64_t res; int bytes = read(request->timerfd, &res, sizeof(res)); @@ -1557,16 +1557,16 @@ void http_request_timer_cb(struct epoll_event* ev) { if (request->timeout == 0) hs_end_session(request); } -void http_platform_add_server_sock_events(http_server_t* serv) { +void hs_add_server_sock_events(http_server_t* serv) { struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; ev.data.ptr = serv; epoll_ctl(serv->loop, EPOLL_CTL_ADD, serv->socket, &ev); } -void http_platform_server_init(http_server_t* serv) { +void hs_server_init(http_server_t* serv) { serv->loop = epoll_create1(0); - serv->timer_handler = http_server_timer_cb; + serv->timer_handler = hs_server_timer_cb; int tfd = timerfd_create(CLOCK_MONOTONIC, 0); struct itimerspec ts = {}; @@ -1594,7 +1594,7 @@ int http_server_listen(http_server_t* serv) { return 0; } -void http_platform_delete_events(http_request_t* request) { +void hs_delete_events(http_request_t* request) { epoll_ctl(request->server->loop, EPOLL_CTL_DEL, request->socket, NULL); epoll_ctl(request->server->loop, EPOLL_CTL_DEL, request->timerfd, NULL); close(request->timerfd); @@ -1609,8 +1609,8 @@ int http_server_poll(http_server_t* serv) { return nev; } -void http_platform_add_events(http_request_t* request) { - request->timer_handler = http_request_timer_cb; +void hs_add_events(http_request_t* request) { + request->timer_handler = hs_request_timer_cb; // Watch for read events struct epoll_event ev; @@ -1631,7 +1631,7 @@ void http_platform_add_events(http_request_t* request) { request->timerfd = tfd; } -void http_platform_add_write_event(http_request_t* request) { +void hs_add_write_event(http_request_t* request) { struct epoll_event ev; ev.events = EPOLLOUT | EPOLLET; ev.data.ptr = request; From 1e536664587f4effcce22690414433a6fd270eee Mon Sep 17 00:00:00 2001 From: Jeremy Williams Date: Fri, 17 Jan 2020 18:15:17 -0500 Subject: [PATCH 2/5] Add limits to protect server against OOM --- httpserver.h | 123 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 33 deletions(-) diff --git a/httpserver.h b/httpserver.h index 68626f0..043d8db 100644 --- a/httpserver.h +++ b/httpserver.h @@ -65,6 +65,19 @@ * HTTP_KEEP_ALIVE_TIMEOUT - default 120 - The amount of seconds to keep a * connection alive a keep-alive request has completed. * +* HTTP_MAX_CONTENT_LENGTH - default 8388608 (8MB) - The max size in bytes +* of the request content length. It should be noted that the request body +* will be fully read into memory so while this could be redefined to a +* value as large as INT_MAX it will allocate a lot of memory. I would +* reccommend using chunked encoding for large requests. +* +* HTTP_MAX_TOTAL_EST_MEM_USAGE - default 4294967296 (4GB) - This is the +* amount of read/write buffer space that is allowed to be allocated across +* all requests before new requests will get 503 responses. +* +* HTTP_MAX_TOKEN_LENGTH - default 8192 (8KB) - This is the max size of any +* non body http tokens. i.e: header names, header values, url length, etc. +* * For more details see the documentation of the interface and the example * below. * @@ -304,6 +317,11 @@ int main() { #define HTTP_RESPONSE_BUF_SIZE 512 #define HTTP_REQUEST_TIMEOUT 20 #define HTTP_KEEP_ALIVE_TIMEOUT 120 +#define HTTP_MAX_CONTENT_LENGTH 8388608 // 8mb +#define HTTP_MAX_TOKEN_LENGTH 8192 // 8kb +#define HTTP_MAX_TOTAL_EST_MEM_USAGE 4294967296 // 4gb + +#define HTTP_MAX_HEADER_COUNT 127 #define HTTP_FLAG_SET(var, flag) var |= flag #define HTTP_FLAG_CLEAR(var, flag) var &= ~flag @@ -318,6 +336,11 @@ int main() { #define HTTP_HEADER_END 5 #define HTTP_NONE 6 #define HTTP_BODY 7 +#define HTTP_PARSE_ERROR 13 + +// error sub types +#define HTTP_ERR_PAYLOAD_TOO_LARGE 0 +#define HTTP_ERR_BAD_REQUEST 1 // chunked token types / parser state #define HTTP_CHUNK_SIZE 8 @@ -345,8 +368,7 @@ int main() { #define HS_CHUNKED_UP "CHUNKED" // parser sentinel lengths -#define HS_PAYLOAD_TOO_LARGE -1 -#define HTTP_CHUNKED_LEN -2 +#define HTTP_CHUNKED_LEN -1 // http session states #define HTTP_SESSION_INIT 0 @@ -382,6 +404,7 @@ typedef struct { int token_start_index; int start; int body_start_index; + char header_count; char content_length_i; char transfer_encoding_i; char flags; @@ -418,6 +441,7 @@ typedef struct http_request_s { int socket; char* buf; int bytes; + int written; int capacity; int timeout; struct http_server_s* server; @@ -433,6 +457,7 @@ typedef struct http_server_s { epoll_cb_t handler; epoll_cb_t timer_handler; #endif + long memused; int socket; int port; int loop; @@ -574,6 +599,15 @@ char const * hs_status_text[] = { #define HS_P_MATCH_HEADER(up, low, i) \ if ((c == up[(int)i] || c == low[(int)i]) && i < (char)(sizeof(up) - 1)) i++; +http_token_t hs_parse_error(http_parser_t* parser, int subtype) { + parser->len = 0; + parser->state = HTTP_PARSE_ERROR; + http_token_t token; + token.index = subtype; + token.type = HTTP_PARSE_ERROR; + return token; +} + http_token_t http_parse(http_parser_t* parser, char* input, int n) { for (int i = parser->start; i < n; ++i, parser->start = i + 1, parser->len++) { char c = input[i]; @@ -667,19 +701,20 @@ http_token_t http_parse(http_parser_t* parser, char* input, int n) { HTTP_FLAG_SET(parser->flags, HS_PF_CHUNKED); } parser->transfer_encoding_i = 0; + if (parser->header_count == HTTP_MAX_HEADER_COUNT) { + return hs_parse_error(parser, HTTP_ERR_BAD_REQUEST); + } + parser->header_count++; http_token_t token; token.index = parser->token_start_index; token.type = HTTP_HEADER_VALUE; token.len = parser->len; return token; - } else if ( - HTTP_FLAG_CHECK(parser->flags, HS_PF_CONTENT_LENGTH) && - parser->content_length != HS_PAYLOAD_TOO_LARGE - ) { + } else if (HTTP_FLAG_CHECK(parser->flags, HS_PF_CONTENT_LENGTH)) { int64_t new_content_length = parser->content_length * 10l; new_content_length += c - '0'; - if (new_content_length > INT_MAX) { - parser->content_length = HS_PAYLOAD_TOO_LARGE; + if (new_content_length > HTTP_MAX_CONTENT_LENGTH) { + return hs_parse_error(parser, HTTP_ERR_PAYLOAD_TOO_LARGE); } else { parser->content_length = (int)new_content_length; } @@ -715,6 +750,9 @@ http_token_t http_parse(http_parser_t* parser, char* input, int n) { } break; } + if (parser->len >= HTTP_MAX_TOKEN_LENGTH && parser->state != HTTP_BODY) { + return hs_parse_error(parser, HTTP_ERR_BAD_REQUEST); + } } http_token_t token = { 0, 0, 0 }; token.type = HTTP_NONE; @@ -857,6 +895,7 @@ void hs_bind_localhost(int s, struct sockaddr_in* addr, int port) { int hs_read_client_socket(http_request_t* session) { if (!session->buf) { + session->server->memused += HTTP_REQUEST_BUF_SIZE; session->buf = (char*)calloc(1, HTTP_REQUEST_BUF_SIZE); assert(session->buf != NULL); session->capacity = HTTP_REQUEST_BUF_SIZE; @@ -871,7 +910,9 @@ int hs_read_client_socket(http_request_t* session) { ); if (bytes > 0) session->bytes += bytes; if (session->bytes == session->capacity) { + session->server->memused -= session->capacity; session->capacity *= 2; + session->server->memused += session->capacity; session->buf = (char*)realloc(session->buf, session->capacity); assert(session->buf != NULL); } @@ -882,16 +923,17 @@ int hs_read_client_socket(http_request_t* session) { int hs_write_client_socket(http_request_t* session) { int bytes = write( session->socket, - session->buf + session->bytes, - session->capacity - session->bytes + session->buf + session->written, + session->bytes - session->written ); - if (bytes > 0) session->bytes += bytes; + if (bytes > 0) session->written += bytes; return errno == EPIPE ? 0 : 1; } void hs_free_buffer(http_request_t* session) { if (session->buf) { free(session->buf); + session->server->memused -= session->capacity; session->buf = NULL; free(session->tokens.buf); session->tokens.buf = NULL; @@ -916,6 +958,7 @@ void hs_init_session(http_request_t* session) { session->flags |= HTTP_AUTOMATIC; session->parser = (http_parser_t){ }; session->bytes = 0; + session->written = 0; session->buf = NULL; session->token.len = 0; session->token.index = 0; @@ -949,11 +992,9 @@ void hs_reset_timeout(http_request_t* request, int time) { request->timeout = time; } -void hs_exec_response_handler(http_request_t* request, void (*handler)(http_request_t*)); - void hs_write_response(http_request_t* request) { if (!hs_write_client_socket(request)) { return hs_end_session(request); } - if (request->bytes != request->capacity) { + if (request->written != request->bytes) { // All bytes of the body were not written and we need to wait until the // socket is writable again to complete the write hs_add_write_event(request); @@ -1034,16 +1075,21 @@ void http_session(http_request_t* request) { case HTTP_SESSION_INIT: hs_init_session(request); request->state = HTTP_SESSION_READ_HEADERS; + if (request->server->memused > HTTP_MAX_TOTAL_EST_MEM_USAGE) { + return hs_error_response(request, 503, "Service Unavailable"); + } // fallthrough case HTTP_SESSION_READ_HEADERS: if (!hs_read_client_socket(request)) { return hs_end_session(request); } hs_reset_timeout(request, HTTP_REQUEST_TIMEOUT); hs_parse_tokens(request); - if ( - !hs_parsing_headers(request) && - request->parser.content_length == HS_PAYLOAD_TOO_LARGE - ) { - return hs_error_response(request, 413, "Payload Too Large"); + if (request->token.type == HTTP_PARSE_ERROR) { + switch (request->token.index) { + case HTTP_ERR_BAD_REQUEST: + return hs_error_response(request, 400, "Bad Request"); + case HTTP_ERR_PAYLOAD_TOO_LARGE: + return hs_error_response(request, 413, "Payload Too Large"); + } } else if (hs_reading_body(request)) { // The full request body has not been ready. Need to wait for more IO. request->state = HTTP_SESSION_READ_BODY; @@ -1116,6 +1162,7 @@ http_server_t* http_server_init(int port, void (*handler)(http_request_t*)) { http_server_t* serv = (http_server_t*)malloc(sizeof(http_server_t)); assert(serv != NULL); serv->port = port; + serv->memused = 0; serv->handler = hs_server_listen_cb; hs_server_init(serv); hs_generate_date_time(&serv->date); @@ -1150,6 +1197,7 @@ int http_server_loop(http_server_t* server) { http_string_t http_get_token_string(http_request_t* request, int token_type) { http_string_t str = { 0, 0 }; + if (request->tokens.buf == NULL) return str; for (int i = 0; i < request->tokens.size; i++) { http_token_t token = request->tokens.buf[i]; if (token.type == token_type) { @@ -1161,7 +1209,7 @@ http_string_t http_get_token_string(http_request_t* request, int token_type) { return str; } -int case_insensitive_cmp(char const * a, char const * b, int len) { +int hs_case_insensitive_cmp(char const * a, char const * b, int len) { for (int i = 0; i < len; i++) { char c1 = a[i] >= 'A' && a[i] <= 'Z' ? a[i] + 32 : a[i]; char c2 = b[i] >= 'A' && b[i] <= 'Z' ? b[i] + 32 : b[i]; @@ -1182,7 +1230,7 @@ http_string_t http_request_body(http_request_t* request) { return http_get_token_string(request, HTTP_BODY); } -int assign_iteration_headers( +int hs_assign_iteration_headers( http_request_t* request, http_string_t* key, http_string_t* val, @@ -1213,13 +1261,13 @@ int http_request_iterate_headers( for ( ; *iter < request->tokens.size; (*iter)++) { http_token_t token = request->tokens.buf[*iter]; if (token.type == HTTP_HEADER_KEY) { - return assign_iteration_headers(request, key, val, iter); + return hs_assign_iteration_headers(request, key, val, iter); } } return 0; } else { (*iter)++; - return assign_iteration_headers(request, key, val, iter); + return hs_assign_iteration_headers(request, key, val, iter); } } @@ -1228,7 +1276,7 @@ http_string_t http_request_header(http_request_t* request, char const * key) { for (int i = 0; i < request->tokens.size; i++) { http_token_t token = request->tokens.buf[i]; if (token.type == HTTP_HEADER_KEY && token.len == len) { - if (case_insensitive_cmp(&request->buf[token.index], key, len)) { + if (hs_case_insensitive_cmp(&request->buf[token.index], key, len)) { token = request->tokens.buf[i + 1]; return (http_string_t) { .buf = &request->buf[token.index], @@ -1252,12 +1300,13 @@ void http_request_set_userdata(http_request_t* request, void* data) { request->data = data; } -void auto_detect_keep_alive(http_request_t* request) { +void hs_auto_detect_keep_alive(http_request_t* request) { http_string_t str = http_get_token_string(request, HTTP_VERSION); + if (str.buf == NULL) return; int version = str.buf[str.len - 1] == '1'; str = http_request_header(request, "Connection"); if ( - (str.len == 5 && case_insensitive_cmp(str.buf, "close", 5)) || + (str.len == 5 && hs_case_insensitive_cmp(str.buf, "close", 5)) || (str.len == 0 && version == HTTP_1_0) ) { HTTP_FLAG_CLEAR(request->flags, HTTP_KEEP_ALIVE); @@ -1315,18 +1364,23 @@ typedef struct { char* buf; int capacity; int size; + long* memused; } grwprintf_t; -void grwprintf_init(grwprintf_t* ctx, int capacity) { +void grwprintf_init(grwprintf_t* ctx, int capacity, long* memused) { + ctx->memused = memused; ctx->size = 0; ctx->buf = (char*)malloc(capacity); + *ctx->memused += capacity; assert(ctx->buf != NULL); ctx->capacity = capacity; } void grwmemcpy(grwprintf_t* ctx, char const * src, int size) { if (ctx->size + size > ctx->capacity) { + *ctx->memused -= ctx->capacity; ctx->capacity = ctx->size + size; + *ctx->memused += ctx->capacity; ctx->buf = (char*)realloc(ctx->buf, ctx->capacity); assert(ctx->buf != NULL); } @@ -1340,7 +1394,9 @@ void grwprintf(grwprintf_t* ctx, char const * fmt, ...) { int bytes = vsnprintf(ctx->buf + ctx->size, ctx->capacity - ctx->size, fmt, args); if (bytes + ctx->size > ctx->capacity) { + *ctx->memused -= ctx->capacity; while (bytes + ctx->size > ctx->capacity) ctx->capacity *= 2; + *ctx->memused += ctx->capacity; ctx->buf = (char*)realloc(ctx->buf, ctx->capacity); assert(ctx->buf != NULL); bytes += vsnprintf(ctx->buf + ctx->size, ctx->capacity - ctx->size, fmt, args); @@ -1372,7 +1428,7 @@ void http_respond_headers( grwprintf_t* printctx ) { if (HTTP_FLAG_CHECK(request->flags, HTTP_AUTOMATIC)) { - auto_detect_keep_alive(request); + hs_auto_detect_keep_alive(request); } if (HTTP_FLAG_CHECK(request->flags, HTTP_KEEP_ALIVE)) { http_response_header(response, "Connection", "keep-alive"); @@ -1396,8 +1452,9 @@ void http_end_response(http_request_t* request, http_response_t* response, grwpr hs_free_buffer(request); free(response); request->buf = printctx->buf; - request->bytes = 0; - request->capacity = printctx->size; + request->written = 0; + request->bytes = printctx->size; + request->capacity = printctx->capacity; request->state = HTTP_SESSION_WRITE; // Signal that the response is ready for writing. HTTP_FLAG_SET(request->flags, HTTP_RESPONSE_READY); @@ -1410,7 +1467,7 @@ void http_end_response(http_request_t* request, http_response_t* response, grwpr void http_respond(http_request_t* request, http_response_t* response) { grwprintf_t printctx; - grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE); + grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE, &request->server->memused); http_respond_headers(request, response, &printctx); if (response->body) { grwmemcpy(&printctx, response->body, response->content_length); @@ -1424,7 +1481,7 @@ void http_respond_chunk( void (*cb)(http_request_t*) ) { grwprintf_t printctx; - grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE); + grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE, &request->server->memused); if (!HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED_RESPONSE)) { HTTP_FLAG_SET(request->flags, HTTP_CHUNKED_RESPONSE); http_response_header(response, "Transfer-Encoding", "chunked"); @@ -1439,7 +1496,7 @@ void http_respond_chunk( void http_respond_chunk_end(http_request_t* request, http_response_t* response) { grwprintf_t printctx; - grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE); + grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE, &request->server->memused); grwprintf(&printctx, "0\r\n"); http_buffer_headers(request, response, &printctx); grwprintf(&printctx, "\r\n"); From b1166b2a8539251e41dca976df40f6cdd1bc6950 Mon Sep 17 00:00:00 2001 From: Jeremy Williams Date: Fri, 17 Jan 2020 18:20:42 -0500 Subject: [PATCH 3/5] Fix compile issues after reorg on linux --- httpserver.h | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/httpserver.h b/httpserver.h index 043d8db..049d94a 100644 --- a/httpserver.h +++ b/httpserver.h @@ -418,6 +418,10 @@ typedef struct { int size; } http_token_dyn_t; +#ifdef EPOLL +typedef void (*epoll_cb_t)(struct epoll_event*); +#endif + typedef struct http_ev_cb_s { #ifdef KQUEUE void (*handler)(struct kevent* ev); @@ -493,17 +497,11 @@ void hs_add_write_event(struct http_request_s* request); void hs_exec_response_handler(http_request_t* request, void (*handler)(http_request_t*)); -#ifdef KQUEUE - -void hs_server_listen_cb(struct kevent* ev); -void hs_session_io_cb(struct kevent* ev); - -#else - -typedef void (*epoll_cb_t)(struct epoll_event*); - void hs_server_listen_cb(struct epoll_event* ev); void hs_session_io_cb(struct epoll_event* ev); + +#ifdef EPOLL + void hs_server_timer_cb(struct epoll_event* ev); void hs_request_timer_cb(struct epoll_event* ev); From 3a84be819a69f0cd7fa4863a3d649a3b3c3ec439 Mon Sep 17 00:00:00 2001 From: Jeremy Williams Date: Fri, 17 Jan 2020 18:28:33 -0500 Subject: [PATCH 4/5] Fix macos --- httpserver.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/httpserver.h b/httpserver.h index 049d94a..e2aa6b5 100644 --- a/httpserver.h +++ b/httpserver.h @@ -497,11 +497,15 @@ void hs_add_write_event(struct http_request_s* request); void hs_exec_response_handler(http_request_t* request, void (*handler)(http_request_t*)); -void hs_server_listen_cb(struct epoll_event* ev); -void hs_session_io_cb(struct epoll_event* ev); +#ifdef KQUEUE -#ifdef EPOLL +void hs_server_listen_cb(struct kevent* ev); +void hs_session_io_cb(struct kevent* ev); +#else + +void hs_server_listen_cb(struct epoll_event* ev); +void hs_session_io_cb(struct epoll_event* ev); void hs_server_timer_cb(struct epoll_event* ev); void hs_request_timer_cb(struct epoll_event* ev); From 6a88a06bf5b0b14b6b4349aafaa9bcd6dedacf36 Mon Sep 17 00:00:00 2001 From: Jeremy Williams Date: Fri, 17 Jan 2020 18:31:02 -0500 Subject: [PATCH 5/5] Test runner fixes --- test/run | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/run b/test/run index 019f915..d2e124c 100755 --- a/test/run +++ b/test/run @@ -37,14 +37,17 @@ curl -H'User-Agent: test-ua' -H'Foo-Header: foo-bar' http://localhost:8080/heade echo "\nStress Test keep-alive:" ab -k -c 200 -n 100000 http://127.0.0.1:8080/ > keep-alive.bench cat keep-alive.bench | grep Complete +cat large-file.bench | grep Non-2xx cat keep-alive.bench 1>&2 echo "\nStress Test close:" ab -c 20 -n 1000 http://127.0.0.1:8080/ > connection-close.bench cat connection-close.bench | grep Complete +cat large-file.bench | grep Non-2xx cat connection-close.bench 1>&2 echo "\nStress Test large body:" -dd if=/dev/urandom of=test.dat bs=52428800 count=1 2> /dev/null +dd if=/dev/urandom of=test.dat bs=8388608 count=1 2> /dev/null ab -c 2 -n 10 -p test.dat http://127.0.0.1:8080/echo > large-file.bench cat large-file.bench | grep Complete +cat large-file.bench | grep Non-2xx cat large-file.bench 1>&2 rm *.dat *.bench