QUOTE: Let your heart guide you always.

libhttp

A basic HTTP Framework

commit 2eff0b30b8b48d376386d0f872991c1192223912
parent bc113ba911b1a73f7a33284923334f3390e316c7
Author: Sophie <info@soophie.de>
Date:   Sun,  6 Apr 2025 12:03:57 +0000

feat: Added routing, macros & fixed memory leaks

Diffstat:
MMakefile | 2--
Msrc/libhttp.h | 304++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 277 insertions(+), 29 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,5 +1,3 @@ -default: install - install: sudo cp src/libhttp.h /usr/local/include/ diff --git a/src/libhttp.h b/src/libhttp.h @@ -83,7 +83,7 @@ typedef enum { HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511, } http_status_t; -typedef struct Http http_t; +typedef struct http http_t; typedef struct { char *hostname; @@ -135,22 +135,48 @@ typedef struct { long body_len; } http_resp_t; -struct Http { +typedef struct { + char *method; + char *path; + http_resp_t (*fn)(http_req_t *); +} http_route_t; + +typedef struct { + char *key; + char *value; +} http_query_t; + +typedef struct { + char *path; + http_query_t *queries; + size_t queries_len; +} http_url_t; + +struct http { http_bind_t *binds; - int binds_len; + size_t binds_len; pthread_t thread; bool quit; SSL_CTX *ssl_ctx; - http_resp_t (*on_req_fn)(http_req_t *); + http_resp_t (*on_req_fn)(http_t *, http_req_t *); }; http_t *http_init(void); void http_cleanup(http_t *http); -void http_on_req(http_t *http, http_resp_t fn(http_req_t *)); +void http_on_req(http_t *http, http_resp_t fn(http_t *, http_req_t *)); +void http_on_route(char *method, char *url, http_resp_t (*fn)(http_req_t *)); +void http_on_default(http_resp_t (*fn)(http_req_t *)); +void http_on_pre_route(void (*fn)(http_req_t *)); +void http_on_post_route(void (*fn)(http_req_t *)); +http_url_t http_url_init(http_req_t *req); +void http_url_free(http_url_t *self); +char *http_url_get(http_url_t *self, char *key); char *http_header_get(http_req_t *req, char *key); void http_header_set(http_resp_t *resp, char *key, char *value); http_resp_t http_resp_create(http_status_t status); void http_resp_body_set(http_resp_t *resp, char *body, long len); +void http_req_free(http_req_t *req); +void http_resp_free(http_resp_t *resp); http_err_t http_bind(http_t *http, char *hostname, int port, http_host_t *hosts, int hosts_len); http_err_t http_listen(http_t *http); void http_close(http_t *http); @@ -176,6 +202,96 @@ void http_close(http_t *http); #define HTTP_SERVER_SLEEP_TIME 100000000 #define HTTP_SERVER_POLL_TIME 100 +#define _HTTP_ON(method, path, fn_name) \ + static http_resp_t fn_name(http_req_t *req); \ + __attribute__((constructor)) \ + static void __macro_http_fn_register_##fn_name() { \ + http_on_route(method, path, &fn_name); \ + } \ + static http_resp_t fn_name(http_req_t *req) + +#define HTTP_GET(path, fn_name) _HTTP_ON("GET", path, fn_name) +#define HTTP_HEAD(path, fn_name) _HTTP_ON("HEAD", path, fn_name) +#define HTTP_POST(path, fn_name) _HTTP_ON("POST", path, fn_name) +#define HTTP_PUT(path, fn_name) _HTTP_ON("PUT", path, fn_name) +#define HTTP_DELETE(path, fn_name) _HTTP_ON("DELETE", path, fn_name) +#define HTTP_CONNECT(path, fn_name) _HTTP_ON("CONNECT", path, fn_name) +#define HTTP_OPTIONS(path, fn_name) _HTTP_ON("OPTIONS", path, fn_name) +#define HTTP_TRACE(path, fn_name) _HTTP_ON("TRACE", path, fn_name) +#define HTTP_PATCH(path, fn_name) _HTTP_ON("PATCH", path, fn_name) + +#define HTTP_DEFAULT(fn_name) \ + static http_resp_t fn_name(http_req_t *req); \ + __attribute__((constructor)) \ + static void __macro_http_fn_register_##fn_name() { \ + http_on_default(&fn_name); \ + } \ + static http_resp_t fn_name(http_req_t *req) + +#define HTTP_MIDDLEWARE_PRE(fn_name) \ + static void fn_name(http_req_t *req); \ + __attribute__((constructor)) \ + static void __macro_http_fn_register_##fn_name() { \ + http_on_pre_route(&fn_name); \ + } \ + static void fn_name(http_req_t *req) + +#define HTTP_MIDDLEWARE_POST(fn_name) \ + static void fn_name(http_req_t *req); \ + __attribute__((constructor)) \ + static void __macro_http_fn_register_##fn_name() { \ + http_on_post_route(&fn_name); \ + } \ + static void fn_name(http_req_t *req) + +http_route_t *_http_routes = NULL; +size_t _http_routes_len = 0; +http_resp_t (*_http_default_route)(http_req_t *); +void (*_http_pre_route)(http_req_t *); +void (*_http_post_route)(http_req_t *); + +http_resp_t _http_on_req(http_t *http, http_req_t *req) { + http_resp_t resp = http_resp_create(HTTP_STATUS_NOT_FOUND); + if (_http_pre_route != NULL) { + _http_pre_route(req); + } + for (size_t i = 0; i < _http_routes_len; i++) { + http_route_t *route = &_http_routes[i]; + http_url_t url = http_url_init(req); + if (strcmp(route->method, req->method) == 0) { + size_t len = strlen(route->path); + char c = route->path[len - 1]; + if (c == '*') { + if (strncmp(route->path, url.path, len - 1) == 0) { + http_resp_free(&resp); + resp = route->fn(req); + } + } + else { + if (strcmp(route->path, url.path) == 0) { + http_resp_free(&resp); + resp = route->fn(req); + } + } + } + http_url_free(&url); + if (resp.status != HTTP_STATUS_NOT_FOUND) { + if (_http_post_route != NULL) { + _http_post_route(req); + } + return resp; + } + } + if (_http_default_route != NULL) { + http_resp_free(&resp); + resp = _http_default_route(req); + } + if (_http_post_route != NULL) { + _http_post_route(req); + } + return resp; +} + http_t *http_init(void) { http_t *http = (http_t *) malloc(sizeof(http_t)); *http = (http_t) { @@ -183,19 +299,160 @@ http_t *http_init(void) { .binds_len = 0, .quit = false, .ssl_ctx = NULL, - .on_req_fn = NULL, + .on_req_fn = &_http_on_req, }; return http; } void http_cleanup(http_t *http) { + if (http->binds != NULL) { + for (size_t i = 0; i < http->binds_len; i++) { + for (size_t j = 0; j < http->binds[i].hosts_len; j++) { + if (http->binds[i].hosts[j].ssl_ctx != NULL) { + SSL_CTX_free(http->binds[i].hosts[j].ssl_ctx); + } + } + } + free(http->binds); + } + if (http->ssl_ctx != NULL) { + SSL_CTX_free(http->ssl_ctx); + } + if (_http_routes != NULL && _http_routes_len > 0) { + free(_http_routes); + } free(http); } -void http_on_req(http_t *http, http_resp_t fn(http_req_t *)) { +void http_on_req(http_t *http, http_resp_t fn(http_t *, http_req_t *)) { http->on_req_fn = fn; } +void http_on_route(char *method, char *path, http_resp_t (*fn)(http_req_t *)) { + _http_routes = realloc(_http_routes, sizeof(http_route_t) * (_http_routes_len + 1)); + _http_routes[_http_routes_len] = (http_route_t) { + .method = method, + .path = path, + .fn = fn, + }; + _http_routes_len++; +} + +void http_on_default(http_resp_t (*fn)(http_req_t *)) { + _http_default_route = fn; +} + +void http_on_pre_route(void (*fn)(http_req_t *)) { + _http_pre_route = fn; +} + +void http_on_post_route(void (*fn)(http_req_t *)) { + _http_post_route = fn; +} + +http_url_t http_url_init(http_req_t *req) { + http_url_t self = (http_url_t) { + .path = NULL, + .queries = NULL, + .queries_len = 0, + }; + char *uri = req->url; + int url_len = strlen(uri); + size_t i = 0; + // read path + for (; i < url_len; i++) { + size_t c = uri[i]; + if (c == '?') { + break; + } + } + size_t len = i; + self.path = malloc(sizeof(char) * (len + 1)); + memcpy(self.path, uri, len); + self.path[len] = '\0'; + i++; + uri += i; + if (i < url_len) { + int j = 0; + url_len = strlen(uri); + int start = j; + int sep = -1; + for (; j <= url_len; j++) { + int c = uri[j]; + if (c == '&' || j == url_len) { + int query_len = j - start; + if (query_len > 0) { + self.queries = realloc(self.queries, sizeof(http_query_t) * (self.queries_len + 1)); + http_query_t *query = &self.queries[self.queries_len]; + query->key = NULL; + query->value = NULL; + // read query key + int key_len = j - start; + if (sep != -1) { + key_len = sep - start; + } + query->key = malloc(sizeof(char) * (key_len + 1)); + memcpy(query->key, uri + start, key_len); + query->key[key_len] = '\0'; + // read query value + if (sep != -1) { + int value_len = i - (sep + 1); + if (value_len > 0) { + query->value = malloc(sizeof(char) * (value_len + 1)); + memcpy(query->value, uri + sep + 1, value_len); + query->value[value_len] = '\0'; + // decode url + // TODO impl full decoding + size_t len = strlen(query->value); + for (size_t k = 0; k < len; k++) { + char c = query->value[k]; + if (c == '+') { + query->value[k] = ' '; + } + } + } + } + self.queries_len++; + } + start = j + 1; + sep = -1; // unset separator + } + if (c == '=') { + sep = j; + } + } + } + return self; +} + +void http_url_free(http_url_t *self) { + if (self) { + free(self->path); + if (self->queries_len > 0) { + for (size_t i = 0; i < self->queries_len; i++) { + http_query_t *query = &self->queries[i]; + free(query->key); + if (query->value) { + free(query->value); + } + } + free(self->queries); + } + } +} + +char *http_url_get(http_url_t *self, char *key) { + if (self && self->queries_len > 0) { + for (size_t i = 0; i < self->queries_len; i++) { + http_query_t *query = &self->queries[i]; + if (strcmp(query->key, key) == 0) { + return query->value; + } + } + } + return NULL; +} + const char *http_status_str(http_status_t status) { switch (status) { case HTTP_STATUS_CONTINUE: @@ -407,6 +664,7 @@ void http_req_free(http_req_t *req) { free(header->value); } } + free(req->headers); } if (req->body != NULL) { free(req->body); @@ -586,7 +844,7 @@ void http_resp_write(http_conn_t *conn, http_resp_t resp) { } } -void *http_client_handle(void *ptr) { +void *_http_client_handle(void *ptr) { if (!ptr) { pthread_exit(0); } @@ -622,7 +880,7 @@ void *http_client_handle(void *ptr) { } if (req.method != NULL && req.url != NULL) { if (conn->http->on_req_fn != NULL) { - http_resp_t resp = conn->http->on_req_fn(&req); + http_resp_t resp = conn->http->on_req_fn(conn->http, &req); http_resp_write(conn, resp); http_resp_free(&resp); } @@ -637,10 +895,10 @@ void *http_client_handle(void *ptr) { pthread_exit(0); } -void *http_server_handle(void *ptr) { +void *_http_server_handle(void *ptr) { http_t *http = (http_t *) ptr; struct pollfd fds[http->binds_len]; - for (int i = 0; i < http->binds_len; i++) { + for (size_t i = 0; i < http->binds_len; i++) { fds[i].fd = http->binds[i].sockfd; fds[i].events = POLLIN | POLLPRI; } @@ -650,7 +908,7 @@ void *http_server_handle(void *ptr) { sleep_req.tv_nsec = HTTP_SERVER_SLEEP_TIME; nanosleep(&sleep_req, &sleep_rem); if (poll(fds, http->binds_len, HTTP_SERVER_POLL_TIME)) { - for (int i = 0; i < http->binds_len; i++) { + for (size_t i = 0; i < http->binds_len; i++) { if (fds[i].revents & POLLIN) { http_conn_t *conn = (http_conn_t *) malloc(sizeof(http_conn_t)); conn->addr_len = sizeof(conn->addr); @@ -666,7 +924,7 @@ void *http_server_handle(void *ptr) { pthread_attr_t thread_attr; pthread_attr_init(&thread_attr); pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); - pthread_create(&thread, &thread_attr, http_client_handle, (void *) conn); + pthread_create(&thread, &thread_attr, _http_client_handle, (void *) conn); pthread_attr_destroy(&thread_attr); } } @@ -676,13 +934,13 @@ void *http_server_handle(void *ptr) { pthread_exit(0); } -int http_tls_sni_callback(SSL *ssl, int *al, void *arg) { +int _http_tls_sni_callback(SSL *ssl, int *al, void *arg) { (void) al; http_conn_t *conn = (http_conn_t *) arg; const char *hostname = SSL_get_servername(ssl, 0); if (hostname != NULL) { - for (int i = 0; i < conn->http->binds_len; i++) { - for (int j = 0; j < conn->http->binds[i].hosts_len; j++) { + for (size_t i = 0; i < conn->http->binds_len; i++) { + for (size_t j = 0; j < conn->http->binds[i].hosts_len; j++) { http_host_t *http_host = &conn->http->binds[i].hosts[j]; if (strcmp(http_host->hostname, hostname) == 0) { conn->curr_host = http_host; @@ -751,24 +1009,16 @@ http_err_t http_listen(http_t *http) { if (!http->ssl_ctx) { return HTTP_ERR_NO_SSL_CONTEXT; } - SSL_CTX_set_tlsext_servername_callback(http->ssl_ctx, http_tls_sni_callback); - pthread_create(&http->thread, 0, http_server_handle, (void *) http); + SSL_CTX_set_tlsext_servername_callback(http->ssl_ctx, _http_tls_sni_callback); + pthread_create(&http->thread, 0, _http_server_handle, (void *) http); return HTTP_ERR_OK; } void http_close(http_t *http) { http->quit = true; pthread_join(http->thread, NULL); - for (int i = 0; i < http->binds_len; i++) { + for (size_t i = 0; i < http->binds_len; i++) { close(http->binds[i].sockfd); - for (int j = 0; j < http->binds[i].hosts_len; j++) { - if (http->binds[i].hosts[j].ssl_ctx != NULL) { - SSL_CTX_free(http->binds[i].hosts[j].ssl_ctx); - } - } - } - if (http->ssl_ctx != NULL) { - free(http->ssl_ctx); } }