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:
M | Makefile | | | 2 | -- |
M | src/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);
}
}