| /* |
| copyright: Boaz segev, 2017 |
| license: MIT |
| |
| Feel free to copy, use and enjoy according to the license provided. |
| */ |
| #include "spnlock.inc" |
| |
| #include "facil.h" |
| #include "fiobj.h" |
| #include "http.h" |
| #include "http1_parser.h" |
| |
| #include <string.h> |
| |
| /* ***************************************************************************** |
| Header File Data |
| ***************************************************************************** */ |
| |
| #ifndef DEFAULT_MAX_MIDDLEWARE_COUNT |
| /** Sets the default maximum number of middleware functions in each category. */ |
| #define DEFAULT_MAX_MIDDLEWARE_COUNT 64 |
| #endif |
| #ifndef DEFAULT_MAX_MESSAGE_SIZE |
| /** Sets the default maximum message size. */ |
| #define DEFAULT_MAX_MESSAGE_SIZE 33554432 |
| #endif |
| |
| /** An opaque service type. */ |
| typedef struct fioapp_s fioapp_s; |
| |
| /** Manages protocol settings for the HTTP protocol */ |
| struct fioapp_settings_s { |
| /** |
| * The maximum size of incoming message data, in bytes. |
| * |
| * This applies to both HTTP and Websocket messages but doesn't include HTTP |
| * header data or cookies. |
| * |
| * Defaults to `DEFAULT_MAX_MESSAGE_SIZE` (33,554,432 bytes). |
| * |
| * It is recomended that this value is *reduced* and messaged are fragmented |
| * when possible. |
| */ |
| size_t max_message; |
| /** |
| * Logging flag - set to TRUE to log requests and service events. |
| */ |
| uint8_t log; |
| /** Short connection timeout, for HTTP/1.x. Defaults to ~5 seconds.*/ |
| uint8_t timeout_short; |
| /** Persistent connection timeout, for Websockets etc'. Defaults to ~40 |
| * seconds.*/ |
| uint8_t timeout_long; |
| /** Opaque user data for the optional `set_rw_hooks`. */ |
| void *rw_udata; |
| /** |
| * Allows a an implementation for the transport layer (i.e. TLS) without |
| * effecting the HTTP protocol. |
| */ |
| sock_rw_hook_s *(*set_rw_hooks)(intptr_t fduuid, void *rw_udata); |
| /** |
| * A cleanup callback for the `rw_udata`. |
| */ |
| void (*on_finish_rw)(intptr_t uuid, void *rw_udata); |
| }; |
| |
| extern fiobj_s *SOCK_UUID; |
| extern fiobj_s *PATH_INFO; |
| extern fiobj_s *PATH_ARRAY; |
| extern fiobj_s *QUERY; |
| extern fiobj_s *HEADERS; |
| extern fiobj_s *METHOD; |
| extern fiobj_s *HTTP_VERSION; |
| extern fiobj_s *BODY; |
| |
| fioapp_s *fioapp_new(void); |
| int fioapp_middleware_conn(fioapp_s *app, |
| int (**func)(fiobj_s *dest, fiobj_s *source, |
| void *udata), |
| void *udata); |
| int fioapp_middleware_req_http(fioapp_s *app, |
| int (**func)(fiobj_s *dest, fiobj_s *source, |
| void *udata), |
| void *udata); |
| int fioapp_middleware_req(fioapp_s *app, |
| int (**func)(fiobj_s *dest, fiobj_s *source, |
| void *udata), |
| void *udata); |
| void fioapp_destroy(fioapp_s *app); |
| |
| /* ***************************************************************************** |
| Constants Etc' |
| ***************************************************************************** */ |
| char *FIOBJ_HTTP1_Protocol_String = "facil_fiobj_http/1.1_protocol"; |
| |
| fiobj_s *SOCK_UUID; |
| fiobj_s *PATH_INFO; |
| fiobj_s *PATH_ARRAY; |
| fiobj_s *QUERY; |
| fiobj_s *HEADERS; |
| fiobj_s *METHOD; |
| fiobj_s *HTTP_VERSION; |
| fiobj_s *BODY; |
| |
| /* ***************************************************************************** |
| The Service object |
| ***************************************************************************** */ |
| /** |
| * Middleware contain a callback and a user opaque pointer. |
| * |
| * If the callback returns a non-zero value, no further processing is performed |
| * (except for resource cleanup). |
| */ |
| struct middleware_info_s { |
| int (**func)(fiobj_s *dest, fiobj_s *source, void *udata); |
| void *udata; |
| struct middleware_info_s *next; |
| }; |
| |
| struct fioapp_s { |
| struct fioapp_settings_s settings; |
| struct { |
| struct middleware_info_s **http; |
| struct middleware_info_s **websocket; |
| struct middleware_info_s **request; |
| size_t http_len; |
| size_t websocket_len; |
| size_t request_len; |
| } middleware; |
| }; |
| /* ***************************************************************************** |
| The HTTP/1.x Protocol object |
| ***************************************************************************** */ |
| #define POOL_SIZE 64 |
| typedef struct fiobj_fiobj_http1_protocol_s { |
| /* connection handling + pooling. */ |
| protocol_s protocol; |
| struct fiobj_fiobj_http1_protocol_s *next; |
| /* HTTP settings and request handling. */ |
| struct fioapp_settings_s *settings; |
| /* connection state. */ |
| uintptr_t uuid; |
| /* request state. */ |
| fiobj_s *request; |
| fiobj_s *headers; |
| fiobj_s *body; |
| /* parsing helpers. */ |
| http1_parser_s parser; |
| ssize_t len; |
| fiobj_s *buffer; |
| } fiobj_http1_protocol_s; |
| |
| static spn_lock_i lock; |
| static fiobj_http1_protocol_s mem_pool[POOL_SIZE]; |
| static fiobj_http1_protocol_s *pool; |
| |
| /* ***************************************************************************** |
| Alloc, Dealloc and Initialization |
| ***************************************************************************** */ |
| |
| static void destroy_data(void) { |
| fiobj_free(SOCK_UUID); |
| fiobj_free(PATH_INFO); |
| fiobj_free(PATH_ARRAY); |
| fiobj_free(QUERY); |
| fiobj_free(HEADERS); |
| fiobj_free(METHOD); |
| fiobj_free(HTTP_VERSION); |
| fiobj_free(BODY); |
| } |
| |
| static inline fiobj_http1_protocol_s *initialize_data(void) { |
| if (BODY) |
| return NULL; |
| /* Initialize symbols */ |
| SOCK_UUID = fiobj_sym_new("SOCK_UUID", 9); |
| PATH_INFO = fiobj_sym_new("PATH_INFO", 9); |
| PATH_ARRAY = fiobj_sym_new("PATH_ARRAY", 9); |
| QUERY = fiobj_sym_new("QUERY", 5); |
| HEADERS = fiobj_sym_new("HEADERS", 7); |
| METHOD = fiobj_sym_new("METHOD", 6); |
| HTTP_VERSION = fiobj_sym_new("HTTP_VERSION", 12); |
| BODY = fiobj_sym_new("BODY", 4); |
| atexit(destroy_data); |
| /* Initialize memory pool */ |
| for (fiobj_http1_protocol_s *i = mem_pool + 1; i < mem_pool + POOL_SIZE - 1; |
| i++) { |
| i->next = i + 1; |
| } |
| mem_pool[POOL_SIZE - 1].next = NULL; |
| pool = mem_pool + 1; |
| return mem_pool; |
| } |
| |
| static inline void protocol_free(fiobj_http1_protocol_s *pr) { |
| /* free objects except headers and body (they're nested in the request) */ |
| if (pr->buffer) |
| fiobj_free(pr->buffer); |
| if (pr->request) |
| fiobj_free(pr->request); |
| /* free the container or replace it in the pool */ |
| if (pr >= mem_pool && pr < mem_pool + POOL_SIZE) { |
| spn_lock(&lock); |
| pr->next = pool; |
| pool = pr->next; |
| spn_unlock(&lock); |
| } else { |
| free(pr); |
| } |
| } |
| |
| static inline fiobj_http1_protocol_s *protocol_alloc(void *settings) { |
| fiobj_http1_protocol_s *pr = NULL; |
| spn_lock(&lock); |
| if (pool) { |
| pr = pool; |
| pool = pool->next; |
| } else if (!BODY) { |
| pr = initialize_data(); |
| } |
| spn_unlock(&lock); |
| if (!pr) |
| pr = malloc(sizeof(*pr)); |
| *pr = (fiobj_http1_protocol_s){.settings = settings, .parser = {.udata = pr}}; |
| return pr; |
| } |
| |
| /* ***************************************************************************** |
| Parser callbacks |
| ***************************************************************************** */ |
| #define HTTP1_READ_BUFFER_SIZE 8096 |
| #define HTTP1_MAX_HEADER_COUNT 128 |
| |
| /** called when a request was received. */ |
| static int on_request(http1_parser_s *parser) { |
| fiobj_http1_protocol_s *pr = parser->udata; |
| } |
| /** called when a response was received. */ |
| static int on_response(http1_parser_s *parser) { |
| return -1; |
| (void)parser; |
| } |
| /** called when a request method is parsed. */ |
| static int on_method(http1_parser_s *parser, char *method, size_t method_len) { |
| fiobj_http1_protocol_s *pr = parser->udata; |
| if (pr->request) |
| fiobj_free(pr->request); |
| pr->request = fiobj_hash_new(); |
| fiobj_hash_set(pr->request, METHOD, fiobj_str_new(method, method_len)); |
| fiobj_hash_set(pr->request, SOCK_UUID, fiobj_num_new(pr->uuid)); |
| return 0; |
| } |
| /** called when a response status is parsed. the status_str is the string |
| * without the prefixed numerical status indicator.*/ |
| static int on_status(http1_parser_s *parser, size_t status, char *status_str, |
| size_t len) { |
| return -1; |
| (void)parser; |
| (void)len; |
| (void)status; |
| (void)status_str; |
| } |
| /** called when a request path (excluding query) is parsed. */ |
| static int on_path(http1_parser_s *parser, char *path, size_t path_len) { |
| fiobj_http1_protocol_s *pr = parser->udata; |
| fiobj_hash_set(pr->request, PATH_INFO, fiobj_str_new(path, path_len)); |
| return 0; |
| } |
| /** called when a request path (excluding query) is parsed. */ |
| static int on_query(http1_parser_s *parser, char *query, size_t query_len) { |
| fiobj_http1_protocol_s *pr = parser->udata; |
| fiobj_hash_set(pr->request, QUERY, fiobj_str_new(query, query_len)); |
| return 0; |
| } |
| /** called when a the HTTP/1.x version is parsed. */ |
| static int on_http_version(http1_parser_s *parser, char *version, size_t len) { |
| fiobj_http1_protocol_s *pr = parser->udata; |
| fiobj_hash_set(pr->request, HTTP_VERSION, fiobj_str_new(version, len)); |
| return 0; |
| } |
| /** called when a header is parsed. */ |
| static int on_header(http1_parser_s *parser, char *name, size_t name_len, |
| char *data, size_t data_len) { |
| fiobj_http1_protocol_s *pr = parser->udata; |
| if (fiobj_hash_count(pr->headers) >= HTTP1_MAX_HEADER_COUNT) |
| return -1; |
| fiobj_s *tmp = fiobj_sym_new(name, name_len); |
| fiobj_hash_set(pr->headers, tmp, fiobj_str_new(data, data_len)); |
| fiobj_free(tmp); |
| return 0; |
| } |
| |
| /** called when a body chunk is parsed. */ |
| static int on_body_chunk(http1_parser_s *parser, char *data, size_t data_len) { |
| fiobj_http1_protocol_s *pr = parser->udata; |
| if (parser->state.read == 0) { |
| if (parser->state.content_length > 0) { |
| if ((size_t)parser->state.content_length > pr->settings->max_message) |
| return -1; |
| pr->body = fiobj_str_buf(parser->state.content_length); |
| } else { |
| pr->body = fiobj_str_buf(4096); |
| } |
| fiobj_hash_set(pr->request, BODY, pr->body); |
| } |
| fiobj_str_write(pr->body, data, data_len); |
| if (fiobj_obj2cstr(pr->body).len > pr->settings->max_message) |
| return -1; |
| return 0; |
| } |
| |
| /** called when a protocol error occured. */ |
| static int on_error(http1_parser_s *parser) { |
| fiobj_http1_protocol_s *pr = parser->udata; |
| /* TODO: return HTTP response "Entity too big" or "Bad Request"*/ |
| |
| /* free resources */ |
| fiobj_free(pr->request); |
| pr->request = pr->headers = pr->body = NULL; |
| return 0; |
| } |
| |
| /* ***************************************************************************** |
| Protocol callbacks |
| ***************************************************************************** */ |
| |
| /** called when a data is available, but will not run concurrently */ |
| static void fio_http1_on_data(intptr_t uuid, protocol_s *pr_) { |
| fiobj_http1_protocol_s *pr = (fiobj_http1_protocol_s *)pr_; |
| char buffer[HTTP1_READ_BUFFER_SIZE]; |
| char *pos = buffer; |
| size_t unread = 0; |
| uint8_t reschedule = 1; |
| if (pr->buffer) { |
| /* leftover data from last "run" */ |
| fio_cstr_s b = fiobj_obj2cstr(pr->buffer); |
| memcpy(buffer, b.buffer, b.len); |
| unread += b.len; |
| fiobj_free(pr->buffer); |
| pr->buffer = NULL; |
| } |
| { |
| /* read data from socket, if any */ |
| const ssize_t tmp = |
| sock_read(uuid, buffer, HTTP1_READ_BUFFER_SIZE - unread); |
| if (tmp <= 0) |
| goto finish; |
| unread += (size_t)tmp; |
| } |
| { |
| /* consume data by parser */ |
| const size_t consumed = |
| http1_fio_parser(.buffer = buffer, .length = unread, |
| .parser = &pr->parser, .on_request = on_request, |
| .on_response = on_response, .on_method = on_method, |
| .on_status = on_status, .on_path = on_path, |
| .on_query = on_query, |
| .on_http_version = on_http_version, |
| .on_header = on_header, .on_body_chunk = on_body_chunk, |
| .on_error = on_error, ); |
| unread -= consumed; |
| pos += consumed; |
| } |
| /* readched leftover buffer limit? */ |
| if (unread >= HTTP1_READ_BUFFER_SIZE) |
| goto error; |
| /* schedule more data reading */ |
| facil_force_event(uuid, FIO_EVENT_ON_DATA); |
| |
| finish: |
| if (unread) { |
| /* store leftover data */ |
| pr->buffer = fiobj_str_new(pos, unread); |
| } |
| return; |
| |
| error: |
| on_error(&pr->parser); |
| } |
| |
| /** called when the connection was closed, but will not run concurrently */ |
| static void fio_http1_on_close(intptr_t uuid, protocol_s *pr_) { |
| fiobj_http1_protocol_s *pr = (fiobj_http1_protocol_s *)pr_; |
| protocol_free(pr); |
| return; |
| (void)uuid; |
| } |
| |
| /** called when the socket is ready to be written to. */ |
| // static void fio_http1_on_ready(intptr_t uuid, protocol_s *pr_); |
| |
| /* ***************************************************************************** |
| Listening callback |
| ***************************************************************************** */ |
| |
| static protocol_s *fio_http1_on_open(intptr_t uuid, void *udata) { |
| fiobj_http1_protocol_s *pr = protocol_alloc(udata); |
| facil_set_timeout(uuid, pr->settings->timeout_short); |
| pr->uuid = uuid; |
| if (sock_uuid2fd(uuid) + 16 >= sock_max_capacity()) { |
| /* TODO: send Server Busy response */ |
| // sock_close(uuid); // returning NULL closes the socket |
| return NULL; |
| } |
| return &pr->protocol; |
| } |