diff seobeo/s_web.c @ 195:f8f5004a920a

Merging back hg-web-tip
author MrJuneJune <me@mrjunejune.com>
date Tue, 27 Jan 2026 06:51:44 -0800
parents a69485d9f2e1
children
line wrap: on
line diff
--- a/seobeo/s_web.c	Sat Jan 24 06:37:43 2026 -0800
+++ b/seobeo/s_web.c	Tue Jan 27 06:51:44 2026 -0800
@@ -151,6 +151,11 @@
   void *p_conn_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Connection");
   const char *conn_header = p_conn_kv ? ((Seobeo_Request_Entry*)p_conn_kv)->value : NULL;
 
+  void *p_real_ip_kv = Dowa_HashMap_Get_Ptr(p_req_map, "X-Real-IP");
+  const char *real_ip = p_real_ip_kv ? ((Seobeo_Request_Entry*)p_real_ip_kv)->value : NULL;
+  if (!real_ip)
+    real_ip = p_cli_handle->host;
+
   if (conn_header)
   {
     if (connection_header_contains(conn_header, "close"))
@@ -185,6 +190,7 @@
 
   // --- Check for WebSocket upgrade request ---
   #ifdef SEOBEO_WEBSOCKET_SERVER
+  Seobeo_Log(SEOBEO_DEBUG, "Web socket path \n");
   if (Seobeo_WebSocket_Server_Handle_Upgrade(p_cli_handle, p_req_map, path))
   {
     Seobeo_Log(SEOBEO_INFO, "WebSocket connection established\n");
@@ -194,7 +200,15 @@
   }
   #endif
 
-  // --- Try to match API route first ---
+  // --- Try to match streaming route first ---
+  Seobeo_Stream_Handler stream_handler = Seobeo_Router_Find_Stream_Handler(method, path, &p_req_map, p_request_arena);
+  if (stream_handler != NULL)
+  {
+    stream_handler(p_cli_handle, p_req_map, p_response_arena);
+    goto clean_up_arenas;
+  }
+
+  // --- Try to match API route ---
   Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena);
   if (handler != NULL)
   {
@@ -333,7 +347,10 @@
       break;
 
     if (r == 0)
-      return 1;     // EAGAIN, try again later TODO: Add this as part of Handle struct.
+    {
+      Seobeo_Log(SEOBEO_INFO, "Waiting?\n");
+      continue;     // EAGAIN, try again later TODO: Add this as part of Handle struct.
+    }
   }
 
   // "METHOD SP PATH SP VERSION CRLF"
@@ -448,7 +465,6 @@
     char *next = strstr(line, "\r\n");
     if (!next) break;
 
-    // split at colon
     char *colon = memchr(line, ':', next - line);
     if (colon)
     {
@@ -472,7 +488,6 @@
       memcpy(val, val_start, value_len);
       val[value_len] = '\0';
 
-      // Both key and value are arena-allocated, hashmap will use them
       Dowa_HashMap_Push_Arena(*pp_map, key, val, p_arena);
     }
 
@@ -490,7 +505,6 @@
 
     Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len);
 
-    // Allocate buffer for entire body
     char *body = Dowa_Arena_Allocate(p_arena, body_len + 1);
     if (!body)
     {
@@ -606,6 +620,7 @@
   char *method; // "GET", "POST", "PUT", "DELETE"
   char *path_pattern; // "/v1/users/:id/posts/:post_id"
   Seobeo_Route_Handler handler;
+  Seobeo_Stream_Handler stream_handler; // For streaming responses
 
   // Pre-parsed path segments for efficient matching
   char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"]
@@ -627,6 +642,25 @@
   route.method = strdup(method);
   route.path_pattern = strdup(path_pattern);
   route.handler = handler;
+  route.stream_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);
+
+  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);
+}
+
+void Seobeo_Router_Register_Stream(const char *method, const char *path_pattern, Seobeo_Stream_Handler handler)
+{
+  Seobeo_Route route = {0};
+
+  route.method = strdup(method);
+  route.path_pattern = strdup(path_pattern);
+  route.handler = NULL;
+  route.stream_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);
@@ -709,6 +743,29 @@
   return NULL;
 }
 
+Seobeo_Stream_Handler Seobeo_Router_Find_Stream_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->stream_handler && match_route_and_extract(route, path, pp_request_map, p_arena))
+      return route->stream_handler;
+  }
+
+  return NULL;
+}
+
 void Seobeo_Router_Send_Response(
     Seobeo_Handle *p_handle,
     Seobeo_Request_Entry *p_response_map,
@@ -744,24 +801,23 @@
   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;
-  }
 
   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;
-  }
 
-  size_t body_length = strlen(body);
+  // TODO: Update this to be integer
+  size_t body_length;
   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);
   }
+  else
+    body_length = strlen(body);
 
   char *header = Dowa_Arena_Allocate(p_arena, 4096);
   Seobeo_Web_Header_Generate_KeepAlive(header, status, content_type, body_length, keep_alive);
@@ -782,6 +838,8 @@
     free(temp);
   }
 
+  printf("hEADER %s\n", header);
+
   Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header));
   Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length);
   Seobeo_Handle_Flush(p_handle);
@@ -804,3 +862,44 @@
   Dowa_Array_Free(g_routes);
   g_routes = NULL;
 }
+
+// Written by AI. I don't know what it does.
+void Seobeo_Url_Decode(char *dst, const char *src)
+{
+  char a, b;
+  while (*src) {
+    /* Check if we have a % followed by two valid hex characters */
+    if (*src == '%' && src[1] && src[2]) {
+      a = src[1];
+      b = src[2];
+
+      /* Manual isxdigit check and conversion for 'a' */
+      int a_val = -1;
+      if (a >= '0' && a <= '9')    a_val = a - '0';
+      else if (a >= 'a' && a <= 'f') a_val = a - 'a' + 10;
+      else if (a >= 'A' && a <= 'F') a_val = a - 'A' + 10;
+
+      /* Manual isxdigit check and conversion for 'b' */
+      int b_val = -1;
+      if (b >= '0' && b <= '9')    b_val = b - '0';
+      else if (b >= 'a' && b <= 'f') b_val = b - 'a' + 10;
+      else if (b >= 'A' && b <= 'F') b_val = b - 'A' + 10;
+
+      /* If both were valid hex, combine them */
+      if (a_val != -1 && b_val != -1) {
+        *dst++ = (char)((a_val << 4) | b_val);
+        src += 3;
+        continue;
+      }
+    }
+
+    /* Handle '+' as space, otherwise copy character literally */
+    if (*src == '+') {
+      *dst++ = ' ';
+    } else {
+      *dst++ = *src;
+    }
+    src++;
+  }
+  *dst = '\0';
+}