QUOTE: Be someone’s rainbow today.

libhttp

A basic HTTP Framework

commit 60c158311b7d1397269c7c6baa025b64c60000c7
parent 2eff0b30b8b48d376386d0f872991c1192223912
Author: Sophie <info@soophie.de>
Date:   Sun,  6 Apr 2025 15:26:01 +0000

feat: Added queries/params parsing & improved type/function names

Diffstat:
Msrc/libhttp.h | 292+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
1 file changed, 173 insertions(+), 119 deletions(-)

diff --git a/src/libhttp.h b/src/libhttp.h @@ -135,22 +135,25 @@ typedef struct { long body_len; } http_resp_t; +typedef http_resp_t (*http_global_fn_t)(http_t *, http_req_t *); +typedef http_resp_t (*http_handle_fn_t)(http_req_t *); +typedef void (*http_hook_fn_t)(http_req_t *); + typedef struct { char *method; char *path; - http_resp_t (*fn)(http_req_t *); + http_handle_fn_t fn; } http_route_t; typedef struct { char *key; char *value; -} http_query_t; +} http_pair_t; typedef struct { - char *path; - http_query_t *queries; - size_t queries_len; -} http_url_t; + http_pair_t *pairs; + size_t len; +} http_pairs_t; struct http { http_bind_t *binds; @@ -158,23 +161,27 @@ struct http { pthread_t thread; bool quit; SSL_CTX *ssl_ctx; - http_resp_t (*on_req_fn)(http_t *, http_req_t *); + http_global_fn_t fn; }; http_t *http_init(void); void http_cleanup(http_t *http); -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); +void http_on_global(http_t *http, http_global_fn_t fn); +void http_on_route(char *method, char *url, http_handle_fn_t fn); +void http_on_default(http_handle_fn_t fn); +void http_on_pre_route(http_hook_fn_t fn); +void http_on_post_route(http_hook_fn_t fn); +char *http_req_path(http_req_t *req); +char *http_req_body(http_req_t *req); +http_pairs_t http_req_queries(http_req_t *req); +http_pairs_t http_req_params(http_req_t *req); +char *http_pairs_get(http_pairs_t *self, char *key); +bool http_pairs_has(http_pairs_t *self, char *key); +void http_pairs_free(http_pairs_t *self); +char *http_req_header(http_req_t *req, char *key); +void http_resp_header(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_resp_body(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); @@ -186,6 +193,7 @@ void http_close(http_t *http); #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <ctype.h> #include <time.h> #include <unistd.h> #include <pthread.h> @@ -246,35 +254,35 @@ void http_close(http_t *http); 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_handle_fn_t _http_default_route; +http_hook_fn_t _http_pre_route; +http_hook_fn_t _http_post_route; -http_resp_t _http_on_req(http_t *http, http_req_t *req) { +http_resp_t _http_on_global(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); + char *path = http_req_path(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) { + if (strncmp(route->path, path, len - 1) == 0) { http_resp_free(&resp); resp = route->fn(req); } } else { - if (strcmp(route->path, url.path) == 0) { + if (strcmp(route->path, path) == 0) { http_resp_free(&resp); resp = route->fn(req); } } } - http_url_free(&url); + free(path); if (resp.status != HTTP_STATUS_NOT_FOUND) { if (_http_post_route != NULL) { _http_post_route(req); @@ -299,7 +307,7 @@ http_t *http_init(void) { .binds_len = 0, .quit = false, .ssl_ctx = NULL, - .on_req_fn = &_http_on_req, + .fn = &_http_on_global, }; return http; } @@ -324,11 +332,11 @@ void http_cleanup(http_t *http) { free(http); } -void http_on_req(http_t *http, http_resp_t fn(http_t *, http_req_t *)) { - http->on_req_fn = fn; +void http_on_global(http_t *http, http_global_fn_t fn) { + http->fn = fn; } -void http_on_route(char *method, char *path, http_resp_t (*fn)(http_req_t *)) { +void http_on_route(char *method, char *path, http_handle_fn_t fn) { _http_routes = realloc(_http_routes, sizeof(http_route_t) * (_http_routes_len + 1)); _http_routes[_http_routes_len] = (http_route_t) { .method = method, @@ -338,28 +346,22 @@ void http_on_route(char *method, char *path, http_resp_t (*fn)(http_req_t *)) { _http_routes_len++; } -void http_on_default(http_resp_t (*fn)(http_req_t *)) { +void http_on_default(http_handle_fn_t fn) { _http_default_route = fn; } -void http_on_pre_route(void (*fn)(http_req_t *)) { +void http_on_pre_route(http_hook_fn_t fn) { _http_pre_route = fn; } -void http_on_post_route(void (*fn)(http_req_t *)) { +void http_on_post_route(http_hook_fn_t fn) { _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 *http_req_path(http_req_t *req) { char *uri = req->url; - int url_len = strlen(uri); + size_t url_len = strlen(uri); size_t i = 0; - // read path for (; i < url_len; i++) { size_t c = uri[i]; if (c == '?') { @@ -367,93 +369,145 @@ http_url_t http_url_init(http_req_t *req) { } } 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; + char *path = malloc(len + 1); + memcpy(path, uri, len); + path[len] = '\0'; + return path; +} + +char *http_req_body(http_req_t *req) { + return req->body; +} + +char *_http_pair_decode(char *str, size_t len) { + size_t j = 0; + size_t k = 0; + char decoded[len + 1]; + while (j < len) { + if (str[j] == '%') { + if (j + 2 < len && isxdigit(str[j + 1]) && isxdigit(str[j + 2])) { + char hex[3] = { str[j + 1], str[j + 2], '\0' }; + decoded[k++] = (char) strtol(hex, NULL, 16); + j += 3; + continue; } + decoded[k++] = '%'; + j++; + continue; } + if (str[j] == '+') { + decoded[k++] = ' '; + j++; + continue; + } + decoded[k++] = str[j++]; } - return self; + decoded[k] = '\0'; + size_t ret_len = strlen(decoded); + char *ret = malloc(ret_len + 1); + memcpy(ret, decoded, ret_len); + ret[ret_len] = '\0'; + return ret; } -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); +http_pairs_t _http_pairs_init(char *uri) { + http_pairs_t self = (http_pairs_t) { + .pairs = NULL, + .len = 0, + }; + int url_len = strlen(uri); + size_t set = 0; + size_t lim = 0; + for (size_t i = 0; i < url_len + 1; i++) { + char c = uri[i]; + // find 'key=value' pair + if (i == url_len || uri[i] == '&') { + size_t key_len = i - set; + if (key_len > 0) { + if (lim > set) { + key_len = lim - set; + } + self.pairs = realloc(self.pairs, sizeof(http_pair_t) * (self.len + 1)); + http_pair_t *pair = &self.pairs[self.len]; + // read key + pair->key = _http_pair_decode(uri + set, key_len); + pair->value = NULL; + if (i > set + key_len + 1) { + // read value + size_t value_len = i - (set + key_len + 1); + pair->value = _http_pair_decode(uri + set + key_len + 1, value_len); } + self.len++; } - free(self->queries); + set = i + 1; + } + // find '=' delimiter + if (uri[i] == '=') { + lim = i; + } + } + return self; +} + +http_pairs_t http_req_queries(http_req_t *req) { + size_t url_len = strlen(req->url); + // skip path + for (size_t i = 0; i < url_len; i++) { + if (req->url[i] == '?') { + return _http_pairs_init(req->url + i + 1); } } + return (http_pairs_t) { + .pairs = NULL, + .len = 0, + }; } -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; +http_pairs_t http_req_params(http_req_t *req) { + return _http_pairs_init(req->body); +} + +char *http_pairs_get(http_pairs_t *self, char *key) { + if (self && self->len > 0) { + for (size_t i = 0; i < self->len; i++) { + http_pair_t *pair = &self->pairs[i]; + if (strcmp(pair->key, key) == 0) { + return pair->value; } } } return NULL; } -const char *http_status_str(http_status_t status) { +bool http_pairs_has(http_pairs_t *self, char *key) { + if (self && self->len > 0) { + for (size_t i = 0; i < self->len; i++) { + http_pair_t *pair = &self->pairs[i]; + if (strcmp(pair->key, key) == 0) { + return true; + } + } + } + return false; +} + +void http_pairs_free(http_pairs_t *self) { + if (self == NULL) { + return; + } + if (self->len > 0) { + for (size_t i = 0; i < self->len; i++) { + http_pair_t *pair = &self->pairs[i]; + free(pair->key); + if (pair->value) { + free(pair->value); + } + } + free(self->pairs); + } +} + +const char *_http_status_str(http_status_t status) { switch (status) { case HTTP_STATUS_CONTINUE: return "Continue"; @@ -596,7 +650,7 @@ ssize_t http_write(http_conn_t *conn, char *buffer, size_t len) { return write(conn->sockfd, buffer, len); } -char *http_header_get(http_req_t *req, char *key) { +char *http_req_header(http_req_t *req, char *key) { if (req->headers_len > 0) { for (int i = 0; i < req->headers_len; i++) { http_header_t *header = &req->headers[i]; @@ -608,7 +662,7 @@ char *http_header_get(http_req_t *req, char *key) { return NULL; } -void http_header_set(http_resp_t *resp, char *key, char *value) { +void http_resp_header(http_resp_t *resp, char *key, char *value) { resp->headers = (http_header_t *) realloc(resp->headers, sizeof(http_header_t) * (resp->headers_len + 1)); http_header_t *new_header = &resp->headers[resp->headers_len]; resp->headers_len++; @@ -633,14 +687,14 @@ 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_resp_body(http_resp_t *resp, char *body, long len) { if (resp != NULL && body != NULL) { resp->body = (char *) malloc(sizeof(char) * len); memcpy(resp->body, body, len); resp->body_len = len; char content_length[50]; snprintf(content_length, 50, "%ld", len); - http_header_set(resp, (char *) "Content-Length", content_length); + http_resp_header(resp, (char *) "Content-Length", content_length); } } @@ -803,7 +857,7 @@ http_err_t http_read_req(http_conn_t *conn, http_req_t *req) { if (len >= 4) { char *slice = buffer + (len - 4); if (strncmp(slice, "\r\n\r\n", 4) == 0) { - char *content_length = http_header_get(req, (char *) "Content-Length"); + char *content_length = http_req_header(req, (char *) "Content-Length"); if (content_length != NULL) { req->body_len = strtol(content_length, NULL, 10); if (req->body_len > 0) { @@ -826,7 +880,7 @@ void http_resp_write(http_conn_t *conn, http_resp_t resp) { snprintf(status, 4, "%d", resp.status); http_write(conn, status, strlen(status)); http_write(conn, (char *) " ", 1); - const char *status_message = http_status_str(resp.status); + const char *status_message = _http_status_str(resp.status); http_write(conn, (char *) status_message, strlen(status_message)); http_write(conn, (char *) "\r\n", 2); if (resp.headers_len > 0) { @@ -879,8 +933,8 @@ void *_http_client_handle(void *ptr) { pthread_exit(0); } if (req.method != NULL && req.url != NULL) { - if (conn->http->on_req_fn != NULL) { - http_resp_t resp = conn->http->on_req_fn(conn->http, &req); + if (conn->http->fn != NULL) { + http_resp_t resp = conn->http->fn(conn->http, &req); http_resp_write(conn, resp); http_resp_free(&resp); }