diff seobeo/s_web.c @ 71:75de5903355c

Giagantic changes that update Dowa library to be more align with stb style array and hashmap. Updated Seobeo to be caching on server side instead of file level caching. Deleted bunch of things I don't really use.
author June Park <parkjune1995@gmail.com>
date Sun, 28 Dec 2025 20:34:22 -0800
parents 6626ec933933
children 4532ce6d9eb8
line wrap: on
line diff
--- a/seobeo/s_web.c	Thu Dec 25 20:10:46 2025 -0800
+++ b/seobeo/s_web.c	Sun Dec 28 20:34:22 2025 -0800
@@ -1,5 +1,8 @@
 #include "seobeo/seobeo.h"
 
+// Global folder path for serving files
+static char g_folder_path[512] = ".";
+
 int Seobeo_Web_GenerateRequestHeader(void *buffer, const char *host, 
                                      const char *path) 
 {
@@ -26,8 +29,39 @@
   );
 }
 
+// Load file from disk and cache it
+static char* Seobeo_Web_LoadFile(const char *file_path, size_t *p_file_size)
+{
+  char full_path[1024];
+  snprintf(full_path, sizeof(full_path), "%s/%s", g_folder_path, file_path);
+
+  FILE *p_file = fopen(full_path, "rb");
+  if (!p_file)
+    return NULL;
+
+  fseek(p_file, 0, SEEK_END);
+  size_t file_size = ftell(p_file);
+  fseek(p_file, 0, SEEK_SET);
+
+  char *p_content = (char*)malloc(file_size + 1);
+  if (!p_content)
+  {
+    fclose(p_file);
+    return NULL;
+  }
+
+  fread(p_content, 1, file_size, p_file);
+  p_content[file_size] = '\0';
+  fclose(p_file);
+
+  if (p_file_size)
+    *p_file_size = file_size;
+
+  return p_content;
+}
+
 void Seobeo_Web_Header_Generate(void *buffer, int status,
-                                       const char *content_type, const int content_length) 
+                                       const char *content_type, const int content_length)
 {
   const char *status_text;
   switch(status)
@@ -56,22 +90,20 @@
 }
 
 void Seobeo_Web_HandleClientRequest(Seobeo_Handle  *p_cli_handle,
-                                    Dowa_HashMap   *p_html_cache)
+                                    Seobeo_Cache_Entry *p_html_cache)
 {
-  printf("p_cli_handle: %p", p_cli_handle);
-  Dowa_HashEntry *entry = NULL;
-  Dowa_HashMap *p_current = p_html_cache;
-  char *slash;
+  Dowa_Arena *p_request_arena = Dowa_Arena_Create(1*1024*1024); // 1MB for request parsing
+  if (!p_request_arena) { perror("Dowa_Arena_Create request"); goto clean_up; }
 
-  Dowa_Arena *p_response_arena = Dowa_Arena_Create(1*1024*1024);
-  if (!p_response_arena) { perror("Dowa_Arena_Initialize"); goto clean_up; }
+  Dowa_Arena *p_response_arena = Dowa_Arena_Create(1*1024*1024); // 1MB for response
+  if (!p_response_arena) { perror("Dowa_Arena_Create response"); goto clean_up; }
 
-  void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)2048);
+  void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)1024*5); // 5Kb
   if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; }
 
-  // Parse request headers into hashmap
-  Dowa_HashMap *p_req_map = Dowa_HashMap_Create_With_Arena(100, p_response_arena);
-  if (Seobeo_Web_Header_Parse(p_cli_handle, p_req_map) != 0)
+  // Parse request headers into hashmap using arena
+  Seobeo_Request_Entry *p_req_map = NULL;
+  if (Seobeo_Web_Header_Parse(p_cli_handle, &p_req_map, p_request_arena) != 0)
   {
     Seobeo_Web_Header_Generate(p_response_header,
                                HTTP_BAD_REQUEST,
@@ -81,13 +113,12 @@
                         (uint32)strlen(p_response_header));
     Seobeo_Handle_Flush(p_cli_handle);
     goto clean_up;
-    return;
   }
 
-  // Dowa_HashMap_Print(p_req_map);
+  // Extract method (GET, POST, etc.)
+  void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method");
+  const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL;
 
-  // Extract method (GET, POST, etc.)
-  const char *method = (const char*)Dowa_HashMap_Get(p_req_map, "HTTP_Method");
   if (!method)
   {
     Seobeo_Web_Header_Generate(p_response_header,
@@ -100,19 +131,12 @@
     goto clean_up;
   }
 
-  // --- Separate GET map for caching or routing ---
-  Dowa_HashMap *p_get_map = Dowa_HashMap_Create(64);
-  if (!p_get_map)
-  {
-    perror("Dowa_HashMap_Create (p_get_map)");
-    goto clean_up;
-  }
-
   // --- Handle different HTTP methods ---
   if (strcmp(method, "GET") == 0)
   {
-    const char *path = (const char*)Dowa_HashMap_Get(p_req_map, "Path");
-    char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)512);
+    void *p_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Path");
+    const char *path = p_kv ? ((Seobeo_Request_Entry*)p_kv)->value : NULL;
+    char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)5 * 1024); // 5Kb only for path
 
     if (!path || strcmp(path, "/") == 0)
     {
@@ -134,37 +158,28 @@
       }
     }
 
-    // Store path for GET handling map
-    Dowa_HashMap_Push_Value(p_get_map, "Path", file_path, strlen(file_path) + 1);
-
-    // Walk through nested maps to find content
-    while ((slash = strchr(file_path, '/')))
-    {
-      *slash = '\0';
-      char *dir = file_path;
-      file_path = slash + 1;
-
-      printf("Directory: %s\n", dir);
+    // Check if file is in cache, load if not
+    void *p_file_kv = Dowa_HashMap_Get_Ptr(p_html_cache, file_path);
+    const char *file_content = NULL;
+    size_t body_size = 0;
 
-      p_current = Dowa_HashMap_Get(p_current, dir);
-      if (!p_current)
+    if (p_file_kv)
+    {
+      // File is cached
+      file_content = ((Seobeo_Cache_Entry*)p_file_kv)->value;
+      body_size = strlen(file_content);
+    }
+    else
+    {
+      // Load from disk and cache
+      file_content = Seobeo_Web_LoadFile(file_path, &body_size);
+      if (file_content)
       {
-        fprintf(stderr, "No value in hashmap key: %s\n\n", dir);
-        Seobeo_Web_Header_Generate(p_response_header,
-                                   HTTP_NOT_FOUND,
-                                   "text/html", 0);
-        Seobeo_Handle_Queue(p_cli_handle,
-                            (const uint8*)p_response_header,
-                            (uint32)strlen(p_response_header));
-        Seobeo_Handle_Flush(p_cli_handle);
-        goto clean_up;
+        Dowa_HashMap_Push(p_html_cache, file_path, file_content);
       }
     }
 
-    size_t pos = Dowa_HashMap_Get_Position(p_current, file_path);
-    entry = p_current->entries[pos];
-
-    if (!entry)
+    if (!file_content)
     {
       Seobeo_Web_Header_Generate(p_response_header,
                                  HTTP_NOT_FOUND,
@@ -187,8 +202,7 @@
     else if (strstr(file_path, ".ico")) mime = "image/x-icon";
     else if (strstr(file_path, ".json")) mime = "application/json";
 
-    size_t body_size = entry->capacity;
-    printf("key: %s\nBody Size: %zu\n", entry->key, body_size);
+    printf("File path: %s\nBody Size: %zu\n", file_path, body_size);
 
     Seobeo_Web_Header_Generate(p_response_header,
                                HTTP_OK,
@@ -199,7 +213,7 @@
                         (const uint8*)p_response_header,
                         (uint32)strlen(p_response_header));
     Seobeo_Handle_Queue(p_cli_handle,
-                        (const uint8*)entry->buffer,
+                        (const uint8*)file_content,
                         (uint32)body_size);
     Seobeo_Handle_Flush(p_cli_handle);
     printf("DONE\n\n\n");
@@ -254,27 +268,32 @@
   printf("clean up\n\n");
   if (p_cli_handle)
     Seobeo_Handle_Destroy(p_cli_handle);
+  if (p_request_arena)
+    Dowa_Arena_Destroy(p_request_arena);
   if (p_response_arena)
     Dowa_Arena_Destroy(p_response_arena);
   return;
 }
 
 
-int Seobeo_Web_Header_Parse(Seobeo_Handle *p_handle, Dowa_HashMap *map)
+int Seobeo_Web_Header_Parse(Seobeo_Handle *p_handle, Seobeo_Request_Entry **pp_map, Dowa_Arena *p_arena)
 {
   // 1) Fill read_buffer until we see "\r\n\r\n"
   while (1)
   {
     int r = Seobeo_Handle_Read(p_handle);
-    if (r < 0)   return -1;   // fatal error
-    if (r == -2) return -2;   // connection closed TODO: Add this as part of Handle struct.
+    if (r < 0)
+      return -1;   // fatal error
+    if (r == -2)
+      return -2;   // connection closed TODO: Add this as part of Handle struct.
 
     if (p_handle->read_buffer_len >= 4 &&
         strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL)
     {
       break;
     }
-    if (r == 0) return 1;     // EAGAIN, try again later TODO: Add this as part of Handle struct.
+    if (r == 0)
+      return 1;     // EAGAIN, try again later TODO: Add this as part of Handle struct.
   }
 
   // 2) Parse request‐line "METHOD SP PATH SP VERSION CRLF"
@@ -289,9 +308,17 @@
     return -1;
   }
 
-  Dowa_HashMap_Push_Value_With_Type(map, "HTTP_Method",  method,  strlen(method)  + 1, DOWA_HASH_MAP_TYPE_STRING);
-  printf("Method: %s Pointer %p\n\n", Dowa_HashMap_Get(map, "HTTP_Method"), map);
-  Dowa_HashMap_Push_Value_With_Type(map, "Version", version, strlen(version) + 1, DOWA_HASH_MAP_TYPE_STRING);
+  // Copy strings to arena and store in hashmap
+  char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1);
+  if (!method_copy) return -1;
+  strcpy(method_copy, method);
+
+  char *version_copy = Dowa_Arena_Allocate(p_arena, strlen(version) + 1);
+  if (!version_copy) return -1;
+  strcpy(version_copy, version);
+
+  Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena);
+  Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena);
 
   // 1) Separate raw path and query string
   char *raw_path = path;  
@@ -305,66 +332,38 @@
   }
   
   // push only the clean path
-  Dowa_HashMap_Push_Value_With_Type(
-    map,
-    "Path",
-    raw_path,
-    strlen(raw_path) + 1,
-    DOWA_HASH_MAP_TYPE_STRING);
+  char *path_copy = Dowa_Arena_Allocate(p_arena, strlen(raw_path) + 1);
+  if (!path_copy) return -1;
+  strcpy(path_copy, raw_path);
+  Dowa_HashMap_Push_Arena(*pp_map, "Path", path_copy, p_arena);
   
-  // 2) If there *is* a query, tokenize into a sub-map
+  // 2) If there *is* a query, tokenize into the same map with "query_" prefix
   if (query_str && *query_str)
   {
-    // create nested map for GET params
-    Dowa_HashMap *p_query_map = Dowa_HashMap_Create_With_Arena(100, map->p_arena);
-
     char *cur = query_str;
     while (cur && *cur)
     {
-      // find the next '&'
       char *next_amp = strchr(cur, '&');
-      // if none, treat end-of-string as the boundary
       char *pair_end = next_amp ? next_amp : cur + strlen(cur);
-    
-      // find '=' in [cur, pair_end)
+
       char *eq = memchr(cur, '=', pair_end - cur);
-      if (eq) {
+      if (eq)
+      {
         size_t key_len = eq - cur;
         size_t val_len = pair_end - (eq + 1);
-    
-        // extract key
-        char key_buf[key_len + 1];
-        memcpy(key_buf, cur, key_len);
-        key_buf[key_len] = '\0';
-    
-        // extract value
-        char val_buf[val_len + 1];
-        memcpy(val_buf, eq + 1, val_len);
-        val_buf[val_len] = '\0';
-    
-        printf("key: '%s', value: '%s'\n", key_buf, val_buf);
-        // push into map with strlen(val_buf)+1 to include '\0'
-        Dowa_HashMap_Push_Value_With_Type(
-          p_query_map,
-          key_buf,
-          val_buf,
-          (uint32_t)(val_len + 1),
-          DOWA_HASH_MAP_TYPE_STRING);
+
+        char key_buf[256];
+        snprintf(key_buf, sizeof(key_buf), "query_%.*s", (int)key_len, cur);
+
+        char *val_copy = Dowa_Arena_Allocate(p_arena, val_len + 1);
+        if (!val_copy) return -1;
+        memcpy(val_copy, eq + 1, val_len);
+        val_copy[val_len] = '\0';
+
+        Dowa_HashMap_Push_Arena(*pp_map, key_buf, val_copy, p_arena);
       }
-    
-      // advance past '&' if present, else end loop
+
       cur = next_amp ? next_amp + 1 : NULL;
-    } 
-    if (
-        Dowa_HashMap_Push_Value_With_Type_NoCopy(
-          map,
-          "QueryParams",
-          p_query_map,
-          sizeof(p_query_map),
-          DOWA_HASH_MAP_TYPE_HASHMAP) == -1
-        )
-    {
-      printf("Something went wrong...\n\n");
     }
   }
 
@@ -394,34 +393,31 @@
         value_len--;
       }
 
-      char *key = malloc(key_len + 1);
+      char *key = Dowa_Arena_Allocate(p_arena, key_len + 1);
+      if (!key) return -1;
       memcpy(key, line, key_len);
       key[key_len] = '\0';
 
-      char *val = malloc(value_len + 1);
+      char *val = Dowa_Arena_Allocate(p_arena, value_len + 1);
+      if (!val) return -1;
       memcpy(val, val_start, value_len);
       val[value_len] = '\0';
 
-      Dowa_HashMap_Push_Value_With_Type(map, key, val, value_len + 1, DOWA_HASH_MAP_TYPE_STRING);
-
-      // printf("Capacity: %d, Length: %d ", (int)map->p_arena->capacity, (int)map->p_arena->offset);
-      // printf("value_len: %d, key: %s value %s position: %d\n\n", (int)value_len + 1, key, val, Dowa_HashMap_Get_Position(map, key));
-      // printf("Method: %s Position: %d Pointer %p\n\n", Dowa_HashMap_Get(map, "HTTP_Method"), Dowa_HashMap_Get_Position(map, "HTTP_Method"), map);
-
-      Dowa_Free(key);
-      Dowa_Free(val);
+      // Both key and value are arena-allocated, hashmap will use them
+      Dowa_HashMap_Push_Arena(*pp_map, key, val, p_arena);
     }
 
     line = next + 2;
   }
+
   Seobeo_Handle_Consume(p_handle, (uint32)hdr_len);
 
   // 4) If Content-Length was provided, read that much body
-  int content_length_pos = Dowa_HashMap_Get_Position(map, "Content-Length");
-  Dowa_HashEntry *p_content_length_entry = map->entries[content_length_pos];
-  if (p_content_length_entry)
+  void *p_cl_kv = Dowa_HashMap_Get_Ptr(*pp_map, "Content-Length");
+  if (p_cl_kv)
   {
-    size_t body_len = atoi((char*)p_content_length_entry->buffer);
+    const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value;
+    size_t body_len = atoi(content_length_str);
     while (p_handle->read_buffer_len < body_len)
     {
       int r = Seobeo_Handle_Read(p_handle);
@@ -429,12 +425,13 @@
       if (r ==  0) return 1;       // wait for more data
     }
 
-    char *body = malloc(body_len + 1);
+    char *body = Dowa_Arena_Allocate(p_arena, body_len + 1);
+    if (!body) return -1;
     memcpy(body, p_handle->read_buffer, body_len);
     body[body_len] = '\0';
 
-    Dowa_HashMap_Push_Value(map, "Body", body, body_len + 1);
-    Dowa_Free(body);
+    // Body is arena-allocated
+    Dowa_HashMap_Push_Arena(*pp_map, "Body", body, p_arena);
 
     Seobeo_Handle_Consume(p_handle, (uint32)body_len);
   }
@@ -461,13 +458,12 @@
     Seobeo_ServerMode mode,
     int                thread_count)
 {
-  Dowa_HashMap *p_html_cache = Dowa_HashMap_Create(200);
-  if (Dowa_HashMap_Cache_Folder(p_html_cache,
-                                folder_path) != 0)
-  {
-    perror("Dowa_Cache_Folder");
-    return -1;
-  }
+  // Store folder path globally
+  if (folder_path)
+    strncpy(g_folder_path, folder_path, sizeof(g_folder_path) - 1);
+
+  // Initialize empty cache - files will be loaded on-demand
+  Seobeo_Cache_Entry *p_html_cache = NULL;
 
   Seobeo_Handle *p_server_handle =
     Seobeo_Stream_Handle_Server_Create(NULL, port);
@@ -503,7 +499,6 @@
   if (mode == SEOBEO_MODE_EDGE)
   {
     printf("EDGE MODE\n");
-    // Seobeo_Web_Edge_2(p_server_handle, p_html_cache);
     Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache);
   }