diff seobeo/s_web.c @ 257:609d3c6aff4e

[seobeo] Add persistent SSE streams Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Tue, 04 Aug 2026 16:49:11 -0700
parents 117c4d53c9a4
children 1f9877b637e9
line wrap: on
line diff
--- a/seobeo/s_web.c	Tue Aug 04 16:49:01 2026 -0700
+++ b/seobeo/s_web.c	Tue Aug 04 16:49:11 2026 -0700
@@ -136,7 +136,7 @@
     }
     token = strtok(NULL, ", \t");
   }
-  free(copy);
+  Dowa_Free(copy);
   return found;
 }
 
@@ -243,6 +243,29 @@
   #endif
 
   // --- Try to match streaming route first ---
+  Seobeo_SSE_Handler sse_handler = Seobeo_Router_Find_SSE_Handler(
+      method,
+      path,
+      &p_req_map,
+      p_request_arena);
+  if (sse_handler != NULL)
+  {
+    Seobeo_SSE_Stream *p_stream = Seobeo_SSE_Server_Attach(
+        p_cli_handle,
+        path);
+    if (p_stream)
+    {
+      sse_handler(p_stream, p_req_map, p_response_arena);
+      should_keep_alive = Seobeo_SSE_Is_Open(p_stream);
+    }
+    else
+    {
+      should_keep_alive = FALSE;
+    }
+    goto clean_up_arenas;
+  }
+
+  // --- Try to match generic streaming route ---
   Seobeo_Stream_Handler stream_handler = Seobeo_Router_Find_Stream_Handler(method, path, &p_req_map, p_request_arena);
   if (stream_handler != NULL)
   {
@@ -356,7 +379,7 @@
                         (const uint8*)file_content,
                         (uint32)body_size);
     Seobeo_Handle_Flush(p_cli_handle);
-    free(file_content);
+    Dowa_Free(file_content);
   }
   else
   {
@@ -690,6 +713,7 @@
   char *path_pattern; // "/v1/users/:id/posts/:post_id"
   Seobeo_Route_Handler handler;
   Seobeo_Stream_Handler stream_handler; // For streaming responses
+  Seobeo_SSE_Handler sse_handler;
 
   // Pre-parsed path segments for efficient matching
   char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"]
@@ -712,6 +736,7 @@
   route.path_pattern = strdup(path_pattern);
   route.handler = handler;
   route.stream_handler = NULL;
+  route.sse_handler = NULL;
   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);
@@ -730,6 +755,7 @@
   route.path_pattern = strdup(path_pattern);
   route.handler = NULL;
   route.stream_handler = handler;
+  route.sse_handler = NULL;
   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);
@@ -740,6 +766,33 @@
   Dowa_Array_Push(g_routes, route);
 }
 
+void Seobeo_Router_Register_SSE(
+    const char *path_pattern,
+    Seobeo_SSE_Handler handler)
+{
+  Seobeo_Route route = {0};
+
+  route.method = strdup("GET");
+  route.path_pattern = strdup(path_pattern);
+  route.handler = NULL;
+  route.stream_handler = NULL;
+  route.sse_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);
+}
+
 static boolean match_route_and_extract(
   Seobeo_Route *route,
   const char *request_path,
@@ -759,20 +812,8 @@
 
   for (size_t i = 0; i < route->segment_count; i++)
   {
-    // parameters
-    if (route->is_param[i])
+    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);
@@ -780,7 +821,16 @@
       }
     }
   }
-  
+
+  for (size_t i = 0; i < route->segment_count; i++)
+  {
+    if (!route->is_param[i])
+      continue;
+    char *key = Dowa_String_Copy_Arena(route->path_segments[i], p_arena);
+    char *value = Dowa_String_Copy_Arena(request_segments[i], p_arena);
+    Dowa_HashMap_Push_Arena(*pp_request_map, key, value, p_arena);
+  }
+
   Dowa_Arena_Free(p_temp_arena);
   return TRUE;
 }
@@ -835,6 +885,34 @@
   return NULL;
 }
 
+Seobeo_SSE_Handler Seobeo_Router_Find_SSE_Handler(
+    const char *method,
+    const char *path,
+    Seobeo_Request_Entry **pp_request_map,
+    Dowa_Arena *p_arena)
+{
+  if (g_routes == NULL || method == NULL || path == 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 (route->sse_handler &&
+        match_route_and_extract(
+            route,
+            path,
+            pp_request_map,
+            p_arena))
+    {
+      return route->sse_handler;
+    }
+  }
+  return NULL;
+}
+
 void Seobeo_Router_Send_Response(
     Seobeo_Handle *p_handle,
     Seobeo_Request_Entry *p_response_map,
@@ -958,6 +1036,7 @@
 
 void Seobeo_Router_Destroy()
 {
+  Seobeo_SSE_Server_Destroy();
   if (g_routes == NULL)
     return;
 
@@ -965,10 +1044,10 @@
   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->method) Dowa_Free(route->method);
+    if (route->path_pattern) Dowa_Free(route->path_pattern);
     if (route->path_segments) Dowa_Array_Free(route->path_segments);
-    if (route->is_param) free(route->is_param);
+    if (route->is_param) Dowa_Free(route->is_param);
   }
   Dowa_Array_Free(g_routes);
   g_routes = NULL;