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;
+}