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