Mercurial
changeset 96:70401cf61e97
[Seobeo] Added logging.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 02 Jan 2026 19:16:17 -0800 |
| parents | b51f8cce9170 |
| children | 3bdfffaad162 |
| files | dowa/dowa.h mrjunejune/BUILD mrjunejune/main.c seobeo/BUILD seobeo/LOGGING.md seobeo/s_linux_network.c seobeo/s_router.c seobeo/s_ssl.c seobeo/s_web.c seobeo/seobeo.h seobeo/seobeo_internal.h |
| diffstat | 11 files changed, 520 insertions(+), 288 deletions(-) [+] |
line wrap: on
line diff
--- a/dowa/dowa.h Fri Jan 02 19:11:35 2026 -0800 +++ b/dowa/dowa.h Fri Jan 02 19:16:17 2026 -0800 @@ -7,11 +7,6 @@ #include <assert.h> // mostly for TODO #include <stddef.h> // size_t -#include <math.h> // I am not re-writing stuff I guess... - -#include <sys/stat.h> -#include <limits.h> - #include "dowa_internal.h" // DLAPI macro for DLL export/import @@ -202,9 +197,9 @@ // --- String Manipulation --- // -DLAPI char *Dowa_String_Slice(char *from, size_t start, size_t end, Dowa_Arena *p_arena); -DLAPI char **Dowa_String_Split(char *from, char *token, int32 from_length, int32 token_length, Dowa_Arena *p_arena); -DLAPI char *Dowa_String_Copy_Arena(char *from, Dowa_Arena *p_arena); +DLAPI char *Dowa_String_Slice(char *from, size_t start, size_t end, Dowa_Arena *p_arena); +DLAPI char **Dowa_String_Split(char *from, char *token, int32 from_length, int32 token_length, Dowa_Arena *p_arena); +DLAPI char *Dowa_String_Copy_Arena(char *from, Dowa_Arena *p_arena); DLAPI int32 Dowa_String_Pos_Find(const char *p_from, const char *p_value, const size_t from_length, const size_t value_length); DLAPI char *Dowa_String_Find(const char *p_from, const char *p_value, const size_t from_length, const size_t value_length); DLAPI int32 Dowa_String_Pos_Find_Char(const char *p_from, int c, int32 from_length);
--- a/mrjunejune/BUILD Fri Jan 02 19:11:35 2026 -0800 +++ b/mrjunejune/BUILD Fri Jan 02 19:16:17 2026 -0800 @@ -28,7 +28,14 @@ cc_binary( name = "mrjunejune_server", srcs = ["main.c"], - deps = ["//seobeo:seobeo_server"], + deps = ["//seobeo:seobeo_server"], + data = [":src_files"], +) + +cc_binary( + name = "mrjunejune_server_dev", + srcs = ["main.c"], + deps = ["//seobeo:seobeo_server_dev"], data = [":src_files"], ) @@ -37,6 +44,11 @@ binary = ":mrjunejune_server", ) +bundle( + name = "mrjunejune_server_dev_bundle", + binary = ":mrjunejune_server_dev", +) + cc_test( name = "integration_test", srcs = ["test/integration_test.c"],
--- a/mrjunejune/main.c Fri Jan 02 19:11:35 2026 -0800 +++ b/mrjunejune/main.c Fri Jan 02 19:16:17 2026 -0800 @@ -28,7 +28,7 @@ int32 token_len = 2; - while (true) + while (1) { char *start_tag = strstr(cursor, "{{"); if (!start_tag) break;
--- a/seobeo/BUILD Fri Jan 02 19:11:35 2026 -0800 +++ b/seobeo/BUILD Fri Jan 02 19:16:17 2026 -0800 @@ -13,7 +13,7 @@ visibility = ["//visibility:public"], ) -# Server-only target (no SSL, no OpenSSL dependency) +# Server-only target (no SSL, no OpenSSL dependency) - Production alias( name = "seobeo_server", actual = select({ @@ -24,13 +24,23 @@ visibility = ["//visibility:public"], ) +# Server-only target (no SSL, no OpenSSL dependency) - Development with Debug Logs +alias( + name = "seobeo_server_dev", + actual = select({ + "//config:macos": ":seobeo_server_macos_dev", + "//config:linux": ":seobeo_server_linux_dev", + "//conditions:default": ":seobeo_server_linux_dev", + }), + visibility = ["//visibility:public"], +) + cc_library( name = "seobeo_server_macos", srcs = [ "s_linux_network.c", "s_web.c", "s_ssl.c", - "s_router.c", "os/s_macos_edge.c", ], hdrs = [":seobeo_hdrs"], @@ -45,12 +55,30 @@ ) cc_library( + name = "seobeo_server_macos_dev", + srcs = [ + "s_linux_network.c", + "s_web.c", + "s_ssl.c", + "os/s_macos_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + ], + defines = ["SEOBEO_NO_SSL", "SEOBEO_ENABLE_DEBUG"], + target_compatible_with = [ + "@platforms//os:osx", + ], + visibility = ["//visibility:public"], +) + +cc_library( name = "seobeo_server_linux", srcs = [ "s_linux_network.c", "s_web.c", "s_ssl.c", - "s_router.c", "os/s_linux_edge.c", ], hdrs = [":seobeo_hdrs"], @@ -64,6 +92,25 @@ visibility = ["//visibility:public"], ) +cc_library( + name = "seobeo_server_linux_dev", + srcs = [ + "s_linux_network.c", + "s_web.c", + "s_ssl.c", + "os/s_linux_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + ], + defines = ["SEOBEO_NO_SSL", "SEOBEO_ENABLE_DEBUG"], + target_compatible_with = [ + "@platforms//os:linux", + ], + visibility = ["//visibility:public"], +) + # Client target with SSL support (full functionality) alias( name = "seobeo_client", @@ -81,7 +128,6 @@ "s_linux_network.c", "s_web.c", "s_ssl.c", - "s_router.c", "snapshot_creator.c", "os/s_macos_edge.c", ], @@ -102,7 +148,6 @@ "s_linux_network.c", "s_web.c", "s_ssl.c", - "s_router.c", "snapshot_creator.c", "os/s_linux_edge.c", ],
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/LOGGING.md Fri Jan 02 19:16:17 2026 -0800 @@ -0,0 +1,157 @@ +# Seobeo Logging System + +## Overview + +All `printf` statements in the seobeo library have been replaced with `Seobeo_Log()` calls for consistent, level-based logging with optional debug mode. + +## Log Levels + +```c +typedef enum { + SEOBEO_INFO = 0, // General information (server start, mode, etc.) + SEOBEO_WARNING, // Warnings (SSL not compiled, etc.) + SEOBEO_ERROR, // Errors (allocation failures, connection errors, etc.) + SEOBEO_DEBUG, // Debug messages (only shown in development mode) +} Seobeo_Log_Level; +``` + +## Usage + +```c +Seobeo_Log(SEOBEO_INFO, "Listening on port %s\n", port); +Seobeo_Log(SEOBEO_ERROR, "Failed to allocate %zu bytes\n", size); +Seobeo_Log(SEOBEO_DEBUG, "Request line (first %zu bytes)\n", len); +``` + +## Debug Mode Control + +Debug logs are controlled by the `SEOBEO_ENABLE_DEBUG` macro: + +- **Production builds**: Debug logs are disabled (not compiled in) +- **Development builds**: Debug logs are enabled via `-DSEOBEO_ENABLE_DEBUG` + +## Build Targets + +### Seobeo Library + +#### Production (No Debug Logs) +```bash +# macOS +//seobeo:seobeo_server_macos + +# Linux +//seobeo:seobeo_server_linux + +# Platform-agnostic alias +//seobeo:seobeo_server +``` + +#### Development (With Debug Logs) +```bash +# macOS +//seobeo:seobeo_server_macos_dev + +# Linux +//seobeo:seobeo_server_linux_dev + +# Platform-agnostic alias +//seobeo:seobeo_server_dev +``` + +### MrJuneJune Server + +#### Production Build +```bash +# Build +bazel build //mrjunejune:mrjunejune_server + +# Run +bazel run //mrjunejune:mrjunejune_server + +# Bundle (for deployment) +bazel build //mrjunejune:mrjunejune_server_bundle +``` + +#### Development Build (With Debug Logs) +```bash +# Build +bazel build //mrjunejune:mrjunejune_server_dev + +# Run +bazel run //mrjunejune:mrjunejune_server_dev + +# Bundle +bazel build //mrjunejune:mrjunejune_server_dev_bundle +``` + +## Log Output Format + +All logs are prefixed with their level: + +``` +[INFO] Listening on port 6969 +[INFO] Server mode: EDGE +[ERROR] Failed to allocate 1024 bytes for body +[DEBUG] Request line (first 200 bytes) +[DEBUG] sscanf returned 3 (method='GET', path='/', version='HTTP/1.1') +``` + +## Changes Summary + +### Files Modified + +1. **seobeo/s_web.c** + - Replaced 26 printf statements with Seobeo_Log + - Updated logging to use appropriate levels (INFO, ERROR, DEBUG) + +2. **seobeo/s_linux_network.c** + - Replaced 10 printf statements with Seobeo_Log + - Added proper error and debug logging for network operations + +3. **seobeo/s_ssl.c** + - Replaced 5 printf/fprintf statements with Seobeo_Log + - SSL errors now use SEOBEO_ERROR level + - SSL info uses SEOBEO_INFO level + +4. **seobeo/BUILD** + - Added `seobeo_server_dev` alias for development builds + - Added `seobeo_server_macos_dev` with `SEOBEO_ENABLE_DEBUG` + - Added `seobeo_server_linux_dev` with `SEOBEO_ENABLE_DEBUG` + +5. **mrjunejune/BUILD** + - Added `mrjunejune_server_dev` binary target + - Added `mrjunejune_server_dev_bundle` for development deployment + +## Benefits + +1. **Cleaner Production Logs**: Debug messages don't clutter production output +2. **Better Debugging**: Enable verbose logging during development +3. **Consistent Format**: All logs follow the same `[LEVEL] message` format +4. **Conditional Compilation**: Debug code is completely removed from production builds (zero runtime overhead) +5. **Easy Toggle**: Switch between dev and prod with different build targets + +## Examples + +### Running in Development Mode +```bash +$ bazel run //mrjunejune:mrjunejune_server_dev +[INFO] Listening on port 6969 +[INFO] Server mode: EDGE +[DEBUG] Request line (first 30 bytes) +[DEBUG] sscanf returned 3 (method='GET', path='/', version='HTTP/1.1') +[DEBUG] Allocating method_copy +[DEBUG] Allocating version_copy +[DEBUG] Map now has 2 entries +[DEBUG] File path: /index.html +Body Size: 1234 +[DEBUG] Request handled successfully +``` + +### Running in Production Mode +```bash +$ bazel run //mrjunejune:mrjunejune_server +[INFO] Listening on port 6969 +[INFO] Server mode: EDGE +``` + +Notice that debug messages are completely absent in production mode!
--- a/seobeo/s_linux_network.c Fri Jan 02 19:11:35 2026 -0800 +++ b/seobeo/s_linux_network.c Fri Jan 02 19:16:17 2026 -0800 @@ -45,7 +45,7 @@ if (listen(socket_fd, 16) != 0) { - printf("Closing: %d\n", socket_fd); + Seobeo_Log(SEOBEO_DEBUG, "Closing socket: %d\n", socket_fd); perror("listen"); close(socket_fd); return NULL; } @@ -73,7 +73,7 @@ p_handle->write_buffer_capacity = INITIAL_BUFFER_CAPACITY; p_handle->write_buffer_len = 0; - p_handle->destroyed = false; + p_handle->destroyed = FALSE; return p_handle; } @@ -117,7 +117,7 @@ return NULL; } } - p_handle->connected = true; + p_handle->connected = TRUE; p_handle->host = host != NULL ? strdup(host) : "localhost"; p_handle->port = strdup(port); @@ -130,7 +130,7 @@ p_handle->write_buffer_capacity = INITIAL_BUFFER_CAPACITY; p_handle->write_buffer_len = 0; - p_handle->destroyed = false; + p_handle->destroyed = FALSE; return p_handle; } @@ -159,7 +159,7 @@ p_client_handle->socket = client_fd; p_client_handle->type = SEOBEO_STREAM_TYPE_CLIENT; - p_client_handle->connected = true; + p_client_handle->connected = TRUE; // TODO: support SSL in the future. p_client_handle->ssl_ctx = NULL; @@ -180,7 +180,7 @@ p_client_handle->file = NULL; p_client_handle->text_copy = NULL; p_client_handle->file_name = NULL; - p_client_handle->destroyed = false; + p_client_handle->destroyed = FALSE; return p_client_handle; } @@ -189,9 +189,9 @@ { if (!p_handle) return; - bool expected = false; + boolean expected = FALSE; // Need to check - if (!atomic_compare_exchange_strong(&p_handle->destroyed, &expected, true)) + if (!atomic_compare_exchange_strong(&p_handle->destroyed, &expected, TRUE)) { return; } @@ -202,7 +202,7 @@ Seobeo_SSL_Cleanup(p_handle); if (p_handle->socket) { - printf("Closing: %d\n", p_handle->socket); + Seobeo_Log(SEOBEO_DEBUG, "Closing handle socket: %d\n", p_handle->socket); close(p_handle->socket); } @@ -218,7 +218,7 @@ uint32 total = p_handle->write_buffer_len; uint32 sent = 0; - printf("Total: %d\n\n", p_handle->write_buffer_len); + Seobeo_Log(SEOBEO_DEBUG, "Write buffer total: %d\n", p_handle->write_buffer_len); while (sent < total) { @@ -230,7 +230,7 @@ sent += (uint32)n; }else { - printf("socket: %d\n", p_handle->socket); + Seobeo_Log(SEOBEO_DEBUG, "Flushing socket: %d\n", p_handle->socket); ssize_t n = write( p_handle->socket, p_handle->write_buffer + sent, @@ -269,7 +269,7 @@ if (n==0) { // DEBUG - printf("NONE %d\n", offset); + Seobeo_Log(SEOBEO_DEBUG, "Write offset: %d\n", offset); break; } if (n < 0) @@ -285,27 +285,27 @@ } offset += (uint32)n; // DEBUG - printf("\n\noffset: %d data_size: %d\n\n", offset, data_size); + Seobeo_Log(SEOBEO_DEBUG, "Write completed - offset: %d, data_size: %d\n", offset, data_size); } // DEBUG - printf("\n\nTotal: %d\n", offset); + Seobeo_Log(SEOBEO_DEBUG, "Total bytes written: %d\n", offset); return 0; } if (!p_handle) { - printf("[ERROR] p_handle is NULL before memcpy\n"); + Seobeo_Log(SEOBEO_ERROR, "p_handle is NULL before memcpy\n"); return -1; } if (!p_handle->write_buffer) { - printf("[ERROR] p_handle->write_buffer is NULL (len=%zu, size=%zu)\n", + Seobeo_Log(SEOBEO_ERROR, "p_handle->write_buffer is NULL (len=%u, size=%u)\n", p_handle->write_buffer_len, data_size); return -1; } - printf("[DEBUG] memcpy -> dest=%p (write_buffer=%p + offset=%zu), src=%p, size=%zu\n", + Seobeo_Log(SEOBEO_DEBUG, "memcpy -> dest=%p (write_buffer=%p + offset=%u), src=%p, size=%u\n", p_handle->write_buffer + p_handle->write_buffer_len, p_handle->write_buffer, p_handle->write_buffer_len,
--- a/seobeo/s_router.c Fri Jan 02 19:11:35 2026 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,203 +0,0 @@ -#include "seobeo/seobeo.h" -#include <string.h> -#include <stdio.h> -#include <stdlib.h> - -struct Seobeo_Route_Struct { - char *method; // "GET", "POST", "PUT", "DELETE" - char *path_pattern; // "/v1/users/:id/posts/:post_id" - Seobeo_Route_Handler handler; - - // Pre-parsed path segments for efficient matching - char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"] - boolean *is_param; // [false, false, true, false, true] - size_t segment_count; -}; - -static Seobeo_Route *g_routes = NULL; - -void Seobeo_Router_Init() -{ - Dowa_Array_Reserve(g_routes, 20); -} - -void Seobeo_Router_Register(const char *method, const char *path_pattern, Seobeo_Route_Handler handler) -{ - Seobeo_Route route = {0}; - - route.method = strdup(method); - route.path_pattern = strdup(path_pattern); - route.handler = handler; - route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL); - route.segment_count = Dowa_Array_Length(route.path_segments); - route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count); - - for (size_t i = 0; i < route.segment_count; i++) - route.is_param[i] = (route.path_segments[i][0] == ':'); - - Dowa_Array_Push(g_routes, route); -} - -// Match route and extract path parameters -static boolean match_route_and_extract( - Seobeo_Route *route, - const char *request_path, - Seobeo_Request_Entry **pp_request_map, - Dowa_Arena *p_arena) -{ - Dowa_Arena *p_temp_arena = Dowa_Arena_Create(1024); - char **request_segments = Dowa_String_Split(request_path, "/", strlen(request_path), 1, p_temp_arena); - size_t request_segment_count = Dowa_Array_Length(request_segments); - // Check segment count matches - if (request_segment_count != route->segment_count) - { - Dowa_Arena_Free(p_temp_arena); - return FALSE; - } - - for (size_t i = 0; i < route->segment_count; i++) - { - // parameters - if (route->is_param[i]) - { - char *param_name = route->path_segments[i]; // e.g., ":id" - char *param_value = request_segments[i]; // e.g., "123" - - // Should Copy to arena - char *key = Dowa_String_Copy_Arena(param_name, p_arena); - char *value = Dowa_String_Copy_Arena(param_value, p_arena); - Dowa_HashMap_Push_Arena(*pp_request_map, key, value, p_arena); - } - else - { - // Does not match. - if (strcmp(route->path_segments[i], request_segments[i]) != 0) - { - Dowa_Arena_Free(p_temp_arena); - return FALSE; - } - } - } - - Dowa_Arena_Free(p_temp_arena); - return TRUE; -} - -Seobeo_Route_Handler Seobeo_Router_Find_Handler(const char *method, - const char *path, - Seobeo_Request_Entry **pp_request_map, - Dowa_Arena *p_arena) { - if (g_routes == NULL) - { - return NULL; - } - - size_t route_count = Dowa_Array_Length(g_routes); - for (size_t i = 0; i < route_count; i++) - { - Seobeo_Route *route = &g_routes[i]; - if (strcmp(route->method, method) != 0) - { - continue; - } - - if (match_route_and_extract(route, path, pp_request_map, p_arena)) - { - return route->handler; - } - } - - return NULL; -} - -void Seobeo_Router_Send_Response( - Seobeo_Handle *p_handle, - Seobeo_Request_Entry *p_response_map, - Dowa_Arena *p_arena) -{ - if (p_response_map == NULL) - { - char *header = Dowa_Arena_Allocate(p_arena, 1024); - Seobeo_Web_Header_Generate(header, HTTP_INTERNAL_ERROR, "text/plain", 21); - Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); - Seobeo_Handle_Queue(p_handle, (uint8_t*)"Internal Server Error", 21); - Seobeo_Handle_Flush(p_handle); - return; - } - - // Header - int status = HTTP_OK; - void *p_status_kv = Dowa_HashMap_Get_Ptr(p_response_map, "status"); - if (p_status_kv) - { - const char *status_str = ((Seobeo_Request_Entry*)p_status_kv)->value; - status = atoi(status_str); - } - - // Body - const char *body = ""; - void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body"); - if (p_body_kv) - { - body = ((Seobeo_Request_Entry*)p_body_kv)->value; - } - - // Default text plain - const char *content_type = "text/html"; - void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type"); - if (p_content_type_kv) - { - content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value; - } - - // Check for custom content-length (for binary data) - size_t body_length = strlen(body); - void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length"); - if (p_content_length_kv) - { - const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value; - body_length = atoi(content_length_str); - } - - // All other types are default thoguht to be headers. - char *header = Dowa_Arena_Allocate(p_arena, 4096); - Seobeo_Web_Header_Generate(header, status, content_type, body_length); - for (int i = 0; i < Dowa_Array_Length(p_response_map); i++) - { - if ( - strstr(p_response_map[i].key, "status") || - strstr(p_response_map[i].key, "body") || - strstr(p_response_map[i].key, "content-type") || - strstr(p_response_map[i].key, "content-length") // Skip custom content-length - ) - continue; - - int32 current_header_len = strlen(header); - char *temp = malloc(sizeof(char) * 1024); - sprintf(temp, "%s: %s\r\n\r\n", p_response_map[i].key, p_response_map[i].value); - memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp)); - free(temp); - } - - Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); - Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); // Use actual body length - Seobeo_Handle_Flush(p_handle); -} - -void Seobeo_Router_Destroy() -{ - if (g_routes == NULL) - return; - - size_t route_count = Dowa_Array_Length(g_routes); - for (size_t i = 0; i < route_count; i++) - { - Seobeo_Route *route = &g_routes[i]; - if (route->method) free(route->method); - if (route->path_pattern) free(route->path_pattern); - if (route->path_segments) Dowa_Array_Free(route->path_segments); - if (route->is_param) free(route->is_param); - } - Dowa_Array_Free(g_routes); - g_routes = NULL; -}
--- a/seobeo/s_ssl.c Fri Jan 02 19:11:35 2026 -0800 +++ b/seobeo/s_ssl.c Fri Jan 02 19:16:17 2026 -0800 @@ -17,12 +17,12 @@ { if (!p_handle) return -1; - printf("USE SSL\n\n"); + Seobeo_Log(SEOBEO_INFO, "Using SSL/TLS encryption\n"); Seobeo_Web_SSL_Init(); p_handle->ssl_ctx = SSL_CTX_new(TLS_client_method()); if (!p_handle->ssl_ctx) { - fprintf(stderr, "SSL_CTX_new failed\n"); + Seobeo_Log(SEOBEO_ERROR, "SSL_CTX_new failed\n"); ERR_print_errors_fp(stderr); return -1; } @@ -32,7 +32,7 @@ p_handle->ssl = SSL_new(p_handle->ssl_ctx); if (!p_handle->ssl) { - fprintf(stderr, "SSL_new failed\n"); + Seobeo_Log(SEOBEO_ERROR, "SSL_new failed\n"); ERR_print_errors_fp(stderr); SSL_CTX_free(p_handle->ssl_ctx); p_handle->ssl_ctx = NULL; @@ -47,7 +47,7 @@ if (SSL_connect(p_handle->ssl) != 1) { - fprintf(stderr, "SSL_connect failed\n"); + Seobeo_Log(SEOBEO_ERROR, "SSL_connect failed\n"); ERR_print_errors_fp(stderr); SSL_free(p_handle->ssl); SSL_CTX_free(p_handle->ssl_ctx); @@ -126,7 +126,7 @@ int Seobeo_SSL_Setup_Client(Seobeo_Handle *p_handle, const char *host, int socket_fd) { (void)p_handle; (void)host; (void)socket_fd; - fprintf(stderr, "SSL support not compiled in\n"); + Seobeo_Log(SEOBEO_WARNING, "SSL support not compiled in\n"); return -1; } void Seobeo_SSL_Cleanup(Seobeo_Handle *p_handle) { (void)p_handle; }
--- a/seobeo/s_web.c Fri Jan 02 19:11:35 2026 -0800 +++ b/seobeo/s_web.c Fri Jan 02 19:16:17 2026 -0800 @@ -93,23 +93,22 @@ void Seobeo_Web_HandleClientRequest(Seobeo_Handle *p_cli_handle, Seobeo_Cache_Entry *p_html_cache) { + Seobeo_Log(SEOBEO_INFO, "Client is from %s\n", p_cli_handle->host); Dowa_Arena *p_request_arena = Dowa_Arena_Create(1*1024*1024); // 1MB for request parsing if (!p_request_arena) { perror("Dowa_Arena_Create request"); goto clean_up; } - Dowa_Arena *p_response_arena = Dowa_Arena_Create(5*1024*1024); // 1MB for response + Dowa_Arena *p_response_arena = Dowa_Arena_Create(5*1024*1024); // 5MB for response if (!p_response_arena) { perror("Dowa_Arena_Create response"); goto clean_up; } void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)1024*5); // 5Kb if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; } - // Parse request headers into hashmap using arena Seobeo_Request_Entry *p_req_map = NULL; int parse_result = Seobeo_Web_Header_Parse(p_cli_handle, &p_req_map, p_request_arena); - // Treat EAGAIN (return code 1) as success - headers were parsed, body may still be coming if (parse_result != 0 && parse_result != 1) { - printf("ERROR: Seobeo_Web_Header_Parse failed with code %d\n", parse_result); + Seobeo_Log(SEOBEO_ERROR, "Seobeo_Web_Header_Parse failed with code %d\n", parse_result); fflush(stdout); Seobeo_Web_Header_Generate(p_response_header, HTTP_BAD_REQUEST, @@ -121,19 +120,18 @@ goto clean_up; } - printf("DEBUG: Parse completed with code %d\n", parse_result); + Seobeo_Log(SEOBEO_DEBUG, "Parse completed with code %d\n", parse_result); fflush(stdout); - // Extract method (GET, POST, etc.) void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method"); const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL; - printf("DEBUG: Parsed request, method=%s\n", method ? method : "NULL"); + Seobeo_Log(SEOBEO_DEBUG, "Parsed request, method=%s\n", method ? method : "NULL"); fflush(stdout); if (!method) { - printf("ERROR: No HTTP method found in request\n"); + Seobeo_Log(SEOBEO_ERROR, "No HTTP method found in request\n"); fflush(stdout); Seobeo_Web_Header_Generate(p_response_header, HTTP_BAD_REQUEST, @@ -225,7 +223,7 @@ else if (strstr(file_path, ".ico")) mime = "image/x-icon"; else if (strstr(file_path, ".json")) mime = "application/json"; - printf("File path: %s\nBody Size: %zu\n", file_path, body_size); + Seobeo_Log(SEOBEO_DEBUG, "File path: %s\nBody Size: %zu\n", file_path, body_size); Seobeo_Web_Header_Generate(p_response_header, HTTP_OK, @@ -239,7 +237,7 @@ (const uint8*)file_content, (uint32)body_size); Seobeo_Handle_Flush(p_cli_handle); - printf("DONE\n\n\n"); + Seobeo_Log(SEOBEO_DEBUG, "Request handled successfully\n"); } else { @@ -254,7 +252,7 @@ goto clean_up; clean_up: - printf("clean up\n\n"); + Seobeo_Log(SEOBEO_INFO, "Clean up all Arenas\n"); if (p_cli_handle) Seobeo_Handle_Destroy(p_cli_handle); if (p_request_arena) @@ -295,16 +293,14 @@ if (first_line_end) { size_t first_line_len = first_line_end - buf; - printf("DEBUG: Request line (first %zu bytes): '", first_line_len > 200 ? 200 : first_line_len); - fwrite(buf, 1, first_line_len > 200 ? 200 : first_line_len, stdout); - printf("'\n"); + Seobeo_Log(SEOBEO_DEBUG, "Request line (first %zu bytes)\n", first_line_len > 200 ? 200 : first_line_len); fflush(stdout); } // This seems kinda bad ? char method[16], path[256], version[16]; int scan_result = sscanf(buf, "%15s %255s %15s", method, path, version); - printf("DEBUG: sscanf returned %d (method='%s', path='%s', version='%s')\n", + Seobeo_Log(SEOBEO_DEBUG, "sscanf returned %d (method='%s', path='%s', version='%s')\n", scan_result, scan_result >= 1 ? method : "N/A", scan_result >= 2 ? path : "N/A", @@ -313,29 +309,29 @@ if (scan_result != 3) { - printf("ERROR: Failed to parse request line\n"); + Seobeo_Log(SEOBEO_ERROR, "Failed to parse request line\n"); fflush(stdout); return -1; } // Copy strings to arena and store in hashmap - printf("DEBUG: Allocating method_copy\n"); + Seobeo_Log(SEOBEO_DEBUG, "Allocating method_copy\n"); fflush(stdout); char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1); - if (!method_copy) { printf("ERROR: Failed to allocate method_copy\n"); return -1; } + if (!method_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate method_copy\n"); return -1; } strcpy(method_copy, method); - printf("DEBUG: Allocating version_copy\n"); + Seobeo_Log(SEOBEO_DEBUG, "Allocating version_copy\n"); fflush(stdout); char *version_copy = Dowa_Arena_Allocate(p_arena, strlen(version) + 1); - if (!version_copy) { printf("ERROR: Failed to allocate version_copy\n"); return -1; } + if (!version_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate version_copy\n"); return -1; } strcpy(version_copy, version); - printf("DEBUG: Pushing HTTP_Method and Version to map\n"); + Seobeo_Log(SEOBEO_DEBUG, "Pushing HTTP_Method and Version to map\n"); fflush(stdout); Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena); Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena); - printf("DEBUG: Map now has %zu entries\n", Dowa_Array_Length(*pp_map)); + Seobeo_Log(SEOBEO_DEBUG, "Map now has %zu entries\n", Dowa_Array_Length(*pp_map)); fflush(stdout); // 1) Separate raw path and query string @@ -437,14 +433,14 @@ const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; size_t body_len = atoi(content_length_str); - printf("DEBUG: Content-Length=%zu, reading body in chunks...\n", body_len); + Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len); fflush(stdout); // Allocate buffer for entire body char *body = Dowa_Arena_Allocate(p_arena, body_len + 1); if (!body) { - printf("ERROR: Failed to allocate %zu bytes for body\n", body_len); + Seobeo_Log(SEOBEO_ERROR, "Failed to allocate %zu bytes for body\n", body_len); fflush(stdout); return -1; } @@ -464,7 +460,7 @@ total_read += to_copy; Seobeo_Handle_Consume(p_handle, (uint32)to_copy); - printf("DEBUG: Copied %zu bytes, total %zu/%zu\n", to_copy, total_read, body_len); + Seobeo_Log(SEOBEO_DEBUG, "Copied %zu bytes, total %zu/%zu\n", to_copy, total_read, body_len); fflush(stdout); } @@ -474,7 +470,7 @@ int r = Seobeo_Handle_Read(p_handle); if (r < 0) { - printf("ERROR: Read failed with %d\n", r); + Seobeo_Log(SEOBEO_ERROR, "Read failed with %d\n", r); fflush(stdout); return -1; } @@ -489,7 +485,7 @@ } body[body_len] = '\0'; - printf("DEBUG: Body fully received (%zu bytes)\n", body_len); + Seobeo_Log(SEOBEO_DEBUG, "Body fully received (%zu bytes)\n", body_len); fflush(stdout); // Body is arena-allocated @@ -529,12 +525,12 @@ Seobeo_Stream_Handle_Server_Create(NULL, port); if (p_server_handle->socket < 0) return 1; - printf("Listening on port %s\n", port); + Seobeo_Log(SEOBEO_INFO, "Listening on port %s\n", port); // Fork‐based fallback if (mode == SEOBEO_MODE_FORK) { - printf("FORK MODE\n"); + Seobeo_Log(SEOBEO_INFO, "Server mode: FORK\n"); struct sigaction sa; sa.sa_handler = SigchildHandler; sigemptyset(&sa.sa_mask); @@ -558,7 +554,7 @@ if (mode == SEOBEO_MODE_EDGE) { - printf("EDGE MODE\n"); + Seobeo_Log(SEOBEO_INFO, "Server mode: EDGE\n"); Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache); } @@ -600,7 +596,7 @@ while (1) { int n = Seobeo_Handle_Read(h); - printf("Size: %d\n", n); + Seobeo_Log(SEOBEO_DEBUG, "Received size: %d bytes\n", n); if (n > 0) { // TODO: Maybe directly use arena inside of the struct? idk if that is useful or not... @@ -615,7 +611,7 @@ { // Debug // peer closed; we’ve got everything - printf("\n\nCLOSED\n\n"); + Seobeo_Log(SEOBEO_DEBUG, "Connection closed by client\n"); break; }else { @@ -626,8 +622,237 @@ } // Debug - printf("Request body %s", p_request_body); + Seobeo_Log(SEOBEO_DEBUG, "Request body: %s\n", p_request_body); Dowa_Arena_Free(p_request_arena); Seobeo_Handle_Destroy(h); return 0; } + +/* Router logic */ +struct Seobeo_Route_Struct { + char *method; // "GET", "POST", "PUT", "DELETE" + char *path_pattern; // "/v1/users/:id/posts/:post_id" + Seobeo_Route_Handler handler; + + // Pre-parsed path segments for efficient matching + char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"] + boolean *is_param; // [false, false, true, false, true] + size_t segment_count; +}; + +static Seobeo_Route *g_routes = NULL; + +void Seobeo_Router_Init() +{ + Dowa_Array_Reserve(g_routes, 20); +} + +void Seobeo_Router_Register(const char *method, const char *path_pattern, Seobeo_Route_Handler handler) +{ + Seobeo_Route route = {0}; + + route.method = strdup(method); + route.path_pattern = strdup(path_pattern); + route.handler = handler; + route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL); + route.segment_count = Dowa_Array_Length(route.path_segments); + route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count); + + for (size_t i = 0; i < route.segment_count; i++) + route.is_param[i] = (route.path_segments[i][0] == ':'); + + Dowa_Array_Push(g_routes, route); +} + +// Match route and extract path parameters +static boolean match_route_and_extract( + Seobeo_Route *route, + const char *request_path, + Seobeo_Request_Entry **pp_request_map, + Dowa_Arena *p_arena) +{ + Dowa_Arena *p_temp_arena = Dowa_Arena_Create(1024); + char **request_segments = Dowa_String_Split(request_path, "/", strlen(request_path), 1, p_temp_arena); + size_t request_segment_count = Dowa_Array_Length(request_segments); + // Check segment count matches + if (request_segment_count != route->segment_count) + { + Dowa_Arena_Free(p_temp_arena); + return FALSE; + } + + for (size_t i = 0; i < route->segment_count; i++) + { + // parameters + if (route->is_param[i]) + { + char *param_name = route->path_segments[i]; // e.g., ":id" + char *param_value = request_segments[i]; // e.g., "123" + + // Should Copy to arena + char *key = Dowa_String_Copy_Arena(param_name, p_arena); + char *value = Dowa_String_Copy_Arena(param_value, p_arena); + Dowa_HashMap_Push_Arena(*pp_request_map, key, value, p_arena); + } + else + { + // Does not match. + if (strcmp(route->path_segments[i], request_segments[i]) != 0) + { + Dowa_Arena_Free(p_temp_arena); + return FALSE; + } + } + } + + Dowa_Arena_Free(p_temp_arena); + return TRUE; +} + +Seobeo_Route_Handler Seobeo_Router_Find_Handler(const char *method, + const char *path, + Seobeo_Request_Entry **pp_request_map, + Dowa_Arena *p_arena) { + if (g_routes == NULL) + { + return NULL; + } + + size_t route_count = Dowa_Array_Length(g_routes); + for (size_t i = 0; i < route_count; i++) + { + Seobeo_Route *route = &g_routes[i]; + if (strcmp(route->method, method) != 0) + { + continue; + } + + if (match_route_and_extract(route, path, pp_request_map, p_arena)) + { + return route->handler; + } + } + + return NULL; +} + +void Seobeo_Router_Send_Response( + Seobeo_Handle *p_handle, + Seobeo_Request_Entry *p_response_map, + Dowa_Arena *p_arena) +{ + if (p_response_map == NULL) + { + char *header = Dowa_Arena_Allocate(p_arena, 1024); + Seobeo_Web_Header_Generate(header, HTTP_INTERNAL_ERROR, "text/plain", 21); + Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); + Seobeo_Handle_Queue(p_handle, (uint8_t*)"Internal Server Error", 21); + Seobeo_Handle_Flush(p_handle); + return; + } + + // Header + int status = HTTP_OK; + void *p_status_kv = Dowa_HashMap_Get_Ptr(p_response_map, "status"); + if (p_status_kv) + { + const char *status_str = ((Seobeo_Request_Entry*)p_status_kv)->value; + status = atoi(status_str); + } + + // Body + const char *body = ""; + void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body"); + if (p_body_kv) + { + body = ((Seobeo_Request_Entry*)p_body_kv)->value; + } + + // Default text plain + const char *content_type = "text/html"; + void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type"); + if (p_content_type_kv) + { + content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value; + } + + // Check for custom content-length (for binary data) + size_t body_length = strlen(body); + void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length"); + if (p_content_length_kv) + { + const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value; + body_length = atoi(content_length_str); + } + + // All other types are default thoguht to be headers. + char *header = Dowa_Arena_Allocate(p_arena, 4096); + Seobeo_Web_Header_Generate(header, status, content_type, body_length); + for (int i = 0; i < Dowa_Array_Length(p_response_map); i++) + { + if ( + strstr(p_response_map[i].key, "status") || + strstr(p_response_map[i].key, "body") || + strstr(p_response_map[i].key, "content-type") || + strstr(p_response_map[i].key, "content-length") // Skip custom content-length + ) + continue; + + int32 current_header_len = strlen(header); + char *temp = malloc(sizeof(char) * 1024); + sprintf(temp, "%s: %s\r\n\r\n", p_response_map[i].key, p_response_map[i].value); + memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp)); + free(temp); + } + + Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); + Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); // Use actual body length + Seobeo_Handle_Flush(p_handle); +} + +void Seobeo_Router_Destroy() +{ + if (g_routes == NULL) + return; + + size_t route_count = Dowa_Array_Length(g_routes); + for (size_t i = 0; i < route_count; i++) + { + Seobeo_Route *route = &g_routes[i]; + if (route->method) free(route->method); + if (route->path_pattern) free(route->path_pattern); + if (route->path_segments) Dowa_Array_Free(route->path_segments); + if (route->is_param) free(route->is_param); + } + Dowa_Array_Free(g_routes); + g_routes = NULL; +} + +static char *Seobeo_Log_Level_String(Seobeo_Log_Level level) +{ + switch(level) { + case SEOBEO_DEBUG: return "DEBUG"; + case SEOBEO_INFO: return "INFO"; + case SEOBEO_WARNING: return "WARNING"; + case SEOBEO_ERROR: return "ERROR"; + default: return "INFO"; + } +} + +int Seobeo_Log(Seobeo_Log_Level level, const char * restrict format, ...) +{ + #ifndef SEOBEO_ENABLE_DEBUG + if (level == SEOBEO_DEBUG) + return 0; + #endif + + int result; + va_list args; + printf("[%s] ", Seobeo_Log_Level_String(level)); + va_start(args, format); + result = vprintf(format, args); + va_end(args); + + return result; +} +
--- a/seobeo/seobeo.h Fri Jan 02 19:11:35 2026 -0800 +++ b/seobeo/seobeo.h Fri Jan 02 19:16:17 2026 -0800 @@ -8,13 +8,13 @@ * Library for starting TCP, UDP server and serving static file or path. */ -// Define DIRECTORY before any includes to enable directory operations in dowa.h -#ifndef DIRECTORY -#define DIRECTORY -#endif +#include "seobeo/seobeo_internal.h" -#include <stdio.h> -#include <stdlib.h> +/* Included in dowa + #include <stdio.h> + #include <stdlib.h> +*/ +#include <stdarg.h> #include <unistd.h> #include <errno.h> #include <string.h> @@ -27,10 +27,7 @@ #include <signal.h> #include <fcntl.h> #include <pthread.h> -#include <stdatomic.h> -#include <stdbool.h> -#include "seobeo/seobeo_internal.h" #define INITIAL_BUFFER_CAPACITY 4096 @@ -73,8 +70,6 @@ extern int Seobeo_Web_Server_Start(const char *folder_path, const char *port, Seobeo_ServerMode mode, int thread_count); /* Generic HTTP GET Rquest to given host and port with path. It will mimic chrome. */ extern int Seobeo_Web_Client_Get(const char *host, const char *port, const char *path); - -// --- Router --- // /* Initialize the router system (called automatically by Seobeo_Web_Server_Start) */ extern void Seobeo_Router_Init(); /* Register an API route handler. Call before starting server. */ @@ -99,5 +94,13 @@ /* Move to read_buffer to front and we already consumed the given amount in the handle. */ extern void Seobeo_Handle_Consume(Seobeo_Handle *p_handle, uint32 consumed); /* Assign IP4 or IP6 to sockaddr. TODO: Maybe create my own struct for this? */ -extern void *Seobeo_Get_IP4_Or_IP6(struct sockaddr *sa); +extern void *Seobeo_Get_IP4_Or_IP6(struct sockaddr *sa); +/* Logging */ +typedef enum { + SEOBEO_INFO = 0, + SEOBEO_WARNING, + SEOBEO_ERROR, + SEOBEO_DEBUG, +} Seobeo_Log_Level; +extern int Seobeo_Log(Seobeo_Log_Level level, const char *format, ...); #endif
--- a/seobeo/seobeo_internal.h Fri Jan 02 19:11:35 2026 -0800 +++ b/seobeo/seobeo_internal.h Fri Jan 02 19:16:17 2026 -0800 @@ -11,10 +11,8 @@ #define SSL_TYPE void #endif -#ifndef DIRECTORY -#define DIRECTORY -#endif #include "dowa/dowa.h" +#include <stdatomic.h> typedef enum { SEOBEO_STREAM_TYPE_SERVER,