comparison 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
comparison
equal deleted inserted replaced
71:75de5903355c 72:4532ce6d9eb8
1 #include "seobeo/seobeo.h"
2 #include <string.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5
6 struct Seobeo_Route_Struct {
7 char *method; // "GET", "POST", "PUT", "DELETE"
8 char *path_pattern; // "/v1/users/:id/posts/:post_id"
9 Seobeo_Route_Handler handler;
10
11 // Pre-parsed path segments for efficient matching
12 char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"]
13 boolean *is_param; // [false, false, true, false, true]
14 size_t segment_count;
15 };
16
17 static Seobeo_Route *g_routes = NULL;
18
19 void Seobeo_Router_Init()
20 {
21 g_routes = NULL;
22 }
23
24 void Seobeo_Router_Register(const char *method, const char *path_pattern, Seobeo_Route_Handler handler)
25 {
26 Seobeo_Route route = {0};
27
28 route.method = strdup(method);
29 route.path_pattern = strdup(path_pattern);
30 route.handler = handler;
31
32 // save it as global variable
33 route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL);
34 route.segment_count = Dowa_Array_Length(route.path_segments);
35 route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count);
36
37 for (size_t i = 0; i < route.segment_count; i++)
38 route.is_param[i] = (route.path_segments[i][0] == ':');
39
40 Dowa_Array_Push(g_routes, route);
41 }
42
43 // Match route and extract path parameters
44 static boolean match_route_and_extract(
45 Seobeo_Route *route,
46 const char *request_path,
47 Seobeo_Request_Entry **pp_request_map,
48 Dowa_Arena *p_arena)
49 {
50 Dowa_Arena *p_temp_arena = Dowa_Arena_Create(1024);
51 char **request_segments = Dowa_String_Split(request_path, "/", strlen(request_path), 1, p_temp_arena);
52 size_t request_segment_count = Dowa_Array_Length(request_segments);
53 // Check segment count matches
54 if (request_segment_count != route->segment_count)
55 {
56 Dowa_Arena_Free(p_temp_arena);
57 return FALSE;
58 }
59
60 for (size_t i = 0; i < route->segment_count; i++)
61 {
62 // parameters
63 if (route->is_param[i])
64 {
65 char *param_name = route->path_segments[i]; // e.g., ":id"
66 char *param_value = request_segments[i]; // e.g., "123"
67
68 // Should Copy to arena
69 char *key = Dowa_String_Copy_Arena(param_name, p_arena);
70 char *value = Dowa_String_Copy_Arena(param_value, p_arena);
71 Dowa_HashMap_Push_Arena(*pp_request_map, key, value, p_arena);
72 }
73 else
74 {
75 // Does not match.
76 if (strcmp(route->path_segments[i], request_segments[i]) != 0)
77 {
78 Dowa_Arena_Free(p_temp_arena);
79 return FALSE;
80 }
81 }
82 }
83
84 Dowa_Arena_Free(p_temp_arena);
85 return TRUE;
86 }
87
88 Seobeo_Route_Handler Seobeo_Router_Find_Handler(const char *method,
89 const char *path,
90 Seobeo_Request_Entry **pp_request_map,
91 Dowa_Arena *p_arena) {
92 if (g_routes == NULL)
93 {
94 return NULL;
95 }
96
97 size_t route_count = Dowa_Array_Length(g_routes);
98 for (size_t i = 0; i < route_count; i++)
99 {
100 Seobeo_Route *route = &g_routes[i];
101 if (strcmp(route->method, method) != 0)
102 {
103 continue;
104 }
105
106 if (match_route_and_extract(route, path, pp_request_map, p_arena))
107 {
108 return route->handler;
109 }
110 }
111
112 return NULL;
113 }
114
115 void Seobeo_Router_Send_Response(Seobeo_Handle *p_handle,
116 Seobeo_Request_Entry *p_response_map,
117 Dowa_Arena *p_arena)
118 {
119 if (p_response_map == NULL)
120 {
121 char *header = Dowa_Arena_Allocate(p_arena, 1024);
122 Seobeo_Web_Header_Generate(header, HTTP_INTERNAL_ERROR, "text/plain", 21);
123 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header));
124 Seobeo_Handle_Queue(p_handle, (uint8_t*)"Internal Server Error", 21);
125 Seobeo_Handle_Flush(p_handle);
126 return;
127 }
128
129 // Header
130 int status = HTTP_OK;
131 void *p_status_kv = Dowa_HashMap_Get_Ptr(p_response_map, "status");
132 if (p_status_kv)
133 {
134 const char *status_str = ((Seobeo_Request_Entry*)p_status_kv)->value;
135 status = atoi(status_str);
136 }
137
138 // Body
139 const char *body = "";
140 void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body");
141 if (p_body_kv)
142 {
143 body = ((Seobeo_Request_Entry*)p_body_kv)->value;
144 }
145
146 // Default text plain
147 const char *content_type = "text/plain";
148 void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type");
149 if (p_content_type_kv)
150 {
151 content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value;
152 }
153
154 char *header = Dowa_Arena_Allocate(p_arena, 1024);
155 Seobeo_Web_Header_Generate(header, status, content_type, strlen(body));
156
157 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header));
158 Seobeo_Handle_Queue(p_handle, (uint8_t*)body, strlen(body));
159 Seobeo_Handle_Flush(p_handle);
160 }
161
162 void Seobeo_Router_Destroy()
163 {
164 if (g_routes == NULL)
165 return;
166
167 size_t route_count = Dowa_Array_Length(g_routes);
168 for (size_t i = 0; i < route_count; i++)
169 {
170 Seobeo_Route *route = &g_routes[i];
171 if (route->method) free(route->method);
172 if (route->path_pattern) free(route->path_pattern);
173 if (route->path_segments) Dowa_Array_Free(route->path_segments);
174 if (route->is_param) free(route->is_param);
175 }
176 Dowa_Array_Free(g_routes);
177 g_routes = NULL;
178 }