Mercurial
diff seobeo/s_router.c @ 72:4532ce6d9eb8
[Seobeo] Added router to the server logic. Few dowa string manipulation logics.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Mon, 29 Dec 2025 07:50:07 -0800 |
| parents | |
| children | e7bf9e002850 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/s_router.c Mon Dec 29 07:50:07 2025 -0800 @@ -0,0 +1,178 @@ +#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() +{ + g_routes = NULL; +} + +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; + + // save it as global variable + 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/plain"; + 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; + } + + char *header = Dowa_Arena_Allocate(p_arena, 1024); + Seobeo_Web_Header_Generate(header, status, content_type, strlen(body)); + + Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); + Seobeo_Handle_Queue(p_handle, (uint8_t*)body, strlen(body)); + 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; +}