From 62454373db4cd2add82ded0cf7b21f7d69ade597 Mon Sep 17 00:00:00 2001 From: spl3g Date: Sat, 22 Mar 2025 21:41:16 +0300 Subject: Reorganize the lib --- Makefile | 28 +- README.org | 11 +- examples/main.c | 34 +++ include/chttp/arena.h | 65 +++++ include/chttp/arena_strings.h | 36 +++ include/chttp/const_strings.h | 28 ++ include/chttp/hashmap.h | 27 ++ include/chttp/http.h | 177 +++++++++++++ lib/arena.c | 84 ------ lib/arena.h | 65 ----- lib/arena_strings.c | 53 ---- lib/arena_strings.h | 36 --- lib/const_strings.c | 86 ------- lib/const_strings.h | 28 -- lib/http.c | 586 ------------------------------------------ lib/http.h | 177 ------------- main.c | 34 --- src/arena.c | 84 ++++++ src/arena.o | Bin 0 -> 3592 bytes src/arena_strings.c | 53 ++++ src/arena_strings.o | Bin 0 -> 2544 bytes src/const_strings.c | 86 +++++++ src/const_strings.o | Bin 0 -> 3048 bytes src/hashmap.c | 43 ++++ src/hashmap.o | Bin 0 -> 2296 bytes src/http.c | 586 ++++++++++++++++++++++++++++++++++++++++++ src/http.o | Bin 0 -> 23744 bytes 27 files changed, 1253 insertions(+), 1154 deletions(-) create mode 100644 examples/main.c create mode 100644 include/chttp/arena.h create mode 100644 include/chttp/arena_strings.h create mode 100644 include/chttp/const_strings.h create mode 100644 include/chttp/hashmap.h create mode 100644 include/chttp/http.h delete mode 100644 lib/arena.c delete mode 100644 lib/arena.h delete mode 100644 lib/arena_strings.c delete mode 100644 lib/arena_strings.h delete mode 100644 lib/const_strings.c delete mode 100644 lib/const_strings.h delete mode 100644 lib/http.c delete mode 100644 lib/http.h delete mode 100644 main.c create mode 100644 src/arena.c create mode 100644 src/arena.o create mode 100644 src/arena_strings.c create mode 100644 src/arena_strings.o create mode 100644 src/const_strings.c create mode 100644 src/const_strings.o create mode 100644 src/hashmap.c create mode 100644 src/hashmap.o create mode 100644 src/http.c create mode 100644 src/http.o diff --git a/Makefile b/Makefile index 000af46..543ef5d 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,26 @@ -main: main.c lib - cc -Wall -Wextra -ggdb -o main main.c lib/arena_strings.c lib/http.c lib/arena.c lib/const_strings.c +CC = gcc +AR = ar +CFLAGS = -Wall -Wextra -fPIC -Iinclude/ + +LIBNAME = chttp +SOURCES = $(wildcard src/*.c) +OBJECTS = $(SOURCES:.c=.o) + +all: static + +static: $(OBJECTS) + $(AR) rcs $(LIBNAME).a $(OBJECTS) + +shared: $(OBJECTS) + $(CC) -shared -o $(LIBNAME).so $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +install: all + install -d /usr/local/include/yourlib/ + install include/yourlib/*.h /usr/local/include/yourlib/ + install $(LIBNAME) /usr/local/lib/ + +clean: + rm -f src/*.o *.a *.so diff --git a/README.org b/README.org index 93b3c94..cf7d2cb 100644 --- a/README.org +++ b/README.org @@ -1,7 +1,12 @@ * chttp This is not really a library, but a little project for my education. But if you really want to use it, you can. ** Installation -Just copy the lib directory and use the header files. +#+begin_src shell + make static + # or + make shared +#+end_src +Copy the directory in the =include/= folder to your project. ** Usage *** Initializing the server #+begin_src c @@ -43,8 +48,8 @@ And to wrap things up, we need to run #+end_src *** Full example #+begin_src c - #include "lib/http.h" - #include "lib/const_strings.h" + #include + #include void logging_func(http_middleware *self, http_request req) { http_run_next(self, req); diff --git a/examples/main.c b/examples/main.c new file mode 100644 index 0000000..c9cffb6 --- /dev/null +++ b/examples/main.c @@ -0,0 +1,34 @@ +#include +#include + +void success(http_request req) { + req.resp->code = OK; + req.resp->body = CS("Hello world!\n"); + http_send(req); +} + +void logging_func(http_middleware *self, http_request req) { + http_run_next(self, req); + http_log(HTTP_INFO, CS_FMT" "CS_FMT": %ld\n", CS_ARG(req.method), CS_ARG(req.path), req.resp->code); +} + +void whatever(http_middleware *self, http_request req) { + http_log(HTTP_INFO, "Whatever\n"); + http_run_next(self, req); +} + +int main() { + arena arena = {0}; + http_server serv = {0}; + if (init_server(&arena, &serv, "127.0.0.1", "6969") != 0) { + return 1; + } + + http_register_global_middleware(&serv, logging_func); + http_handler *success_handler = http_handle_path(&serv, "GET", "/", success); + http_register_handler_middleware(&arena, success_handler, whatever); + + listen_and_serve(&serv); + + return 0; +} diff --git a/include/chttp/arena.h b/include/chttp/arena.h new file mode 100644 index 0000000..1f53355 --- /dev/null +++ b/include/chttp/arena.h @@ -0,0 +1,65 @@ +// Most of the code is from https://github.com/tsoding/arena + +#ifndef ARENA_H_ +#define ARENA_H_ + +#include +#include +#include +#include +#include + +typedef struct region region; + +struct region { + region *next; + size_t cap; + size_t len; + uintptr_t data[]; +}; + +typedef struct { + region *beg; + region *end; + size_t region_cap; +} arena; + +arena arena_init_cap(size_t cap); +void *arena_alloc(arena *a, size_t size); +void arena_free(arena *a); +void *arena_realloc(arena *a, void *oldptr, size_t oldsz, size_t newsz); + +#define new(a, t, c) (t *)arena_alloc(a, sizeof(t)*c) + +#define ARENA_REGION_DEFAULT_CAPACITY (8*1024) +#define ARENA_DA_INIT_CAP (2) + +#define arena_da_reserve(a, da, expected_size) \ + do { \ + if ((expected_size) >= (da)->cap) { \ + size_t new_cap = (da)->cap == 0 ? ARENA_DA_INIT_CAP : (da)->cap*2; \ + while (new_cap < expected_size) { \ + new_cap *= 2; \ + } \ + (da)->data = arena_realloc( \ + (a), (da)->data, \ + (da)->cap*sizeof(*(da)->data), \ + new_cap*sizeof(*(da)->data)); \ + (da)->cap = new_cap; \ + } \ + } while (0) + +#define arena_da_append(a, da, item) \ + do { \ + arena_da_reserve((a), (da), (da)->len + 1); \ + (da)->data[(da)->len++] = (item); \ + } while (0) + +#define arena_da_append_many(a, da, items, items_len) \ + do { \ + arena_da_reserve((a), (da), (da)->len+len); \ + memcpy((da)->data+(da)->len, (items), sizeof(*(da)->data)*items_len); \ + (da)->len += len; \ + } while (0) + +#endif //ARENA_H_ diff --git a/include/chttp/arena_strings.h b/include/chttp/arena_strings.h new file mode 100644 index 0000000..b25435d --- /dev/null +++ b/include/chttp/arena_strings.h @@ -0,0 +1,36 @@ +#ifndef ARENA_STRINGS_H_ +#define ARENA_STRINGS_H_ + +#include "arena.h" +#include "const_strings.h" + +typedef struct { + const_string *data; + size_t len; + size_t cap; +} const_string_da; + +typedef struct { + char* data; + size_t len; + size_t cap; +} string_builder; + +const_string arena_cs_append(arena *a, const_string dst, const_string src); +const_string arena_cs_init(arena *a, int len); +const_string arena_cs_concat(arena *a, const_string_da strings, const_string sep); + +#define arena_sb_append_buf(a, sb, buf, len) arena_da_append_many((a), (sb), (buf), (len)) + +#define arena_sb_append_cstr(a, sb, cstr) \ + do { \ + const char * str = (cstr); \ + size_t len = strlen((cstr)); \ + arena_da_append_many((a), (sb), (str), len); \ + } while (0) + +#define arena_sb_append_null(a, sb) arena_da_append_many((a), (sb), "", 1) + +#define arena_sb_to_cs(sb) cs_from_parts((sb).data, (sb).len) + +#endif // ARENA_STRINGS_H_ diff --git a/include/chttp/const_strings.h b/include/chttp/const_strings.h new file mode 100644 index 0000000..cb92442 --- /dev/null +++ b/include/chttp/const_strings.h @@ -0,0 +1,28 @@ +#ifndef CONST_STRINGS_H_ +#define CONST_STRINGS_H_ + +#include +#include +#include + +typedef struct { + const char* data; + int len; +} const_string; + +const_string cs_from_parts(const char* data, int len); +const_string cs_from_cstr(const char* cstr); +const_string cs_slice(const_string src, int from, int to); +const_string cs_chop_delim(const_string *src, char delim); +bool cs_try_chop_delim(const_string *str, char delim, const_string *dst); +int cs_find_delim(const_string str, char delim); +bool cs_eq(const_string str1, const_string str2); +void cs_print(char *format, const_string str); + +#define CS(cstr) cs_from_parts(cstr, sizeof(cstr) - 1) +#define CS_STATIC(cstr) {.data = cstr, .len = sizeof(cstr) - 1} + +#define CS_FMT "%.*s" +#define CS_ARG(cs) (int) (cs).len, (cs).data + +#endif // STRINGS_H_ diff --git a/include/chttp/hashmap.h b/include/chttp/hashmap.h new file mode 100644 index 0000000..eefb5ee --- /dev/null +++ b/include/chttp/hashmap.h @@ -0,0 +1,27 @@ +#ifndef HASHMAP_H_ +#define HASHMAP_H_ + +#include +#include "const_strings.h" +#include "arena.h" + +#define HASHMAP_DEFAULT_R 31 + +typedef struct hashitem hashitem; +struct hashitem { + hashitem *next; + const_string key; + void* val; +}; + +typedef struct { + size_t cap; + hashitem data[]; +} hashmap; + +size_t hash_str(const_string str, size_t cap); +hashmap *hashmap_init(arena *a, size_t cap); +size_t hashmap_kstr_insert(arena *a, hashmap* map, const_string key, void* val); +void *hashmap_kstr_get(hashmap *map, const_string key); + +#endif // HASHMAP_H_ diff --git a/include/chttp/http.h b/include/chttp/http.h new file mode 100644 index 0000000..4fcc418 --- /dev/null +++ b/include/chttp/http.h @@ -0,0 +1,177 @@ +#ifndef HTTP_H_ +#define HTTP_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "const_strings.h" +#include "arena_strings.h" +#include "arena.h" + +typedef struct { + const_string key; + const_string value; +} KV; + +typedef struct { + KV *data; + size_t len; + size_t cap; +} header_da; + +typedef struct { + const_string key; + const_string val; +} http_query; + +typedef struct { + KV *data; + size_t len; + size_t cap; +} http_query_da; + +typedef struct { + const_string key; + void *val; +} http_context_value; + +typedef struct { + http_context_value *data; + size_t len; + size_t cap; +} http_context; + +typedef struct { + size_t code; + header_da headers; + const_string body; + bool sent; +} http_response; + +typedef struct { + size_t inc_fd; + const_string method; + const_string path; + http_query_da query; + header_da headers; + const_string body; + + arena *arena; + http_response* resp; + http_context *ctx; +} http_request; + +typedef void (*http_handler_func)(http_request); + +typedef struct http_handler_da http_handler_da; +typedef struct http_middleware http_middleware; +typedef struct http_handler http_handler; + +typedef void (*http_middleware_func)(http_middleware *, http_request); + +struct http_middleware { + http_middleware_func func; + http_middleware *next; + http_handler_func handler; +}; + +struct http_handler { + const_string method; + const_string path; + http_middleware *middleware; + http_handler_func func; +}; + +struct http_handler_da { + http_handler *data; + size_t len; + size_t cap; +}; + +typedef struct { + http_middleware *start; + http_middleware *end; +} http_global_middleware; + +typedef struct { + size_t sockfd; + struct sockaddr *addr; + arena *arena; + http_handler_da handlers; + http_context global_ctx; + http_global_middleware *global_middleware; +} http_server; + +typedef enum { + HTTP_INFO = 0, + HTTP_WARNING, + HTTP_ERROR, +} http_log_level; + +typedef enum { + CONTINUE = 100, + SWITCHING_PROTOCOLS = 101, + PROCESSING = 102, + EARLY_HINTS = 103, + OK = 200, + CREATED = 201, + ACCEPTED = 202, + NO_CONTENT = 204, + MULTIPLE_CHOICES = 300, + MOVED_PERMANENTLY = 301, + FOUND = 302, + SEE_OTHER = 303, + NOT_MODIFIED = 304, + TEMPORARY_REDIRECT = 307, + PERMANENT_REDIRECT = 308, + BAD_REQUEST = 400, + UNAUTHORIZED = 401, + PAYMENT_REQUIRED = 402, + FORBIDDEN = 403, + NOT_FOUND = 404, + METHOD_NOT_ALLOWED = 405, + NOT_ACCEPTABLE = 406, + REQUEST_TIMEOUT = 408, + IM_A_TEEPOT = 418, + INTERNAL_SERVER_ERROR = 500, + NOT_IMPLEMENTED = 501, + BAD_GATEWAY = 502, + SERVICE_UNAVAILABLE = 503, + HTTP_VERSION_NOT_SUPPPORTED = 505, +} http_response_code; + +const_string get_response_string(http_response_code code); +void *get_in_addr(struct sockaddr *sa); +int get_in_port(struct sockaddr *sa); +void http_log(http_log_level log, char *fmt, ...); + +void set_context_value(arena *arena, http_context *ctx, http_context_value kv); +void *get_context_value(http_context *ctx, char* key); + +void http_send(http_request req); +void http_send_json(http_request req, const_string json); +void http_send_body(http_request req, const_string body); + +int init_server(arena *arena, http_server *serv, char *addr, char *port); +int listen_and_serve(http_server *serv); + +http_handler *http_handle_path(http_server *serv, char *method, char *path, http_handler_func handler); + +void http_run_next(http_middleware *self, http_request req); +void http_register_global_middleware(http_server *hand, http_middleware_func func); +void http_register_handler_middleware(arena *arena, http_handler *hand, http_middleware_func func); + +#endif // HTTP_H_ diff --git a/lib/arena.c b/lib/arena.c deleted file mode 100644 index 8cd1748..0000000 --- a/lib/arena.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "arena.h" - -region *new_region(size_t cap) { - size_t size_bytes = sizeof(region) + sizeof(uintptr_t)*cap; - region *r = (region *)malloc(size_bytes); - assert(r); - r->next = NULL; - r->len = 0; - r->cap = cap; - return r; -} - -arena arena_init_cap(size_t cap) { - arena a = {0}; - a.region_cap = cap; - return a; -} - -void *arena_alloc(arena *a, size_t size_bytes) { - size_t size = (size_bytes + sizeof(uintptr_t)-1)/sizeof(uintptr_t); - - if (a->end == NULL) { - assert(a->beg == NULL); - size_t capacity = a->region_cap ? a->region_cap : ARENA_REGION_DEFAULT_CAPACITY; - if (capacity < size) capacity = size; - a->beg = new_region(capacity); - a->end = a->beg; - } - - while (a->end->len + size < a->end->cap && a->end->next != NULL) { - a->end = a->end->next; - } - - if (a->end->len + size > a->end->cap) { - size_t capacity = a->region_cap ? a->region_cap : ARENA_REGION_DEFAULT_CAPACITY; - if (capacity < size) capacity = size; - a->beg = new_region(capacity); - a->end->next = new_region(capacity); - a->end = a->end->next; - } - - void *p = &a->end->data[a->end->len]; - a->end->len += size; - return memset(p, 0, size); -} - -void arena_free(arena *a) { - region *r = a->beg; - while (r != NULL) { - region *r0 = r; - r = r0->next; - free(r0); - } -} - -void *arena_realloc(arena *a, void *oldptr, size_t oldsz, size_t newsz) { - if (newsz <= oldsz) return oldptr; - void *newptr = arena_alloc(a, newsz); - char *newptr_char = (char*)newptr; - char *oldptr_char = (char*)oldptr; - for (size_t i = 0; i < oldsz; ++i) { - newptr_char[i] = oldptr_char[i]; - } - return newptr; -} - -void grow_da(void *slice, size_t size, arena *arena) { - struct { - void *data; - size_t len; - size_t cap; - } replica; - memcpy(&replica, slice, sizeof(replica)); - - replica.cap = replica.cap ? replica.cap : 1; - void *data = arena_alloc(arena, size*2*replica.cap); - replica.cap *= 2; - if (replica.len) { - memcpy(data, replica.data, size*replica.len); - } - replica.data = data; - - memcpy(slice, &replica, sizeof(replica)); -} diff --git a/lib/arena.h b/lib/arena.h deleted file mode 100644 index 1f53355..0000000 --- a/lib/arena.h +++ /dev/null @@ -1,65 +0,0 @@ -// Most of the code is from https://github.com/tsoding/arena - -#ifndef ARENA_H_ -#define ARENA_H_ - -#include -#include -#include -#include -#include - -typedef struct region region; - -struct region { - region *next; - size_t cap; - size_t len; - uintptr_t data[]; -}; - -typedef struct { - region *beg; - region *end; - size_t region_cap; -} arena; - -arena arena_init_cap(size_t cap); -void *arena_alloc(arena *a, size_t size); -void arena_free(arena *a); -void *arena_realloc(arena *a, void *oldptr, size_t oldsz, size_t newsz); - -#define new(a, t, c) (t *)arena_alloc(a, sizeof(t)*c) - -#define ARENA_REGION_DEFAULT_CAPACITY (8*1024) -#define ARENA_DA_INIT_CAP (2) - -#define arena_da_reserve(a, da, expected_size) \ - do { \ - if ((expected_size) >= (da)->cap) { \ - size_t new_cap = (da)->cap == 0 ? ARENA_DA_INIT_CAP : (da)->cap*2; \ - while (new_cap < expected_size) { \ - new_cap *= 2; \ - } \ - (da)->data = arena_realloc( \ - (a), (da)->data, \ - (da)->cap*sizeof(*(da)->data), \ - new_cap*sizeof(*(da)->data)); \ - (da)->cap = new_cap; \ - } \ - } while (0) - -#define arena_da_append(a, da, item) \ - do { \ - arena_da_reserve((a), (da), (da)->len + 1); \ - (da)->data[(da)->len++] = (item); \ - } while (0) - -#define arena_da_append_many(a, da, items, items_len) \ - do { \ - arena_da_reserve((a), (da), (da)->len+len); \ - memcpy((da)->data+(da)->len, (items), sizeof(*(da)->data)*items_len); \ - (da)->len += len; \ - } while (0) - -#endif //ARENA_H_ diff --git a/lib/arena_strings.c b/lib/arena_strings.c deleted file mode 100644 index 3497c2e..0000000 --- a/lib/arena_strings.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "arena_strings.h" - -const_string arena_cs_init(arena *a, int len) { - char *str = new(a, char, len+1); - const_string cs = {0}; - cs.data = str; - cs.len = len; - return cs; -} - -const_string arena_cs_append(arena *a, const_string dst, const_string src) { - char *str = new(a, char, dst.len+src.len + 1); - memcpy(str, dst.data, dst.len); - memcpy(str + dst.len, src.data, src.len + 1); - const_string cs = {0}; - cs.data = str; - cs.len = dst.len + src.len; - return cs; -} - -const_string arena_cs_concat(arena *a, const_string_da strings, const_string sep) { - if (strings.len == 0) { - return CS(""); - } else if (strings.len == 0) { - return *strings.data; - } - - int len = sep.len * (strings.len - 1); - for (size_t i = 0; i < strings.len; i++) { - len += (strings.data + i)->len; - }; - - char* str = arena_alloc(a, len); - int offset = 0; - - for (size_t i = 0; i < strings.len; i++) { - int curr_strlen = (strings.data + i)->len; - memcpy(str + offset, - (strings.data + i)->data, - curr_strlen); - - offset += curr_strlen; - - if (i != strings.len - 1) { - memcpy(str + offset, - sep.data, sep.len); - offset += sep.len; - } - }; - *(str + len + 1) = '\0'; - - return (const_string){str, len}; -}; diff --git a/lib/arena_strings.h b/lib/arena_strings.h deleted file mode 100644 index b25435d..0000000 --- a/lib/arena_strings.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef ARENA_STRINGS_H_ -#define ARENA_STRINGS_H_ - -#include "arena.h" -#include "const_strings.h" - -typedef struct { - const_string *data; - size_t len; - size_t cap; -} const_string_da; - -typedef struct { - char* data; - size_t len; - size_t cap; -} string_builder; - -const_string arena_cs_append(arena *a, const_string dst, const_string src); -const_string arena_cs_init(arena *a, int len); -const_string arena_cs_concat(arena *a, const_string_da strings, const_string sep); - -#define arena_sb_append_buf(a, sb, buf, len) arena_da_append_many((a), (sb), (buf), (len)) - -#define arena_sb_append_cstr(a, sb, cstr) \ - do { \ - const char * str = (cstr); \ - size_t len = strlen((cstr)); \ - arena_da_append_many((a), (sb), (str), len); \ - } while (0) - -#define arena_sb_append_null(a, sb) arena_da_append_many((a), (sb), "", 1) - -#define arena_sb_to_cs(sb) cs_from_parts((sb).data, (sb).len) - -#endif // ARENA_STRINGS_H_ diff --git a/lib/const_strings.c b/lib/const_strings.c deleted file mode 100644 index 2d743b1..0000000 --- a/lib/const_strings.c +++ /dev/null @@ -1,86 +0,0 @@ -#include "const_strings.h" - -const_string cs_from_parts(const char* data, int len) { - const_string str; - str.data = data; - str.len = len; - - return str; -} - -const_string cs_from_cstr(const char* cstr) { - return cs_from_parts(cstr, strlen(cstr)); -} - -const_string cs_slice(const_string src, int from, int to) { - if (from > src.len) { - return src; - } - - int new_from; - if (from <= 0) { - new_from = 1; - } - new_from -= 1; - - int new_len; - if (to > src.len) { - new_len = src.len; - } else { - new_len = to; - } - - return cs_from_parts(src.data + new_from, new_len); -} - -const_string cs_chop_delim(const_string *str, char delim) { - int n; - for (n = 0; n < str->len && str->data[n] != delim; n++) {} - - const_string result = cs_from_parts(str->data, n); - - if (n < str->len) { - str->len -= n + 1; - str->data += n + 1; - } else { - str->len -= n; - str->data += n; - } - - return result; -} - -bool cs_try_chop_delim(const_string *str, char delim, const_string *dst) { - const_string str_copy = *str; - const_string tmp = cs_chop_delim(&str_copy, delim); - if (str_copy.len == 0) { - return false; - } - *str = str_copy; - *dst = tmp; - return true; -} - -int cs_find_delim(const_string str, char delim) { - int n; - for (n = 0; n < str.len && str.data[n] != delim; n++) {} - - if (n < str.len) { - return n; - } - return -1; -} - -bool cs_eq(const_string str1, const_string str2) { - if (str1.len != str2.len) { - return false; - } - return memcmp(str1.data, str2.data, str1.len) == 0; -} - -void cs_print(char *format, const_string str) { - char buf[str.len + 1]; - memcpy(buf, str.data, str.len); - buf[str.len] = '\0'; - printf(format, buf); -} diff --git a/lib/const_strings.h b/lib/const_strings.h deleted file mode 100644 index cb92442..0000000 --- a/lib/const_strings.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef CONST_STRINGS_H_ -#define CONST_STRINGS_H_ - -#include -#include -#include - -typedef struct { - const char* data; - int len; -} const_string; - -const_string cs_from_parts(const char* data, int len); -const_string cs_from_cstr(const char* cstr); -const_string cs_slice(const_string src, int from, int to); -const_string cs_chop_delim(const_string *src, char delim); -bool cs_try_chop_delim(const_string *str, char delim, const_string *dst); -int cs_find_delim(const_string str, char delim); -bool cs_eq(const_string str1, const_string str2); -void cs_print(char *format, const_string str); - -#define CS(cstr) cs_from_parts(cstr, sizeof(cstr) - 1) -#define CS_STATIC(cstr) {.data = cstr, .len = sizeof(cstr) - 1} - -#define CS_FMT "%.*s" -#define CS_ARG(cs) (int) (cs).len, (cs).data - -#endif // STRINGS_H_ diff --git a/lib/http.c b/lib/http.c deleted file mode 100644 index 64b357b..0000000 --- a/lib/http.c +++ /dev/null @@ -1,586 +0,0 @@ -#include "http.h" - -#define MAX_DATA_SIZE 256 -#define BUFFER_SIZE 4096 -#define REQUEST_ARENA_SIZE 1024 - -int volatile keepRunning = 1; - -struct { - http_response_code code; - const_string string; -} response_strings [] = { - {100, CS_STATIC("Continue")}, - {101, CS_STATIC("Switching Protocols")}, - {102, CS_STATIC("Processing")}, - {103, CS_STATIC("Early Hints")}, - {200, CS_STATIC("OK")}, - {201, CS_STATIC("Created")}, - {202, CS_STATIC("Accepted")}, - {204, CS_STATIC("No Content")}, - {300, CS_STATIC("Multiple Choices")}, - {301, CS_STATIC("Moved Permanently")}, - {302, CS_STATIC("Found")}, - {303, CS_STATIC("See Other")}, - {304, CS_STATIC("Not Modified")}, - {307, CS_STATIC("Temporary Redirect")}, - {308, CS_STATIC("Permanent Redirect")}, - {400, CS_STATIC("Bad Request")}, - {401, CS_STATIC("Unauthorized")}, - {402, CS_STATIC("Payment Required")}, - {403, CS_STATIC("Forbidden")}, - {404, CS_STATIC("Not Found")}, - {405, CS_STATIC("Method Not Allowed")}, - {406, CS_STATIC("Not Acceptable")}, - {408, CS_STATIC("Request Timeout")}, - {418, CS_STATIC("Im A Teepot")}, - {500, CS_STATIC("Internal Server Error")}, - {501, CS_STATIC("Not Implemented")}, - {502, CS_STATIC("Bad Gateway")}, - {503, CS_STATIC("Service Unavailable")}, - {505, CS_STATIC("Http Version Not Suppported")}, -}; - -const_string get_response_string(http_response_code code) { - for (int i = 0; sizeof(response_strings)/sizeof(response_strings[0]) ;i++) { - if (response_strings[i].code == code) { - return response_strings[i].string; - } - } - return CS(""); -} - - -void sigchld_handler() { - // waitpid() might overwrite errno, so we save and restore it: - int saved_errno = errno; - - while(waitpid(-1, NULL, WNOHANG) > 0); - - errno = saved_errno; -} - -void http_log(http_log_level log, char *fmt, ...) { - switch (log) { - case HTTP_INFO: - fprintf(stderr, "[INFO] "); - break; - - case HTTP_WARNING: - fprintf(stderr, "[WARNING] "); - break; - - case HTTP_ERROR: - fprintf(stderr, "[ERROR] "); - break; - } - - va_list args; - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); -} - - -void *get_in_addr(struct sockaddr *sa) { - if (sa->sa_family == AF_INET) { - return &(((struct sockaddr_in*)sa)->sin_addr); - } - - return &(((struct sockaddr_in6*)sa)->sin6_addr); -} - -int get_in_port(struct sockaddr *sa) { - if (sa->sa_family == AF_INET) { - return ntohs((((struct sockaddr_in*)sa)->sin_port)); - } - - return (((struct sockaddr_in6*)sa)->sin6_port); -} - -void set_context_value(arena *arena, http_context *ctx, http_context_value kv) { - for (size_t i = 0; i < ctx->len; i++) { - http_context_value *ctx_val = (ctx->data + i); - if (cs_eq(kv.key, ctx_val->key)) { - ctx_val->val = kv.val; - return; - } - } - arena_da_append(arena, ctx, kv); -} - -void *get_context_value(http_context *ctx, char* key) { - if (ctx == NULL) { - return NULL; - } - - const_string key_cs = cs_from_cstr(key); - for (size_t i = 0; i < ctx->len; i++) { - http_context_value *ctx_val = (ctx->data + i); - if (cs_eq(key_cs, ctx_val->key)) { - return ctx_val->val; - } - } - return NULL; -} - -void http_internal_server_error_handler(http_request req) { - const_string *error = get_context_value(req.ctx, "error"); - req.resp->code = INTERNAL_SERVER_ERROR; - req.resp->body = *error; - http_send(req); -} - -void http_not_found_handler(http_request req) { - req.resp->code = NOT_FOUND; - http_send(req); -} - -void http_run_next(http_middleware *self, http_request req) { - if (self->next == NULL) { - (self->handler)(req); - return; - } - - (self->next->func)(self->next, req); -}; - -void http_register_handler_middleware(arena *arena, http_handler *hand, http_middleware_func func) { - http_middleware *middleware = arena_alloc(arena, sizeof(http_middleware)); - middleware->func = func; - middleware->handler = hand->func; - if (hand->middleware == NULL) { - hand->middleware = middleware; - return; - } - - http_middleware **prev = &hand->middleware; - http_middleware *next = hand->middleware; - while (next->next != NULL) { - prev = &next; - next = next->next; - } - middleware->next = next; - *prev = middleware; -} - -void http_register_global_middleware(http_server *serv, http_middleware_func func) { - arena *arena = serv->arena; - - http_middleware *middleware = arena_alloc(arena, sizeof(http_middleware)); - http_handler_func *handler = arena_alloc(arena, sizeof(http_handler_func)); - middleware->func = func; - middleware->handler = *handler; - if (serv->global_middleware == NULL) { - http_global_middleware *global_middleware = arena_alloc(arena, sizeof(http_global_middleware)); - global_middleware->start = middleware; - global_middleware->end = middleware; - serv->global_middleware = global_middleware; - return; - } - - serv->global_middleware->end->next = middleware; - serv->global_middleware->end = middleware; -} - -int init_server(arena *arena, http_server *serv, char *addr, char *port) { - // serv initialization - struct addrinfo hints, *res; - - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; - - int status = getaddrinfo(addr, port, &hints, &res); - if (status != 0) { - fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); - return 1; - } - - int sockfd = socket(res->ai_family, res->ai_socktype, 0); - if (sockfd < 0) { - perror("socket"); - return 1; - } - - int yes = 1; - if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) { - perror("setsockopt"); - return 1; - } - - if (bind(sockfd, res->ai_addr, res->ai_addrlen)) { - perror("bind"); - return 1; - } - - serv->arena = arena; - serv->addr = (struct sockaddr *)res->ai_addr; - serv->sockfd = sockfd; - - freeaddrinfo(res); - - struct sigaction sa; - sa.sa_handler = sigchld_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - if (sigaction(SIGCHLD, &sa, NULL) == -1) { - perror("sigaction"); - return 1; - } - - return 0; -} - -http_handler *http_handle_path(http_server *serv, char* method, char* path, http_handler_func handler) { - http_handler hand = { - .method = cs_from_cstr(method), - .path = cs_from_cstr(path), - .func = handler, - }; - - arena_da_append(serv->arena, &serv->handlers, hand); - return &serv->handlers.data[serv->handlers.len-1]; -} - -const_string http_compose_response(arena *arena, http_response *resp) { - const_string_da header_da = {0}; - arena_da_append(arena, &header_da, CS("HTTP/1.1")); - - if (resp->code != 0) { - char buf[4]; - sprintf(buf, "%ld", resp->code); - arena_da_append(arena, &header_da, cs_from_cstr(buf)); - arena_da_append(arena, &header_da, get_response_string(resp->code)); - } else { - printf("Response is handeled, but no code is given\n"); - arena_da_append(arena, &header_da, CS("500")); - arena_da_append(arena, &header_da, get_response_string(INTERNAL_SERVER_ERROR)); - } - - const_string_da resp_da= {0}; - - const_string http_header = arena_cs_concat(arena, header_da, CS(" ")); - arena_da_append(arena, &resp_da, http_header); - - for (size_t i = 0; i < resp->headers.len; i++) { - KV *header = (resp->headers.data + i); - const_string header_str = {0}; - header_str = arena_cs_append(arena, header_str, header->key); - header_str = arena_cs_append(arena, header_str, CS(": ")); - header_str = arena_cs_append(arena, header_str, header->value); - arena_da_append(arena, &resp_da, header_str); - } - - arena_da_append(arena, &resp_da, CS("")); - if (resp->body.len != 0) { - arena_da_append(arena, &resp_da, resp->body); - } - - const_string str = arena_cs_concat(arena, resp_da, CS("\r\n")); - - return str; -} - - -void http_send_response(size_t inc_fd, const_string resp) { - const char *resp_cstr = resp.data; - int resp_len = resp.len; - - int sent = send(inc_fd, resp_cstr, resp_len, 0); - while (sent > 0 && resp_len - sent > 0) { - resp_len -= sent; - sent = send(inc_fd, resp_cstr, resp_len, 0); - } - - if (sent < 0) { - perror("send"); - } -} - -void http_send(http_request req) { - arena *arena = req.arena; - - assert(req.resp->code); - const_string response = http_compose_response(arena, req.resp); - http_send_response(req.inc_fd, response); - req.resp->sent = true; -} - -void http_send_json(http_request req, const_string json) { - arena *arena = req.arena; - - int num_str_len = snprintf(NULL, 0, "%d", json.len); - char* len_str = arena_alloc(arena, snprintf(NULL, 0, "%d", json.len)); - sprintf(len_str, "%d", json.len); - const_string len_cs = {len_str, num_str_len}; - - arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Length"), len_cs})); - arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Type"), CS("application/json")})); - req.resp->body = json; - - http_send(req); -} -void http_send_body(http_request req, const_string body) { - arena *arena = req.arena; - - int num_str_len = snprintf(NULL, 0, "%d", body.len+2); - char* len_str = arena_alloc(arena, snprintf(NULL, 0, "%d", body.len)); - sprintf(len_str, "%d", body.len); - const_string len_cs = {len_str, num_str_len}; - - arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Length"), len_cs})); - arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Type"), CS("application/text")})); - req.resp->body = body; - - http_send(req); -} - -int chop_query(arena *arena, const_string *path, http_query_da *queries) { - const_string queries_str = *path; - int query_count = 0; - - if (!cs_try_chop_delim(&queries_str, '?', path)) { - return 0; - } - - const_string query_str; - - if (!cs_try_chop_delim(&queries_str, '&', &query_str)) { - query_str = queries_str; - } - - do { - KV query = {0}; - const_string key; - if (!cs_try_chop_delim(&query_str, '=', &key)) { - return -1; - } - - query.key = key; - query.value = query_str; - arena_da_append(arena, queries, query); - query_count++; - } while (cs_try_chop_delim(&queries_str, '&', &query_str)); - - return query_count; -} - -const_string chop_request(const_string *req_str, char delim, bool *parse_err) { - if (*parse_err) { - return CS(""); - } - const_string str; - if (!cs_try_chop_delim(req_str, delim, &str)) { - *parse_err = true; - return CS(""); - } - return str; -} - -int parse_request(arena *arena, size_t inc_fd, http_request *req) { - char buf[MAX_DATA_SIZE]; - const_string req_str = {0}; - - int got = recv(inc_fd, &buf, MAX_DATA_SIZE - 1, 0); - if (got < 0) { - perror("recv"); - return REQUEST_TIMEOUT; - } - - buf[got] = '\0'; - - req_str = arena_cs_append(arena, req_str, cs_from_cstr(buf)); - - while (got == MAX_DATA_SIZE - 1) { - got = recv(inc_fd, &buf, MAX_DATA_SIZE - 1, 0); - if (got < 0) { - perror("recv"); - return REQUEST_TIMEOUT; - } - - buf[got] = '\0'; - - const_string tmp = cs_from_parts(buf, got); - req_str = arena_cs_append(arena, req_str, tmp); - } - - bool parse_err = false; - - const_string method = chop_request(&req_str, ' ', &parse_err); - const_string path = chop_request(&req_str, ' ', &parse_err); - chop_request(&req_str, '\r', &parse_err); - req_str.data += 1; - - header_da headers = {0}; - while (!parse_err && req_str.data[0] != '\r') { - const_string header_str = chop_request(&req_str, '\r', &parse_err); - req_str.data += 1; - - KV header; - header.key = chop_request(&header_str, ':', &parse_err); - header_str.data++; - header.value = header_str; - - arena_da_append(arena, &headers, header); - } - chop_request(&req_str, '\n', &parse_err); - - if (parse_err) { - return BAD_REQUEST; - } - - http_query_da queries = {0}; - - if (chop_query(arena, &path, &queries) < 0) { - return BAD_REQUEST; - } - - - req->method = method; - req->path = path; - req->query = queries; - req->headers = headers; - req->body = req_str; - - return 0; -} - -bool check_handler(http_handler hand, http_request req) { - arena *arena = req.arena; - http_context *ctx = req.ctx; - - const_string handler_path = hand.path; - const_string req_path = req.path; - if (!(cs_eq(hand.method, req.method) || - (cs_eq(hand.method, CS("GET")) && cs_eq(req.method, CS("HEAD"))))) { - return false; - } - - const_string path; - bool entered = false; - - while (cs_try_chop_delim(&handler_path, ':', &path)) { - entered = true; - - const_string req_byte = {req_path.data, path.len}; - if (!cs_eq(path, req_byte)) { - return false; - } - - req_path.data += path.len; - req_path.len -= path.len; - - const_string name = cs_chop_delim(&handler_path, '/'); - const_string tmp = cs_chop_delim(&req_path, '/'); - const_string *value = arena_alloc(arena, sizeof(const_string)); - *value = tmp; - set_context_value(arena, ctx, (http_context_value){name, (void *)value}); - } - - if (!entered && !cs_eq(handler_path, req_path)) { - return false; - } - - return true; -} - -struct process_request_args { - http_server serv; - size_t inc_fd; -}; - -void *process_request(void *args) { - struct process_request_args *pargs = args; - http_server serv = pargs->serv; - size_t inc_fd = pargs->inc_fd; - - arena req_arena = {0}; - - http_request req = {0}; - req.inc_fd = inc_fd; - req.arena = &req_arena; - - http_response resp = {0}; - req.resp = &resp; - - int parse_res = parse_request(&req_arena, inc_fd, &req); - if (parse_res != 0) { - resp.code = parse_res; - http_send(req); - } - - http_context req_context = {0}; - for (size_t i = 0; i < serv.global_ctx.len; i++) { - set_context_value(&req_arena, &req_context, *(serv.global_ctx.data + i)); - } - req.ctx = &req_context; - - http_global_middleware *g_mid = serv.global_middleware; - if (!resp.sent) { - for (size_t i = 0; i < serv.handlers.len; i++) { - http_handler handler = serv.handlers.data[i]; - if (check_handler(handler, req)) { - if (serv.global_middleware != NULL) { - g_mid->end->next = handler.middleware; - g_mid->end->handler = handler.func; - g_mid->start->func(g_mid->start, req); - } else if (handler.middleware != NULL) { - handler.middleware->func(handler.middleware, req); - } else { - handler.func(req); - } - - if (!resp.sent) { - const_string error = CS("Server handled the request but did not send anything\n"); - http_log(HTTP_ERROR, "%s", error.data); - set_context_value(&req_arena, &req_context, (http_context_value){CS("error"), (void *)&error}); - g_mid->end->handler = http_internal_server_error_handler; - g_mid->start->func(g_mid->start, req); - } - break; - } - } - } - - if (!resp.code) { - g_mid->end->handler = http_not_found_handler; - g_mid->start->func(g_mid->start, req); - } - - close(inc_fd); - arena_free(&req_arena); - - pthread_exit(NULL); -} - -int listen_and_serve(http_server *serv) { - if (listen(serv->sockfd, 10) != 0) { - perror("listen"); - return 1; - } - - char my_ipstr[INET6_ADDRSTRLEN]; - inet_ntop(serv->addr->sa_family, get_in_addr(serv->addr), my_ipstr, sizeof my_ipstr); - http_log(HTTP_INFO, "Listening on %s:%d\n", my_ipstr, get_in_port(serv->addr)); - - while (keepRunning) { - struct sockaddr inc_addr; - socklen_t addr_size = sizeof inc_addr; - int inc_fd = accept(serv->sockfd, &inc_addr, &addr_size); - if (inc_fd < 0) { - perror("accept"); - return 1; - } - - struct process_request_args args = { - .serv = *serv, - .inc_fd = inc_fd, - }; - - pthread_t thread_id; - pthread_create(&thread_id, NULL, process_request, (void *)&args); - } - return 0; -} diff --git a/lib/http.h b/lib/http.h deleted file mode 100644 index 4fcc418..0000000 --- a/lib/http.h +++ /dev/null @@ -1,177 +0,0 @@ -#ifndef HTTP_H_ -#define HTTP_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "const_strings.h" -#include "arena_strings.h" -#include "arena.h" - -typedef struct { - const_string key; - const_string value; -} KV; - -typedef struct { - KV *data; - size_t len; - size_t cap; -} header_da; - -typedef struct { - const_string key; - const_string val; -} http_query; - -typedef struct { - KV *data; - size_t len; - size_t cap; -} http_query_da; - -typedef struct { - const_string key; - void *val; -} http_context_value; - -typedef struct { - http_context_value *data; - size_t len; - size_t cap; -} http_context; - -typedef struct { - size_t code; - header_da headers; - const_string body; - bool sent; -} http_response; - -typedef struct { - size_t inc_fd; - const_string method; - const_string path; - http_query_da query; - header_da headers; - const_string body; - - arena *arena; - http_response* resp; - http_context *ctx; -} http_request; - -typedef void (*http_handler_func)(http_request); - -typedef struct http_handler_da http_handler_da; -typedef struct http_middleware http_middleware; -typedef struct http_handler http_handler; - -typedef void (*http_middleware_func)(http_middleware *, http_request); - -struct http_middleware { - http_middleware_func func; - http_middleware *next; - http_handler_func handler; -}; - -struct http_handler { - const_string method; - const_string path; - http_middleware *middleware; - http_handler_func func; -}; - -struct http_handler_da { - http_handler *data; - size_t len; - size_t cap; -}; - -typedef struct { - http_middleware *start; - http_middleware *end; -} http_global_middleware; - -typedef struct { - size_t sockfd; - struct sockaddr *addr; - arena *arena; - http_handler_da handlers; - http_context global_ctx; - http_global_middleware *global_middleware; -} http_server; - -typedef enum { - HTTP_INFO = 0, - HTTP_WARNING, - HTTP_ERROR, -} http_log_level; - -typedef enum { - CONTINUE = 100, - SWITCHING_PROTOCOLS = 101, - PROCESSING = 102, - EARLY_HINTS = 103, - OK = 200, - CREATED = 201, - ACCEPTED = 202, - NO_CONTENT = 204, - MULTIPLE_CHOICES = 300, - MOVED_PERMANENTLY = 301, - FOUND = 302, - SEE_OTHER = 303, - NOT_MODIFIED = 304, - TEMPORARY_REDIRECT = 307, - PERMANENT_REDIRECT = 308, - BAD_REQUEST = 400, - UNAUTHORIZED = 401, - PAYMENT_REQUIRED = 402, - FORBIDDEN = 403, - NOT_FOUND = 404, - METHOD_NOT_ALLOWED = 405, - NOT_ACCEPTABLE = 406, - REQUEST_TIMEOUT = 408, - IM_A_TEEPOT = 418, - INTERNAL_SERVER_ERROR = 500, - NOT_IMPLEMENTED = 501, - BAD_GATEWAY = 502, - SERVICE_UNAVAILABLE = 503, - HTTP_VERSION_NOT_SUPPPORTED = 505, -} http_response_code; - -const_string get_response_string(http_response_code code); -void *get_in_addr(struct sockaddr *sa); -int get_in_port(struct sockaddr *sa); -void http_log(http_log_level log, char *fmt, ...); - -void set_context_value(arena *arena, http_context *ctx, http_context_value kv); -void *get_context_value(http_context *ctx, char* key); - -void http_send(http_request req); -void http_send_json(http_request req, const_string json); -void http_send_body(http_request req, const_string body); - -int init_server(arena *arena, http_server *serv, char *addr, char *port); -int listen_and_serve(http_server *serv); - -http_handler *http_handle_path(http_server *serv, char *method, char *path, http_handler_func handler); - -void http_run_next(http_middleware *self, http_request req); -void http_register_global_middleware(http_server *hand, http_middleware_func func); -void http_register_handler_middleware(arena *arena, http_handler *hand, http_middleware_func func); - -#endif // HTTP_H_ diff --git a/main.c b/main.c deleted file mode 100644 index 019597b..0000000 --- a/main.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "lib/const_strings.h" -#include "lib/http.h" - -void success(http_request req) { - req.resp->code = OK; - req.resp->body = CS("Hello world!\n"); - http_send(req); -} - -void logging_func(http_middleware *self, http_request req) { - http_run_next(self, req); - http_log(HTTP_INFO, CS_FMT" "CS_FMT": %ld\n", CS_ARG(req.method), CS_ARG(req.path), req.resp->code); -} - -void whatever(http_middleware *self, http_request req) { - http_log(HTTP_INFO, "Whatever"); - http_run_next(self, req); -} - -int main() { - arena arena = {0}; - http_server serv = {0}; - if (init_server(&arena, &serv, "127.0.0.1", "6969") != 0) { - return 1; - } - - http_register_global_middleware(&serv, logging_func); - http_handler *success_handler = http_handle_path(&serv, CS("GET"), CS("/"), success); - http_register_handler_middleware(&arena, success_handler, whatever); - - listen_and_serve(&serv); - - return 0; -} diff --git a/src/arena.c b/src/arena.c new file mode 100644 index 0000000..8b4828f --- /dev/null +++ b/src/arena.c @@ -0,0 +1,84 @@ +#include + +region *new_region(size_t cap) { + size_t size_bytes = sizeof(region) + sizeof(uintptr_t)*cap; + region *r = (region *)malloc(size_bytes); + assert(r); + r->next = NULL; + r->len = 0; + r->cap = cap; + return r; +} + +arena arena_init_cap(size_t cap) { + arena a = {0}; + a.region_cap = cap; + return a; +} + +void *arena_alloc(arena *a, size_t size_bytes) { + size_t size = (size_bytes + sizeof(uintptr_t)-1)/sizeof(uintptr_t); + + if (a->end == NULL) { + assert(a->beg == NULL); + size_t capacity = a->region_cap ? a->region_cap : ARENA_REGION_DEFAULT_CAPACITY; + if (capacity < size) capacity = size; + a->beg = new_region(capacity); + a->end = a->beg; + } + + while (a->end->len + size < a->end->cap && a->end->next != NULL) { + a->end = a->end->next; + } + + if (a->end->len + size > a->end->cap) { + size_t capacity = a->region_cap ? a->region_cap : ARENA_REGION_DEFAULT_CAPACITY; + if (capacity < size) capacity = size; + a->beg = new_region(capacity); + a->end->next = new_region(capacity); + a->end = a->end->next; + } + + void *p = &a->end->data[a->end->len]; + a->end->len += size; + return memset(p, 0, size); +} + +void arena_free(arena *a) { + region *r = a->beg; + while (r != NULL) { + region *r0 = r; + r = r0->next; + free(r0); + } +} + +void *arena_realloc(arena *a, void *oldptr, size_t oldsz, size_t newsz) { + if (newsz <= oldsz) return oldptr; + void *newptr = arena_alloc(a, newsz); + char *newptr_char = (char*)newptr; + char *oldptr_char = (char*)oldptr; + for (size_t i = 0; i < oldsz; ++i) { + newptr_char[i] = oldptr_char[i]; + } + return newptr; +} + +void grow_da(void *slice, size_t size, arena *arena) { + struct { + void *data; + size_t len; + size_t cap; + } replica; + memcpy(&replica, slice, sizeof(replica)); + + replica.cap = replica.cap ? replica.cap : 1; + void *data = arena_alloc(arena, size*2*replica.cap); + replica.cap *= 2; + if (replica.len) { + memcpy(data, replica.data, size*replica.len); + } + replica.data = data; + + memcpy(slice, &replica, sizeof(replica)); +} diff --git a/src/arena.o b/src/arena.o new file mode 100644 index 0000000..845d142 Binary files /dev/null and b/src/arena.o differ diff --git a/src/arena_strings.c b/src/arena_strings.c new file mode 100644 index 0000000..78f98a1 --- /dev/null +++ b/src/arena_strings.c @@ -0,0 +1,53 @@ +#include + +const_string arena_cs_init(arena *a, int len) { + char *str = new(a, char, len+1); + const_string cs = {0}; + cs.data = str; + cs.len = len; + return cs; +} + +const_string arena_cs_append(arena *a, const_string dst, const_string src) { + char *str = new(a, char, dst.len+src.len + 1); + memcpy(str, dst.data, dst.len); + memcpy(str + dst.len, src.data, src.len + 1); + const_string cs = {0}; + cs.data = str; + cs.len = dst.len + src.len; + return cs; +} + +const_string arena_cs_concat(arena *a, const_string_da strings, const_string sep) { + if (strings.len == 0) { + return CS(""); + } else if (strings.len == 0) { + return *strings.data; + } + + int len = sep.len * (strings.len - 1); + for (size_t i = 0; i < strings.len; i++) { + len += (strings.data + i)->len; + }; + + char* str = arena_alloc(a, len); + int offset = 0; + + for (size_t i = 0; i < strings.len; i++) { + int curr_strlen = (strings.data + i)->len; + memcpy(str + offset, + (strings.data + i)->data, + curr_strlen); + + offset += curr_strlen; + + if (i != strings.len - 1) { + memcpy(str + offset, + sep.data, sep.len); + offset += sep.len; + } + }; + *(str + len + 1) = '\0'; + + return (const_string){str, len}; +}; diff --git a/src/arena_strings.o b/src/arena_strings.o new file mode 100644 index 0000000..b16bcec Binary files /dev/null and b/src/arena_strings.o differ diff --git a/src/const_strings.c b/src/const_strings.c new file mode 100644 index 0000000..7712eac --- /dev/null +++ b/src/const_strings.c @@ -0,0 +1,86 @@ +#include + +const_string cs_from_parts(const char* data, int len) { + const_string str; + str.data = data; + str.len = len; + + return str; +} + +const_string cs_from_cstr(const char* cstr) { + return cs_from_parts(cstr, strlen(cstr)); +} + +const_string cs_slice(const_string src, int from, int to) { + if (from > src.len) { + return src; + } + + int new_from; + if (from <= 0) { + new_from = 1; + } + new_from -= 1; + + int new_len; + if (to > src.len) { + new_len = src.len; + } else { + new_len = to; + } + + return cs_from_parts(src.data + new_from, new_len); +} + +const_string cs_chop_delim(const_string *str, char delim) { + int n; + for (n = 0; n < str->len && str->data[n] != delim; n++) {} + + const_string result = cs_from_parts(str->data, n); + + if (n < str->len) { + str->len -= n + 1; + str->data += n + 1; + } else { + str->len -= n; + str->data += n; + } + + return result; +} + +bool cs_try_chop_delim(const_string *str, char delim, const_string *dst) { + const_string str_copy = *str; + const_string tmp = cs_chop_delim(&str_copy, delim); + if (str_copy.len == 0) { + return false; + } + *str = str_copy; + *dst = tmp; + return true; +} + +int cs_find_delim(const_string str, char delim) { + int n; + for (n = 0; n < str.len && str.data[n] != delim; n++) {} + + if (n < str.len) { + return n; + } + return -1; +} + +bool cs_eq(const_string str1, const_string str2) { + if (str1.len != str2.len) { + return false; + } + return memcmp(str1.data, str2.data, str1.len) == 0; +} + +void cs_print(char *format, const_string str) { + char buf[str.len + 1]; + memcpy(buf, str.data, str.len); + buf[str.len] = '\0'; + printf(format, buf); +} diff --git a/src/const_strings.o b/src/const_strings.o new file mode 100644 index 0000000..7154db9 Binary files /dev/null and b/src/const_strings.o differ diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 0000000..c68d110 --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,43 @@ +#include + +size_t hash_str(const_string str, size_t cap) { + size_t hash = 0; + for (int i = 0; i < str.len; i++) { + hash = (HASHMAP_DEFAULT_R * hash + str.data[i]) % cap; + } + return hash; +} + +hashmap *hashmap_init(arena *a, size_t cap) { + hashmap *map = arena_alloc(a, sizeof(hashmap) + sizeof(ptrdiff_t) * cap); + map->cap = cap; + return map; +} + +size_t hashmap_kstr_insert(arena *a, hashmap* map, const_string key, void* val) { + size_t hash = hash_str(key, map->cap); + if (map->data[hash].key.len > 0) { + map->data[hash].next = arena_alloc(a, sizeof(hashitem)); + map->data[hash].next->key = key; + map->data[hash].next->val = val; + } else { + map->data[hash].key = key; + map->data[hash].val = val; + } + + return hash; +} + +void *hashmap_kstr_get(hashmap *map, const_string key) { + size_t hash = hash_str(key, map->cap); + hashitem *item = &map->data[hash]; + while (item->next != NULL && !cs_eq(item->key, key)) { + item = item->next; + } + + if (!cs_eq(item->key, key)) { + return NULL; + } + + return item->val; +} diff --git a/src/hashmap.o b/src/hashmap.o new file mode 100644 index 0000000..5dd78ec Binary files /dev/null and b/src/hashmap.o differ diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..db497d5 --- /dev/null +++ b/src/http.c @@ -0,0 +1,586 @@ +#include + +#define MAX_DATA_SIZE 256 +#define BUFFER_SIZE 4096 +#define REQUEST_ARENA_SIZE 1024 + +int volatile keepRunning = 1; + +struct { + http_response_code code; + const_string string; +} response_strings [] = { + {100, CS_STATIC("Continue")}, + {101, CS_STATIC("Switching Protocols")}, + {102, CS_STATIC("Processing")}, + {103, CS_STATIC("Early Hints")}, + {200, CS_STATIC("OK")}, + {201, CS_STATIC("Created")}, + {202, CS_STATIC("Accepted")}, + {204, CS_STATIC("No Content")}, + {300, CS_STATIC("Multiple Choices")}, + {301, CS_STATIC("Moved Permanently")}, + {302, CS_STATIC("Found")}, + {303, CS_STATIC("See Other")}, + {304, CS_STATIC("Not Modified")}, + {307, CS_STATIC("Temporary Redirect")}, + {308, CS_STATIC("Permanent Redirect")}, + {400, CS_STATIC("Bad Request")}, + {401, CS_STATIC("Unauthorized")}, + {402, CS_STATIC("Payment Required")}, + {403, CS_STATIC("Forbidden")}, + {404, CS_STATIC("Not Found")}, + {405, CS_STATIC("Method Not Allowed")}, + {406, CS_STATIC("Not Acceptable")}, + {408, CS_STATIC("Request Timeout")}, + {418, CS_STATIC("Im A Teepot")}, + {500, CS_STATIC("Internal Server Error")}, + {501, CS_STATIC("Not Implemented")}, + {502, CS_STATIC("Bad Gateway")}, + {503, CS_STATIC("Service Unavailable")}, + {505, CS_STATIC("Http Version Not Suppported")}, +}; + +const_string get_response_string(http_response_code code) { + for (int i = 0; sizeof(response_strings)/sizeof(response_strings[0]) ;i++) { + if (response_strings[i].code == code) { + return response_strings[i].string; + } + } + return CS(""); +} + + +void sigchld_handler() { + // waitpid() might overwrite errno, so we save and restore it: + int saved_errno = errno; + + while(waitpid(-1, NULL, WNOHANG) > 0); + + errno = saved_errno; +} + +void http_log(http_log_level log, char *fmt, ...) { + switch (log) { + case HTTP_INFO: + fprintf(stderr, "[INFO] "); + break; + + case HTTP_WARNING: + fprintf(stderr, "[WARNING] "); + break; + + case HTTP_ERROR: + fprintf(stderr, "[ERROR] "); + break; + } + + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + + +void *get_in_addr(struct sockaddr *sa) { + if (sa->sa_family == AF_INET) { + return &(((struct sockaddr_in*)sa)->sin_addr); + } + + return &(((struct sockaddr_in6*)sa)->sin6_addr); +} + +int get_in_port(struct sockaddr *sa) { + if (sa->sa_family == AF_INET) { + return ntohs((((struct sockaddr_in*)sa)->sin_port)); + } + + return (((struct sockaddr_in6*)sa)->sin6_port); +} + +void set_context_value(arena *arena, http_context *ctx, http_context_value kv) { + for (size_t i = 0; i < ctx->len; i++) { + http_context_value *ctx_val = (ctx->data + i); + if (cs_eq(kv.key, ctx_val->key)) { + ctx_val->val = kv.val; + return; + } + } + arena_da_append(arena, ctx, kv); +} + +void *get_context_value(http_context *ctx, char* key) { + if (ctx == NULL) { + return NULL; + } + + const_string key_cs = cs_from_cstr(key); + for (size_t i = 0; i < ctx->len; i++) { + http_context_value *ctx_val = (ctx->data + i); + if (cs_eq(key_cs, ctx_val->key)) { + return ctx_val->val; + } + } + return NULL; +} + +void http_internal_server_error_handler(http_request req) { + const_string *error = get_context_value(req.ctx, "error"); + req.resp->code = INTERNAL_SERVER_ERROR; + req.resp->body = *error; + http_send(req); +} + +void http_not_found_handler(http_request req) { + req.resp->code = NOT_FOUND; + http_send(req); +} + +void http_run_next(http_middleware *self, http_request req) { + if (self->next == NULL) { + (self->handler)(req); + return; + } + + (self->next->func)(self->next, req); +}; + +void http_register_handler_middleware(arena *arena, http_handler *hand, http_middleware_func func) { + http_middleware *middleware = arena_alloc(arena, sizeof(http_middleware)); + middleware->func = func; + middleware->handler = hand->func; + if (hand->middleware == NULL) { + hand->middleware = middleware; + return; + } + + http_middleware **prev = &hand->middleware; + http_middleware *next = hand->middleware; + while (next->next != NULL) { + prev = &next; + next = next->next; + } + middleware->next = next; + *prev = middleware; +} + +void http_register_global_middleware(http_server *serv, http_middleware_func func) { + arena *arena = serv->arena; + + http_middleware *middleware = arena_alloc(arena, sizeof(http_middleware)); + http_handler_func *handler = arena_alloc(arena, sizeof(http_handler_func)); + middleware->func = func; + middleware->handler = *handler; + if (serv->global_middleware == NULL) { + http_global_middleware *global_middleware = arena_alloc(arena, sizeof(http_global_middleware)); + global_middleware->start = middleware; + global_middleware->end = middleware; + serv->global_middleware = global_middleware; + return; + } + + serv->global_middleware->end->next = middleware; + serv->global_middleware->end = middleware; +} + +int init_server(arena *arena, http_server *serv, char *addr, char *port) { + // serv initialization + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + int status = getaddrinfo(addr, port, &hints, &res); + if (status != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); + return 1; + } + + int sockfd = socket(res->ai_family, res->ai_socktype, 0); + if (sockfd < 0) { + perror("socket"); + return 1; + } + + int yes = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) { + perror("setsockopt"); + return 1; + } + + if (bind(sockfd, res->ai_addr, res->ai_addrlen)) { + perror("bind"); + return 1; + } + + serv->arena = arena; + serv->addr = (struct sockaddr *)res->ai_addr; + serv->sockfd = sockfd; + + freeaddrinfo(res); + + struct sigaction sa; + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + perror("sigaction"); + return 1; + } + + return 0; +} + +http_handler *http_handle_path(http_server *serv, char* method, char* path, http_handler_func handler) { + http_handler hand = { + .method = cs_from_cstr(method), + .path = cs_from_cstr(path), + .func = handler, + }; + + arena_da_append(serv->arena, &serv->handlers, hand); + return &serv->handlers.data[serv->handlers.len-1]; +} + +const_string http_compose_response(arena *arena, http_response *resp) { + const_string_da header_da = {0}; + arena_da_append(arena, &header_da, CS("HTTP/1.1")); + + if (resp->code != 0) { + char buf[4]; + sprintf(buf, "%ld", resp->code); + arena_da_append(arena, &header_da, cs_from_cstr(buf)); + arena_da_append(arena, &header_da, get_response_string(resp->code)); + } else { + printf("Response is handeled, but no code is given\n"); + arena_da_append(arena, &header_da, CS("500")); + arena_da_append(arena, &header_da, get_response_string(INTERNAL_SERVER_ERROR)); + } + + const_string_da resp_da= {0}; + + const_string http_header = arena_cs_concat(arena, header_da, CS(" ")); + arena_da_append(arena, &resp_da, http_header); + + for (size_t i = 0; i < resp->headers.len; i++) { + KV *header = (resp->headers.data + i); + const_string header_str = {0}; + header_str = arena_cs_append(arena, header_str, header->key); + header_str = arena_cs_append(arena, header_str, CS(": ")); + header_str = arena_cs_append(arena, header_str, header->value); + arena_da_append(arena, &resp_da, header_str); + } + + arena_da_append(arena, &resp_da, CS("")); + if (resp->body.len != 0) { + arena_da_append(arena, &resp_da, resp->body); + } + + const_string str = arena_cs_concat(arena, resp_da, CS("\r\n")); + + return str; +} + + +void http_send_response(size_t inc_fd, const_string resp) { + const char *resp_cstr = resp.data; + int resp_len = resp.len; + + int sent = send(inc_fd, resp_cstr, resp_len, 0); + while (sent > 0 && resp_len - sent > 0) { + resp_len -= sent; + sent = send(inc_fd, resp_cstr, resp_len, 0); + } + + if (sent < 0) { + perror("send"); + } +} + +void http_send(http_request req) { + arena *arena = req.arena; + + assert(req.resp->code); + const_string response = http_compose_response(arena, req.resp); + http_send_response(req.inc_fd, response); + req.resp->sent = true; +} + +void http_send_json(http_request req, const_string json) { + arena *arena = req.arena; + + int num_str_len = snprintf(NULL, 0, "%d", json.len); + char* len_str = arena_alloc(arena, snprintf(NULL, 0, "%d", json.len)); + sprintf(len_str, "%d", json.len); + const_string len_cs = {len_str, num_str_len}; + + arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Length"), len_cs})); + arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Type"), CS("application/json")})); + req.resp->body = json; + + http_send(req); +} +void http_send_body(http_request req, const_string body) { + arena *arena = req.arena; + + int num_str_len = snprintf(NULL, 0, "%d", body.len+2); + char* len_str = arena_alloc(arena, snprintf(NULL, 0, "%d", body.len)); + sprintf(len_str, "%d", body.len); + const_string len_cs = {len_str, num_str_len}; + + arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Length"), len_cs})); + arena_da_append(arena, &req.resp->headers, ((KV){CS("Content-Type"), CS("application/text")})); + req.resp->body = body; + + http_send(req); +} + +int chop_query(arena *arena, const_string *path, http_query_da *queries) { + const_string queries_str = *path; + int query_count = 0; + + if (!cs_try_chop_delim(&queries_str, '?', path)) { + return 0; + } + + const_string query_str; + + if (!cs_try_chop_delim(&queries_str, '&', &query_str)) { + query_str = queries_str; + } + + do { + KV query = {0}; + const_string key; + if (!cs_try_chop_delim(&query_str, '=', &key)) { + return -1; + } + + query.key = key; + query.value = query_str; + arena_da_append(arena, queries, query); + query_count++; + } while (cs_try_chop_delim(&queries_str, '&', &query_str)); + + return query_count; +} + +const_string chop_request(const_string *req_str, char delim, bool *parse_err) { + if (*parse_err) { + return CS(""); + } + const_string str; + if (!cs_try_chop_delim(req_str, delim, &str)) { + *parse_err = true; + return CS(""); + } + return str; +} + +int parse_request(arena *arena, size_t inc_fd, http_request *req) { + char buf[MAX_DATA_SIZE]; + const_string req_str = {0}; + + int got = recv(inc_fd, &buf, MAX_DATA_SIZE - 1, 0); + if (got < 0) { + perror("recv"); + return REQUEST_TIMEOUT; + } + + buf[got] = '\0'; + + req_str = arena_cs_append(arena, req_str, cs_from_cstr(buf)); + + while (got == MAX_DATA_SIZE - 1) { + got = recv(inc_fd, &buf, MAX_DATA_SIZE - 1, 0); + if (got < 0) { + perror("recv"); + return REQUEST_TIMEOUT; + } + + buf[got] = '\0'; + + const_string tmp = cs_from_parts(buf, got); + req_str = arena_cs_append(arena, req_str, tmp); + } + + bool parse_err = false; + + const_string method = chop_request(&req_str, ' ', &parse_err); + const_string path = chop_request(&req_str, ' ', &parse_err); + chop_request(&req_str, '\r', &parse_err); + req_str.data += 1; + + header_da headers = {0}; + while (!parse_err && req_str.data[0] != '\r') { + const_string header_str = chop_request(&req_str, '\r', &parse_err); + req_str.data += 1; + + KV header; + header.key = chop_request(&header_str, ':', &parse_err); + header_str.data++; + header.value = header_str; + + arena_da_append(arena, &headers, header); + } + chop_request(&req_str, '\n', &parse_err); + + if (parse_err) { + return BAD_REQUEST; + } + + http_query_da queries = {0}; + + if (chop_query(arena, &path, &queries) < 0) { + return BAD_REQUEST; + } + + + req->method = method; + req->path = path; + req->query = queries; + req->headers = headers; + req->body = req_str; + + return 0; +} + +bool check_handler(http_handler hand, http_request req) { + arena *arena = req.arena; + http_context *ctx = req.ctx; + + const_string handler_path = hand.path; + const_string req_path = req.path; + if (!(cs_eq(hand.method, req.method) || + (cs_eq(hand.method, CS("GET")) && cs_eq(req.method, CS("HEAD"))))) { + return false; + } + + const_string path; + bool entered = false; + + while (cs_try_chop_delim(&handler_path, ':', &path)) { + entered = true; + + const_string req_byte = {req_path.data, path.len}; + if (!cs_eq(path, req_byte)) { + return false; + } + + req_path.data += path.len; + req_path.len -= path.len; + + const_string name = cs_chop_delim(&handler_path, '/'); + const_string tmp = cs_chop_delim(&req_path, '/'); + const_string *value = arena_alloc(arena, sizeof(const_string)); + *value = tmp; + set_context_value(arena, ctx, (http_context_value){name, (void *)value}); + } + + if (!entered && !cs_eq(handler_path, req_path)) { + return false; + } + + return true; +} + +struct process_request_args { + http_server serv; + size_t inc_fd; +}; + +void *process_request(void *args) { + struct process_request_args *pargs = args; + http_server serv = pargs->serv; + size_t inc_fd = pargs->inc_fd; + + arena req_arena = {0}; + + http_request req = {0}; + req.inc_fd = inc_fd; + req.arena = &req_arena; + + http_response resp = {0}; + req.resp = &resp; + + int parse_res = parse_request(&req_arena, inc_fd, &req); + if (parse_res != 0) { + resp.code = parse_res; + http_send(req); + } + + http_context req_context = {0}; + for (size_t i = 0; i < serv.global_ctx.len; i++) { + set_context_value(&req_arena, &req_context, *(serv.global_ctx.data + i)); + } + req.ctx = &req_context; + + http_global_middleware *g_mid = serv.global_middleware; + if (!resp.sent) { + for (size_t i = 0; i < serv.handlers.len; i++) { + http_handler handler = serv.handlers.data[i]; + if (check_handler(handler, req)) { + if (serv.global_middleware != NULL) { + g_mid->end->next = handler.middleware; + g_mid->end->handler = handler.func; + g_mid->start->func(g_mid->start, req); + } else if (handler.middleware != NULL) { + handler.middleware->func(handler.middleware, req); + } else { + handler.func(req); + } + + if (!resp.sent) { + const_string error = CS("Server handled the request but did not send anything\n"); + http_log(HTTP_ERROR, "%s", error.data); + set_context_value(&req_arena, &req_context, (http_context_value){CS("error"), (void *)&error}); + g_mid->end->handler = http_internal_server_error_handler; + g_mid->start->func(g_mid->start, req); + } + break; + } + } + } + + if (!resp.code) { + g_mid->end->handler = http_not_found_handler; + g_mid->start->func(g_mid->start, req); + } + + close(inc_fd); + arena_free(&req_arena); + + pthread_exit(NULL); +} + +int listen_and_serve(http_server *serv) { + if (listen(serv->sockfd, 10) != 0) { + perror("listen"); + return 1; + } + + char my_ipstr[INET6_ADDRSTRLEN]; + inet_ntop(serv->addr->sa_family, get_in_addr(serv->addr), my_ipstr, sizeof my_ipstr); + http_log(HTTP_INFO, "Listening on %s:%d\n", my_ipstr, get_in_port(serv->addr)); + + while (keepRunning) { + struct sockaddr inc_addr; + socklen_t addr_size = sizeof inc_addr; + int inc_fd = accept(serv->sockfd, &inc_addr, &addr_size); + if (inc_fd < 0) { + perror("accept"); + return 1; + } + + struct process_request_args args = { + .serv = *serv, + .inc_fd = inc_fd, + }; + + pthread_t thread_id; + pthread_create(&thread_id, NULL, process_request, (void *)&args); + } + return 0; +} diff --git a/src/http.o b/src/http.o new file mode 100644 index 0000000..9d4103c Binary files /dev/null and b/src/http.o differ -- cgit v1.2.3