diff --git a/slige/runtime/src/collection.h b/slige/runtime/src/collection.h new file mode 100644 index 0000000..ce33d66 --- /dev/null +++ b/slige/runtime/src/collection.h @@ -0,0 +1,151 @@ +#pragma once + +#include +#include + +#define DEFINE_VEC(TYPE, VEC_TYPE, FN_PREFIX, INITIAL_CAPACITY) \ + typedef struct { \ + TYPE* data; \ + size_t capacity; \ + size_t size; \ + } VEC_TYPE; \ + \ + static inline int FN_PREFIX##_construct(VEC_TYPE* vec) \ + { \ + const size_t capacity = INITIAL_CAPACITY; \ + TYPE* data = malloc(sizeof(TYPE) * capacity); \ + if (!data) \ + return -1; \ + *vec = (VEC_TYPE) { data, capacity, .size = 0 }; \ + return 0; \ + } \ + \ + static inline void FN_PREFIX##_destroy(VEC_TYPE* vec) \ + { \ + free(vec->data); \ + } \ + \ + static inline VEC_TYPE* FN_PREFIX##_new(void) \ + { \ + VEC_TYPE* vec = malloc(sizeof(VEC_TYPE)); \ + if (!vec) \ + return NULL; \ + int res = FN_PREFIX##_construct(vec); \ + if (res != 0) \ + return NULL; \ + return vec; \ + } \ + \ + static inline void FN_PREFIX##_free(VEC_TYPE* vec) \ + { \ + FN_PREFIX##_destroy(vec); \ + free(vec); \ + } \ + \ + static inline int FN_PREFIX##_push(VEC_TYPE* vec, TYPE value) \ + { \ + if (vec->size + 1 > vec->capacity) { \ + size_t new_capacity = vec->capacity * 2; \ + TYPE* new_data = realloc(vec->data, new_capacity * sizeof(TYPE)); \ + if (!new_data) \ + return -1; \ + vec->data = new_data; \ + vec->capacity = new_capacity; \ + } \ + vec->data[vec->size] = value; \ + vec->size += 1; \ + return 0; \ + } \ + \ + static inline TYPE* FN_PREFIX##_at(VEC_TYPE* vec, size_t idx) \ + { \ + return &vec->data[idx]; \ + } \ + \ + static inline const TYPE* FN_PREFIX##_at_const( \ + const VEC_TYPE* vec, size_t idx) \ + { \ + return &vec->data[idx]; \ + } \ + \ + static inline TYPE FN_PREFIX##_get(const VEC_TYPE* vec, size_t idx) \ + { \ + return vec->data[idx]; \ + } + +#define DEFINE_STATIC_QUEUE(TYPE, QUEUE_TYPE, FN_PREFIX) \ + typedef struct { \ + TYPE* data; \ + size_t capacity; \ + size_t back; \ + size_t front; \ + } QUEUE_TYPE; \ + \ + static inline int FN_PREFIX##_construct( \ + QUEUE_TYPE* queue, size_t capacity) \ + { \ + *queue = (QUEUE_TYPE) { \ + .data = malloc(sizeof(TYPE) * capacity), \ + .capacity = capacity, \ + .back = 0, \ + .front = 0, \ + }; \ + return 0; \ + } \ + \ + static inline void FN_PREFIX##_destroy(QUEUE_TYPE* queue) \ + { \ + free(queue->data); \ + } \ + \ + static inline QUEUE_TYPE* FN_PREFIX##_new(size_t capacity) \ + { \ + QUEUE_TYPE* queue = malloc(sizeof(QUEUE_TYPE)); \ + if (!queue) \ + return NULL; \ + int res = FN_PREFIX##_construct(queue, capacity); \ + if (res != 0) \ + return NULL; \ + return queue; \ + } \ + \ + static inline void FN_PREFIX##_free(QUEUE_TYPE* queue) \ + { \ + FN_PREFIX##_destroy(queue); \ + free(queue); \ + } \ + \ + static inline int FN_PREFIX##_push(QUEUE_TYPE* queue, TYPE req) \ + { \ + size_t front = queue->front + 1; \ + if (front >= queue->capacity) { \ + front = 0; \ + } \ + if (front == queue->back) { \ + return -1; \ + } \ + queue->data[queue->front] = req; \ + queue->front = front; \ + return 0; \ + } \ + \ + static inline size_t FN_PREFIX##_size(const QUEUE_TYPE* queue) \ + { \ + return queue->front >= queue->back \ + ? queue->front - queue->back \ + : (queue->capacity - queue->back) + queue->front; \ + } \ + \ + static inline int FN_PREFIX##_pop(QUEUE_TYPE* queue, TYPE* req) \ + { \ + if (queue->back == queue->front) { \ + return -1; \ + } \ + *req = queue->data[queue->back]; \ + size_t back = queue->back + 1; \ + if (back >= queue->capacity) { \ + back = 0; \ + } \ + queue->back = back; \ + return 0; \ + } diff --git a/slige/runtime/src/http_response_code_string.c b/slige/runtime/src/http_response_code_string.c new file mode 100644 index 0000000..a1fa2b8 --- /dev/null +++ b/slige/runtime/src/http_response_code_string.c @@ -0,0 +1,130 @@ + +const char* http_response_code_string(int code) +{ + switch (code) { + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 103: + return "Early Hints"; + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 208: + return "Already Reported"; + case 226: + return "IM Used"; + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Content Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 418: + return "I'm a teapot"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Content"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 425: + return "Too Early"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 510: + return "Not Extended"; + case 511: + return "Network Authentication Required"; + default: + return ""; + } +} diff --git a/slige/runtime/src/http_server.c b/slige/runtime/src/http_server.c index 27f1034..f24ba2b 100644 --- a/slige/runtime/src/http_server.c +++ b/slige/runtime/src/http_server.c @@ -2,7 +2,6 @@ #include "http_server_internal.h" #include #include -#include #include #include #include @@ -49,12 +48,16 @@ HttpServer* http_server_new(HttpServerOpts opts) // .ctx = {}, .workers = malloc(sizeof(Worker) * opts.workers_amount), .workers_size = opts.workers_amount, + .handlers = { 0 }, + .not_found_handler = NULL, + .user_ctx = NULL, }; - server_ctx_construct(&server->ctx); + ctx_construct(&server->ctx, server); for (size_t i = 0; i < opts.workers_amount; ++i) { worker_construct(&server->workers[i], &server->ctx); } + handler_vec_construct(&server->handlers); return server; } @@ -65,13 +68,14 @@ void http_server_free(HttpServer* server) for (size_t i = 0; i < server->workers_size; ++i) { worker_destroy(&server->workers[i]); } - server_ctx_destroy(&server->ctx); + ctx_destroy(&server->ctx); + handler_vec_destroy(&server->handlers); free(server); } int http_server_listen(HttpServer* server) { - ServerCtx* ctx = &server->ctx; + Cx* ctx = &server->ctx; while (true) { SockAddrIn client_addr; @@ -83,7 +87,7 @@ int http_server_listen(HttpServer* server) return -1; } - Request req = { .client_file = res, client_addr }; + Client req = { .file = res, client_addr }; pthread_mutex_lock(&ctx->mutex); res = request_queue_push(&ctx->req_queue, req); @@ -96,72 +100,105 @@ int http_server_listen(HttpServer* server) } } -void server_ctx_construct(ServerCtx* ctx) +void http_server_set_user_ctx(HttpServer* server, void* user_ctx) { + server->user_ctx = user_ctx; +} + +void http_server_get( + HttpServer* server, const char* path, HttpHandlerFn handler) +{ + handler_vec_push( + &server->handlers, (Handler) { path, .method = Method_GET, handler }); +} + +void http_server_post( + HttpServer* server, const char* path, HttpHandlerFn handler) +{ + handler_vec_push( + &server->handlers, (Handler) { path, .method = Method_POST, handler }); +} + +void http_server_set_not_found(HttpServer* server, HttpHandlerFn handler) +{ + server->not_found_handler = handler; +} + +void* http_ctx_user_ctx(HttpCtx* ctx) +{ + return ctx->user_ctx; +} + +bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key) +{ + return req_has_header(ctx->req, key); +} + +const char* http_ctx_req_headers_get(HttpCtx* ctx, const char* key) +{ + return req_get_header(ctx->req, key); +} + +const char* http_ctx_req_body(HttpCtx* ctx) +{ + return ctx->req_body; +} + +void http_ctx_res_headers_set(HttpCtx* ctx, const char* key, const char* value) +{ + char* key_copy = malloc(strlen(key) + 1); + strcpy(key_copy, key); + char* value_copy = malloc(strlen(value) + 1); + strcpy(value_copy, value); + + header_vec_push(&ctx->res_headers, (Header) { key_copy, value_copy }); +} + +void http_ctx_respond(HttpCtx* ctx, int status, const char* body) +{ + String res; + string_construct(&res); + + char first_line[32]; + snprintf(first_line, 32 - 1, "HTTP/1.1 %d %s\r\n", status, + http_response_code_string(status)); + string_push_str(&res, first_line); + + for (size_t i = 0; i < ctx->res_headers.size; ++i) { + Header* header = &ctx->res_headers.data[i]; + char line[96]; + snprintf(line, 96 - 1, "%s: %s\r\n", header->key, header->value); + string_push_str(&res, line); + } + string_push_str(&res, "\r\n"); + + string_push_str(&res, body); + + send(ctx->client->file, res.data, res.size, 0); + + string_destroy(&res); +} + +// +// +// + +static inline void ctx_construct(Cx* ctx, const HttpServer* server) +{ + ctx->server = server; pthread_mutex_init(&ctx->mutex, NULL); pthread_cond_init(&ctx->cond, NULL); request_queue_construct(&ctx->req_queue, 8192); } -void server_ctx_destroy(ServerCtx* ctx) +static inline void ctx_destroy(Cx* ctx) { pthread_mutex_destroy(&ctx->mutex); pthread_cond_destroy(&ctx->cond); request_queue_destroy(&ctx->req_queue); } -int request_queue_construct(RequestQueue* queue, size_t capacity) -{ - *queue = (RequestQueue) { - .data = malloc(sizeof(Request) * capacity), - .capacity = capacity, - .back = 0, - .front = 0, - }; - return 0; -} - -void request_queue_destroy(RequestQueue* queue) -{ - free(queue->data); -} - -int request_queue_push(RequestQueue* queue, Request req) -{ - size_t front = queue->front + 1; - if (front >= queue->capacity) { - front = 0; - } - if (front == queue->back) { - return -1; - } - queue->data[queue->front] = req; - queue->front = front; - return 0; -} - -size_t request_queue_size(const RequestQueue* queue) -{ - return queue->front >= queue->back - ? queue->front - queue->back - : (queue->capacity - queue->back) + queue->front; -} - -int request_queue_pop(RequestQueue* queue, Request* req) -{ - if (queue->back == queue->front) { - return -1; - } - *req = queue->data[queue->back]; - size_t back = queue->back + 1; - if (back >= queue->capacity) { - back = 0; - } - queue->back = back; - return 0; -} - -int worker_construct(Worker* worker, ServerCtx* ctx) +static inline int worker_construct(Worker* worker, Cx* ctx) { *worker = (Worker) { // .thread = {}, @@ -172,7 +209,7 @@ int worker_construct(Worker* worker, ServerCtx* ctx) return 0; } -void worker_destroy(Worker* worker) +static inline void worker_destroy(Worker* worker) { if (worker->thread != 0) { pthread_cancel(worker->thread); @@ -184,16 +221,16 @@ void worker_destroy(Worker* worker) } } -void* worker_thread_fn(void* data) +static inline void* worker_thread_fn(void* data) { Worker* worker = data; worker_listen(worker); return NULL; } -void worker_listen(Worker* worker) +static inline void worker_listen(Worker* worker) { - ServerCtx* ctx = worker->ctx; + Cx* ctx = worker->ctx; while (true) { pthread_testcancel(); @@ -205,7 +242,7 @@ void worker_listen(Worker* worker) continue; } - Request req; + Client req; request_queue_pop(&ctx->req_queue, &req); pthread_mutex_unlock(&ctx->mutex); @@ -213,16 +250,16 @@ void worker_listen(Worker* worker) } } -void worker_handle_request(Worker* worker, Request* req) +static inline void worker_handle_request(Worker* worker, Client* client) { (void)worker; uint8_t buffer[MAX_HEADER_BUFFER_SIZE] = { 0 }; - ssize_t bytes_received = recv(req->client_file, &buffer, sizeof(buffer), 0); + ssize_t bytes_received = recv(client->file, &buffer, sizeof(buffer), 0); if (bytes_received == -1) { fprintf(stderr, "error: could not receive request\n"); - goto f_return; + goto l0_return; } size_t header_end = 0; @@ -234,135 +271,195 @@ void worker_handle_request(Worker* worker, Request* req) if (header_end == 0) { fprintf(stderr, "error: header too big, exceeded %d bytes\n", MAX_HEADER_BUFFER_SIZE); - goto f_return; + goto l0_return; } - puts((char*)buffer); + // puts((char*)buffer); - HttpReq http_req; - int res = http_parse_header(&http_req, (char*)buffer, header_end); + Req req; + size_t body_idx; + int res = parse_header(&req, &body_idx, (char*)buffer, header_end); if (res != 0) { fprintf(stderr, "error: failed to parse header\n"); - goto f_return; + goto l0_return; } - if (http_req_has_header(&http_req, "Content-Length")) { } - - printf("headers:\n"); - for (size_t i = 0; i < http_req.headers_size; ++i) { - HttpHeader* header = &http_req.headers[i]; - printf("'%s': '%s'\n", header->key, header->value); + char* body = NULL; + if (req.method == Method_POST) { + if (!req_has_header(&req, "Content-Length")) { + fprintf(stderr, + "error: POST request has no body and/or Content-Length " + "header\n"); + goto l1_return; + } + const char* length_val = req_get_header(&req, "Content-Length"); + size_t length = strtoul(length_val, NULL, 10); + body = calloc(length + 1, sizeof(char)); } -f_return: - close(req->client_file); + HttpCtx handler_ctx = { + .client = client, + .req = &req, + .req_body = body, + .res_headers = { 0 }, + .user_ctx = worker->ctx->server->user_ctx, + }; + + header_vec_construct(&handler_ctx.res_headers); + + for (size_t i = 0; i < worker->ctx->server->handlers.size; ++i) { + Handler* handler = &worker->ctx->server->handlers.data[i]; + if (handler->method != req.method) + continue; + if (strcmp(handler->path, req.path) != 0) + continue; + handler->handler(&handler_ctx); + break; + } + +l1_return: + header_vec_destroy(&handler_ctx.res_headers); + req_destroy(&req); + free(body); +l0_return: + close(client->file); } -int http_parse_header(HttpReq* req, const char* buf, size_t buf_size) +static inline int parse_header( + Req* req, size_t* body_idx, const char* const buf, size_t buf_size) { -#define CHECK_OVERRUN \ - if (i >= buf_size) { \ - return -1; \ - } + Strlitter splitter = string_split(buf, buf_size, "\r\n"); - size_t i = 0; + StrSlice first = split_next(&splitter); + Strlitter first_splitter = string_split(first.ptr, first.len, " "); + StrSlice method_str = split_next(&first_splitter); + StrSlice path_str = split_next(&first_splitter); + StrSlice version_str = split_next(&first_splitter); - char method_buf[8] = { 0 }; - size_t method_buf_i = 0; - for (; i < buf_size && method_buf_i < 8 && buf[i] != ' '; ++i) { - method_buf[method_buf_i] = buf[i]; - method_buf_i += 1; - } - CHECK_OVERRUN; - i += 1; - - HttpMethod method; - if (strncmp(method_buf, "GET", 3) == 0) { - method = HttpMethod_GET; - } else if (strncmp(method_buf, "POST", 4) == 0) { - method = HttpMethod_POST; - } else { - fprintf(stderr, "error: unrecognized http method '%.8s'\n", method_buf); + if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) { + fprintf(stderr, "error: unrecognized http version '%.*s'\n", + (int)version_str.len, version_str.ptr); return -1; } - char* path = calloc(MAX_PATH_SIZE, sizeof(char)); - size_t path_i = 0; - for (; i < buf_size && path_i < MAX_PATH_SIZE - 1 && buf[i] != ' '; ++i) { - char ch = buf[i]; - path[path_i] = ch; - path_i += 1; - } - CHECK_OVERRUN; - - for (; i < buf_size && buf[i] != '\r'; ++i) { } - i += 2; - CHECK_OVERRUN; - - HttpHeader* headers = malloc(sizeof(HttpHeader) * MAX_HEADERS_SIZE); - size_t headers_size = 0; - for (; i < buf_size && headers_size < MAX_HEADERS_SIZE && buf[i] != '\r'; - ++i) { - - i += 1; - CHECK_OVERRUN; - char* key = calloc(MAX_HEADER_KEY_SIZE, sizeof(char)); - size_t key_i = 0; - for (; i < buf_size && key_i < MAX_HEADER_KEY_SIZE - 1 && buf[i] != ':'; - ++i) { - key[key_i] = buf[i]; - key_i += 1; - } - i += 1; - CHECK_OVERRUN; - char* value = calloc(MAX_HEADER_VALUE_SIZE, sizeof(char)); - size_t value_i = 0; - for (; i < buf_size && value_i < MAX_HEADER_VALUE_SIZE - 1 - && buf[i] != '\r'; - ++i) { - value[value_i] = buf[i]; - value_i += 1; - } - i += 2; - CHECK_OVERRUN; - headers[headers_size] = (HttpHeader) { key, value }; - headers_size += 1; + Method method; + if (strncmp(method_str.ptr, "GET", method_str.len) == 0) { + method = Method_GET; + } else if (strncmp(method_str.ptr, "POST", method_str.len) == 0) { + method = Method_POST; + } else { + fprintf(stderr, "error: unrecognized http method '%.*s'\n", + (int)method_str.len, method_str.ptr); + return -1; } - *req = (HttpReq) { - method, - path, - headers, - headers_size, - }; + if (path_str.len >= MAX_PATH_LEN + 1) + return -1; + + char* path = calloc(MAX_PATH_LEN + 1, sizeof(char)); + strncpy(path, path_str.ptr, path_str.len); + path[path_str.len] = '\0'; + + HeaderVec headers; + header_vec_construct(&headers); + + while (headers.size < MAX_HEADERS_LEN) { + StrSlice line = split_next(&splitter); + if (line.len == 0) { + *body_idx = (size_t)&line.ptr[2] - (size_t)buf; + break; + } + + size_t key_len = 0; + while (key_len < line.len && line.ptr[key_len] != ':') { + key_len += 1; + } + if (key_len == 0 || key_len > MAX_HEADER_KEY_LEN) { + return -1; + } + size_t value_begin = key_len + 1; + while (value_begin < line.len && line.ptr[value_begin] == ' ') { + value_begin += 1; + } + size_t value_len = line.len - value_begin; + if (value_len == 0 || value_len > MAX_HEADER_VALUE_LEN) { + return -1; + } + + char* key = calloc(key_len + 1, sizeof(char)); + strncpy(key, line.ptr, key_len); + + char* value = calloc(value_len + 1, sizeof(char)); + strncpy(value, &line.ptr[value_begin], value_len); + + header_vec_push(&headers, (Header) { key, value }); + } + + *req = (Req) { method, path, headers }; return 0; } -void http_req_destroy(HttpReq* req) +static inline void req_destroy(Req* req) { free(req->path); - for (size_t i = 0; i < req->headers_size; ++i) { - free(req->headers[i].key); - free(req->headers[i].value); + for (size_t i = 0; i < req->headers.size; ++i) { + free(req->headers.data[i].key); + free(req->headers.data[i].value); } - free(req->headers); + header_vec_destroy(&req->headers); } -bool http_req_has_header(HttpReq* req, const char* key) +static inline bool req_has_header(const Req* req, const char* key) { - for (size_t i = 0; i < req->headers_size; ++i) { - if (strcmp(key, req->headers[i].key) == 0) { + for (size_t i = 0; i < req->headers.size; ++i) { + if (strcmp(key, req->headers.data[i].key) == 0) { return true; } } return false; } -const char* http_req_get_header(HttpReq* req, const char* key) +static inline const char* req_get_header(const Req* req, const char* key) { - for (size_t i = 0; i < req->headers_size; ++i) { - if (strcmp(key, req->headers[i].key) == 0) { - return req->headers[i].value; + for (size_t i = 0; i < req->headers.size; ++i) { + if (strcmp(key, req->headers.data[i].key) == 0) { + return req->headers.data[i].value; } } return NULL; } + +static inline Strlitter string_split( + const char* text, size_t text_len, const char* split) +{ + return (Strlitter) { + .text = text, + .text_len = text_len, + .i = 0, + .split = split, + .split_len = strlen(split), + }; +} + +static inline StrSlice split_next(Strlitter* splitter) +{ + for (size_t i = splitter->i; i < splitter->text_len; ++i) { + if (strncmp(&splitter->text[i], splitter->split, splitter->split_len) + == 0) { + const char* ptr = &splitter->text[splitter->i]; + size_t len = i - splitter->i; + splitter->i = i + splitter->split_len; + return (StrSlice) { ptr, len }; + } + } + return (StrSlice) { + .ptr = &splitter->text[splitter->i], + .len = splitter->text_len - splitter->i, + }; +} + +static inline void string_push_str(String* string, const char* str) +{ + for (size_t i = 0; i < strlen(str); ++i) { + string_push(string, str[i]); + } +} diff --git a/slige/runtime/src/http_server.h b/slige/runtime/src/http_server.h index c508ebf..4c50fb5 100644 --- a/slige/runtime/src/http_server.h +++ b/slige/runtime/src/http_server.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -10,6 +11,9 @@ typedef struct { size_t workers_amount; } HttpServerOpts; +typedef struct HttpCtx HttpCtx; +typedef void (*HttpHandlerFn)(HttpCtx* ctx); + /// On ok, HttpServer /// On error, returns NULL and prints. HttpServer* http_server_new(HttpServerOpts opts); @@ -17,3 +21,16 @@ void http_server_free(HttpServer* server); /// On ok, returns 0. /// On error, returns -1 and prints; int http_server_listen(HttpServer* server); +void http_server_set_user_ctx(HttpServer* server, void* user_ctx); +void http_server_get( + HttpServer* server, const char* path, HttpHandlerFn handler); +void http_server_post( + HttpServer* server, const char* path, HttpHandlerFn handler); +void http_server_set_not_found(HttpServer* server, HttpHandlerFn handler); + +void* http_ctx_user_ctx(HttpCtx* ctx); +bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key); +const char* http_ctx_req_headers_get(HttpCtx* ctx, const char* key); +const char* http_ctx_req_body(HttpCtx* ctx); +void http_ctx_res_headers_set(HttpCtx* ctx, const char* key, const char* value); +void http_ctx_respond(HttpCtx* ctx, int status, const char* body); diff --git a/slige/runtime/src/http_server_internal.h b/slige/runtime/src/http_server_internal.h index 5e2711a..d489651 100644 --- a/slige/runtime/src/http_server_internal.h +++ b/slige/runtime/src/http_server_internal.h @@ -1,5 +1,6 @@ #pragma once +#include "collection.h" #include "http_server.h" #include #include @@ -14,89 +15,113 @@ typedef struct sockaddr SockAddr; typedef struct sockaddr_in SockAddrIn; typedef struct { - int client_file; - SockAddrIn client_addr; -} Request; + int file; + SockAddrIn addr; +} Client; -/// This shit is implemented as a static size fifo buffer. -typedef struct { - Request* data; - size_t capacity; - size_t back; - size_t front; -} RequestQueue; - -/// On ok, returns 0. -/// On error, returns -1. -int request_queue_construct(RequestQueue* queue, size_t capacity); -void request_queue_destroy(RequestQueue* queue); - -/// On ok, returns 0. -/// On error, returns -1 if queue is full. -int request_queue_push(RequestQueue* queue, Request req); -size_t request_queue_size(const RequestQueue* queue); - -/// On ok, returns 0. -/// On error, returns -1 if queue is empty. -int request_queue_pop(RequestQueue* queue, Request* req); +DEFINE_STATIC_QUEUE(Client, ReqQueue, request_queue) typedef struct { + const HttpServer* server; pthread_mutex_t mutex; pthread_cond_t cond; - RequestQueue req_queue; -} ServerCtx; + ReqQueue req_queue; +} Cx; -void server_ctx_construct(ServerCtx* ctx); -void server_ctx_destroy(ServerCtx* ctx); +static inline void ctx_construct(Cx* ctx, const HttpServer* server); +static inline void ctx_destroy(Cx* ctx); typedef struct { pthread_t thread; - ServerCtx* ctx; + Cx* ctx; } Worker; /// On ok, returns 0. /// On error, returns -1; -int worker_construct(Worker* worker, ServerCtx* ctx); -void worker_destroy(Worker* worker); -void* worker_thread_fn(void* data); -void worker_thread_cleanup(void* data); -void worker_listen(Worker* worker); -void worker_handle_request(Worker* worker, Request* req); - -struct HttpServer { - int file; - SockAddrIn addr; - ServerCtx ctx; - Worker* workers; - size_t workers_size; -}; +static inline int worker_construct(Worker* worker, Cx* ctx); +static inline void worker_destroy(Worker* worker); +static inline void* worker_thread_fn(void* data); +static inline void worker_listen(Worker* worker); +static inline void worker_handle_request(Worker* worker, Client* req); #define MAX_HEADER_BUFFER_SIZE 8192 -#define MAX_PATH_SIZE 128 -#define MAX_HEADERS_SIZE 32 -#define MAX_HEADER_KEY_SIZE 32 -#define MAX_HEADER_VALUE_SIZE 64 +#define MAX_PATH_LEN 128 - 1 +#define MAX_HEADERS_LEN 32 +#define MAX_HEADER_KEY_LEN 32 - 1 +#define MAX_HEADER_VALUE_LEN 128 - 1 typedef enum { - HttpMethod_GET, - HttpMethod_POST, -} HttpMethod; + Method_GET, + Method_POST, +} Method; typedef struct { char* key; char* value; -} HttpHeader; +} Header; + +DEFINE_VEC(Header, HeaderVec, header_vec, 8) typedef struct { - HttpMethod method; + Method method; char* path; - HttpHeader* headers; - size_t headers_size; -} HttpReq; + HeaderVec headers; +} Req; /// On error, returns -1. -int http_parse_header(HttpReq* req, const char* buf, size_t buf_size); -void http_req_destroy(HttpReq* req); -bool http_req_has_header(HttpReq* req, const char* key); -const char* http_req_get_header(HttpReq* req, const char* key); +static inline int parse_header( + Req* req, size_t* body_idx, const char* const buf, size_t buf_size); +static inline void req_destroy(Req* req); +static inline bool req_has_header(const Req* req, const char* key); +static inline const char* req_get_header(const Req* req, const char* key); + +typedef struct { + const char* path; + Method method; + HttpHandlerFn handler; +} Handler; + +DEFINE_VEC(Handler, HandlerVec, handler_vec, 8) + +struct HttpServer { + int file; + SockAddrIn addr; + Cx ctx; + Worker* workers; + size_t workers_size; + HandlerVec handlers; + HttpHandlerFn not_found_handler; + void* user_ctx; +}; + +struct HttpCtx { + Client* client; + const Req* req; + const char* req_body; + HeaderVec res_headers; + void* user_ctx; +}; + +typedef struct { + const char* ptr; + size_t len; +} StrSlice; + +typedef struct { + const char* text; + size_t text_len; + size_t i; + const char* split; + size_t split_len; +} Strlitter; + +static inline Strlitter string_split( + const char* text, size_t text_len, const char* split); +static inline StrSlice split_next(Strlitter* splitter); + +DEFINE_VEC(char, String, string, 8) + +static inline void string_push_str(String* string, const char* str); + +const char* http_response_code_string(int code); diff --git a/slige/runtime/src/main.c b/slige/runtime/src/main.c index 17151ce..c6aedb5 100644 --- a/slige/runtime/src/main.c +++ b/slige/runtime/src/main.c @@ -1,13 +1,40 @@ #include "http_server.h" -#include #include -#include +#include + +typedef struct { + int number; +} Cx; + +void route_get_index(HttpCtx* ctx) +{ + Cx* cx = http_ctx_user_ctx(ctx); + + char body[512]; + snprintf(body, 512 - 1, + "

Number = %d

", + cx->number); + + char content_length[24] = { 0 }; + snprintf(content_length, 24 - 1, "%ld", strlen(body)); + + http_ctx_res_headers_set(ctx, "Content-Type", "text/html"); + http_ctx_res_headers_set(ctx, "Content-Length", content_length); + + http_ctx_respond(ctx, 200, body); +} + +void route_post_set_number(HttpCtx* ctx) +{ + printf("set number\n"); +} HttpServer* server; int main(void) { - printf("hello world\n"); + Cx cx = { .number = 1 }; server = http_server_new((HttpServerOpts) { .port = 8080, @@ -17,6 +44,10 @@ int main(void) return -1; } + http_server_set_user_ctx(server, &cx); + http_server_get(server, "/", route_get_index); + http_server_post(server, "/set_number", route_post_set_number); + printf("listening at http://127.0.0.1:8080/\n"); http_server_listen(server);