/* Copyright: Boaz Segev, 2016-2019 License: MIT Feel free to copy, use and enjoy according to the license provided. */ #ifndef H_HTTP_INTERNAL_H #define H_HTTP_INTERNAL_H #include /* subscription lists have a long lifetime */ #define FIO_FORCE_MALLOC_TMP 1 #define FIO_INCLUDE_LINKED_LIST #include #include #include #include /* ***************************************************************************** Types ***************************************************************************** */ typedef struct http_fio_protocol_s http_fio_protocol_s; typedef struct http_vtable_s http_vtable_s; struct http_vtable_s { /** Should send existing headers and data */ int (*const http_send_body)(http_s *h, void *data, uintptr_t length); /** Should send existing headers and file */ int (*const http_sendfile)(http_s *h, int fd, uintptr_t length, uintptr_t offset); /** Should send existing headers and data and prepare for streaming */ int (*const http_stream)(http_s *h, void *data, uintptr_t length); /** Should send existing headers or complete streaming */ void (*const http_finish)(http_s *h); /** Push for data. */ int (*const http_push_data)(http_s *h, void *data, uintptr_t length, FIOBJ mime_type); /** Upgrades a connection to Websockets. */ int (*const http2websocket)(http_s *h, websocket_settings_s *arg); /** Push for files. */ int (*const http_push_file)(http_s *h, FIOBJ filename, FIOBJ mime_type); /** Pauses the request / response handling. */ void (*http_on_pause)(http_s *, http_fio_protocol_s *); /** Resumes a request / response handling. */ void (*http_on_resume)(http_s *, http_fio_protocol_s *); /** hijacks the socket aaway from the protocol. */ intptr_t (*http_hijack)(http_s *h, fio_str_info_s *leftover); /** Upgrades an HTTP connection to an EventSource (SSE) connection. */ int (*http_upgrade2sse)(http_s *h, http_sse_s *sse); /** Writes data to an EventSource (SSE) connection. MUST free the FIOBJ. */ int (*http_sse_write)(http_sse_s *sse, FIOBJ str); /** Closes an EventSource (SSE) connection. */ int (*http_sse_close)(http_sse_s *sse); }; struct http_fio_protocol_s { fio_protocol_s protocol; /* facil.io protocol */ intptr_t uuid; /* socket uuid */ http_settings_s *settings; /* pointer to HTTP settings */ }; #define http2protocol(h) ((http_fio_protocol_s *)h->private_data.flag) /* ***************************************************************************** Constants that shouldn't be accessed by the users (`fiobj_dup` required). ***************************************************************************** */ extern FIOBJ HTTP_HEADER_ACCEPT_RANGES; extern FIOBJ HTTP_HEADER_WS_SEC_CLIENT_KEY; extern FIOBJ HTTP_HEADER_WS_SEC_KEY; extern FIOBJ HTTP_HVALUE_BYTES; extern FIOBJ HTTP_HVALUE_CLOSE; extern FIOBJ HTTP_HVALUE_CONTENT_TYPE_DEFAULT; extern FIOBJ HTTP_HVALUE_GZIP; extern FIOBJ HTTP_HVALUE_KEEP_ALIVE; extern FIOBJ HTTP_HVALUE_MAX_AGE; extern FIOBJ HTTP_HVALUE_NO_CACHE; extern FIOBJ HTTP_HVALUE_SSE_MIME; extern FIOBJ HTTP_HVALUE_WEBSOCKET; extern FIOBJ HTTP_HVALUE_WS_SEC_VERSION; extern FIOBJ HTTP_HVALUE_WS_UPGRADE; extern FIOBJ HTTP_HVALUE_WS_VERSION; /* ***************************************************************************** HTTP request/response object management ***************************************************************************** */ static inline void http_s_new(http_s *h, http_fio_protocol_s *owner, http_vtable_s *vtbl) { *h = (http_s){ .private_data = { .vtbl = vtbl, .flag = (uintptr_t)owner, .out_headers = fiobj_hash_new(), }, .headers = fiobj_hash_new(), .received_at = fio_last_tick(), .status = 200, }; } static inline void http_s_destroy(http_s *h, uint8_t log) { if (log && h->status && !h->status_str) { http_write_log(h); } fiobj_free(h->method); fiobj_free(h->status_str); fiobj_free(h->private_data.out_headers); fiobj_free(h->headers); fiobj_free(h->version); fiobj_free(h->query); fiobj_free(h->path); fiobj_free(h->cookies); fiobj_free(h->body); fiobj_free(h->params); *h = (http_s){ .private_data.vtbl = h->private_data.vtbl, .private_data.flag = h->private_data.flag, }; } static inline void http_s_clear(http_s *h, uint8_t log) { http_s_destroy(h, log); http_s_new(h, (http_fio_protocol_s *)h->private_data.flag, h->private_data.vtbl); } /** tests handle validity */ #define HTTP_INVALID_HANDLE(h) \ (!(h) || (!(h)->method && !(h)->status_str && (h)->status)) /* ***************************************************************************** Request / Response Handlers ***************************************************************************** */ /** Use this function to handle HTTP requests.*/ void http_on_request_handler______internal(http_s *h, http_settings_s *settings); void http_on_response_handler______internal(http_s *h, http_settings_s *settings); int http_send_error2(size_t error, intptr_t uuid, http_settings_s *settings); /* ***************************************************************************** EventSource Support (SSE) ***************************************************************************** */ typedef struct http_sse_internal_s { http_sse_s sse; /* the user SSE settings */ intptr_t uuid; /* the socket's uuid */ http_vtable_s *vtable; /* the protocol's vtable */ uintptr_t id; /* the SSE identifier */ fio_ls_s subscriptions; /* Subscription List */ fio_lock_i lock; /* Subscription List lock */ size_t ref; /* reference count */ } http_sse_internal_s; static inline void http_sse_init(http_sse_internal_s *sse, intptr_t uuid, http_vtable_s *vtbl, http_sse_s *args) { *sse = (http_sse_internal_s){ .sse = *args, .uuid = uuid, .subscriptions = FIO_LS_INIT(sse->subscriptions), .vtable = vtbl, .ref = 1, }; } static inline void http_sse_try_free(http_sse_internal_s *sse) { if (fio_atomic_sub(&sse->ref, 1)) return; fio_free(sse); } static inline void http_sse_destroy(http_sse_internal_s *sse) { while (fio_ls_any(&sse->subscriptions)) { void *sub = fio_ls_pop(&sse->subscriptions); fio_unsubscribe(sub); } if (sse->sse.on_close) sse->sse.on_close(&sse->sse); sse->uuid = -1; http_sse_try_free(sse); } /* ***************************************************************************** Helpers ***************************************************************************** */ /** sets an outgoing header only if it doesn't exist */ static inline void set_header_if_missing(FIOBJ hash, FIOBJ name, FIOBJ value) { FIOBJ old = fiobj_hash_replace(hash, name, value); if (!old) return; fiobj_hash_replace(hash, name, old); fiobj_free(value); } /** sets an outgoing header, collecting duplicates in an Array (i.e. cookies) */ static inline void set_header_add(FIOBJ hash, FIOBJ name, FIOBJ value) { FIOBJ old = fiobj_hash_replace(hash, name, value); if (!old) return; if (!value) { fiobj_free(old); return; } if (!FIOBJ_TYPE_IS(old, FIOBJ_T_ARRAY)) { FIOBJ tmp = fiobj_ary_new(); fiobj_ary_push(tmp, old); old = tmp; } if (FIOBJ_TYPE_IS(value, FIOBJ_T_ARRAY)) { for (size_t i = 0; i < fiobj_ary_count(value); ++i) { fiobj_ary_push(old, fiobj_dup(fiobj_ary_index(value, i))); } /* frees `value` */ fiobj_hash_set(hash, name, old); return; } /* value will be owned by both hash and array */ fiobj_ary_push(old, value); /* don't free `value` (leave in array) */ fiobj_hash_replace(hash, name, old); } #endif /* H_HTTP_INTERNAL_H */