Mercurial
diff seobeo/s_web.c @ 96:70401cf61e97
[Seobeo] Added logging.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 02 Jan 2026 19:16:17 -0800 |
| parents | 655ea0b661fd |
| children | 65e5a5b89a4e |
line wrap: on
line diff
--- 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; +} +