comparison seobeo/s_web.c @ 96:70401cf61e97

[Seobeo] Added logging.
author June Park <parkjune1995@gmail.com>
date Fri, 02 Jan 2026 19:16:17 -0800
parents 655ea0b661fd
children 65e5a5b89a4e
comparison
equal deleted inserted replaced
95:b51f8cce9170 96:70401cf61e97
91 } 91 }
92 92
93 void Seobeo_Web_HandleClientRequest(Seobeo_Handle *p_cli_handle, 93 void Seobeo_Web_HandleClientRequest(Seobeo_Handle *p_cli_handle,
94 Seobeo_Cache_Entry *p_html_cache) 94 Seobeo_Cache_Entry *p_html_cache)
95 { 95 {
96 Seobeo_Log(SEOBEO_INFO, "Client is from %s\n", p_cli_handle->host);
96 Dowa_Arena *p_request_arena = Dowa_Arena_Create(1*1024*1024); // 1MB for request parsing 97 Dowa_Arena *p_request_arena = Dowa_Arena_Create(1*1024*1024); // 1MB for request parsing
97 if (!p_request_arena) { perror("Dowa_Arena_Create request"); goto clean_up; } 98 if (!p_request_arena) { perror("Dowa_Arena_Create request"); goto clean_up; }
98 99
99 Dowa_Arena *p_response_arena = Dowa_Arena_Create(5*1024*1024); // 1MB for response 100 Dowa_Arena *p_response_arena = Dowa_Arena_Create(5*1024*1024); // 5MB for response
100 if (!p_response_arena) { perror("Dowa_Arena_Create response"); goto clean_up; } 101 if (!p_response_arena) { perror("Dowa_Arena_Create response"); goto clean_up; }
101 102
102 void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)1024*5); // 5Kb 103 void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)1024*5); // 5Kb
103 if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; } 104 if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; }
104 105
105 // Parse request headers into hashmap using arena
106 Seobeo_Request_Entry *p_req_map = NULL; 106 Seobeo_Request_Entry *p_req_map = NULL;
107 int parse_result = Seobeo_Web_Header_Parse(p_cli_handle, &p_req_map, p_request_arena); 107 int parse_result = Seobeo_Web_Header_Parse(p_cli_handle, &p_req_map, p_request_arena);
108 108
109 // Treat EAGAIN (return code 1) as success - headers were parsed, body may still be coming
110 if (parse_result != 0 && parse_result != 1) 109 if (parse_result != 0 && parse_result != 1)
111 { 110 {
112 printf("ERROR: Seobeo_Web_Header_Parse failed with code %d\n", parse_result); 111 Seobeo_Log(SEOBEO_ERROR, "Seobeo_Web_Header_Parse failed with code %d\n", parse_result);
113 fflush(stdout); 112 fflush(stdout);
114 Seobeo_Web_Header_Generate(p_response_header, 113 Seobeo_Web_Header_Generate(p_response_header,
115 HTTP_BAD_REQUEST, 114 HTTP_BAD_REQUEST,
116 "text/plain", 0); 115 "text/plain", 0);
117 Seobeo_Handle_Queue(p_cli_handle, 116 Seobeo_Handle_Queue(p_cli_handle,
119 (uint32)strlen(p_response_header)); 118 (uint32)strlen(p_response_header));
120 Seobeo_Handle_Flush(p_cli_handle); 119 Seobeo_Handle_Flush(p_cli_handle);
121 goto clean_up; 120 goto clean_up;
122 } 121 }
123 122
124 printf("DEBUG: Parse completed with code %d\n", parse_result); 123 Seobeo_Log(SEOBEO_DEBUG, "Parse completed with code %d\n", parse_result);
125 fflush(stdout); 124 fflush(stdout);
126 125
127 // Extract method (GET, POST, etc.)
128 void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method"); 126 void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method");
129 const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL; 127 const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL;
130 128
131 printf("DEBUG: Parsed request, method=%s\n", method ? method : "NULL"); 129 Seobeo_Log(SEOBEO_DEBUG, "Parsed request, method=%s\n", method ? method : "NULL");
132 fflush(stdout); 130 fflush(stdout);
133 131
134 if (!method) 132 if (!method)
135 { 133 {
136 printf("ERROR: No HTTP method found in request\n"); 134 Seobeo_Log(SEOBEO_ERROR, "No HTTP method found in request\n");
137 fflush(stdout); 135 fflush(stdout);
138 Seobeo_Web_Header_Generate(p_response_header, 136 Seobeo_Web_Header_Generate(p_response_header,
139 HTTP_BAD_REQUEST, 137 HTTP_BAD_REQUEST,
140 "text/plain", 0); 138 "text/plain", 0);
141 Seobeo_Handle_Queue(p_cli_handle, 139 Seobeo_Handle_Queue(p_cli_handle,
223 else if (strstr(file_path, ".gif")) mime = "image/gif"; 221 else if (strstr(file_path, ".gif")) mime = "image/gif";
224 else if (strstr(file_path, ".svg")) mime = "image/svg+xml"; 222 else if (strstr(file_path, ".svg")) mime = "image/svg+xml";
225 else if (strstr(file_path, ".ico")) mime = "image/x-icon"; 223 else if (strstr(file_path, ".ico")) mime = "image/x-icon";
226 else if (strstr(file_path, ".json")) mime = "application/json"; 224 else if (strstr(file_path, ".json")) mime = "application/json";
227 225
228 printf("File path: %s\nBody Size: %zu\n", file_path, body_size); 226 Seobeo_Log(SEOBEO_DEBUG, "File path: %s\nBody Size: %zu\n", file_path, body_size);
229 227
230 Seobeo_Web_Header_Generate(p_response_header, 228 Seobeo_Web_Header_Generate(p_response_header,
231 HTTP_OK, 229 HTTP_OK,
232 mime, 230 mime,
233 body_size); 231 body_size);
237 (uint32)strlen(p_response_header)); 235 (uint32)strlen(p_response_header));
238 Seobeo_Handle_Queue(p_cli_handle, 236 Seobeo_Handle_Queue(p_cli_handle,
239 (const uint8*)file_content, 237 (const uint8*)file_content,
240 (uint32)body_size); 238 (uint32)body_size);
241 Seobeo_Handle_Flush(p_cli_handle); 239 Seobeo_Handle_Flush(p_cli_handle);
242 printf("DONE\n\n\n"); 240 Seobeo_Log(SEOBEO_DEBUG, "Request handled successfully\n");
243 } 241 }
244 else 242 else
245 { 243 {
246 Seobeo_Web_Header_Generate(p_response_header, 244 Seobeo_Web_Header_Generate(p_response_header,
247 HTTP_FORBIDDEN, 245 HTTP_FORBIDDEN,
252 Seobeo_Handle_Flush(p_cli_handle); 250 Seobeo_Handle_Flush(p_cli_handle);
253 } 251 }
254 goto clean_up; 252 goto clean_up;
255 253
256 clean_up: 254 clean_up:
257 printf("clean up\n\n"); 255 Seobeo_Log(SEOBEO_INFO, "Clean up all Arenas\n");
258 if (p_cli_handle) 256 if (p_cli_handle)
259 Seobeo_Handle_Destroy(p_cli_handle); 257 Seobeo_Handle_Destroy(p_cli_handle);
260 if (p_request_arena) 258 if (p_request_arena)
261 Dowa_Arena_Free(p_request_arena); 259 Dowa_Arena_Free(p_request_arena);
262 if (p_response_arena) 260 if (p_response_arena)
293 // Debug: Print the first line of the request 291 // Debug: Print the first line of the request
294 char *first_line_end = strstr(buf, "\r\n"); 292 char *first_line_end = strstr(buf, "\r\n");
295 if (first_line_end) 293 if (first_line_end)
296 { 294 {
297 size_t first_line_len = first_line_end - buf; 295 size_t first_line_len = first_line_end - buf;
298 printf("DEBUG: Request line (first %zu bytes): '", first_line_len > 200 ? 200 : first_line_len); 296 Seobeo_Log(SEOBEO_DEBUG, "Request line (first %zu bytes)\n", first_line_len > 200 ? 200 : first_line_len);
299 fwrite(buf, 1, first_line_len > 200 ? 200 : first_line_len, stdout);
300 printf("'\n");
301 fflush(stdout); 297 fflush(stdout);
302 } 298 }
303 299
304 // This seems kinda bad ? 300 // This seems kinda bad ?
305 char method[16], path[256], version[16]; 301 char method[16], path[256], version[16];
306 int scan_result = sscanf(buf, "%15s %255s %15s", method, path, version); 302 int scan_result = sscanf(buf, "%15s %255s %15s", method, path, version);
307 printf("DEBUG: sscanf returned %d (method='%s', path='%s', version='%s')\n", 303 Seobeo_Log(SEOBEO_DEBUG, "sscanf returned %d (method='%s', path='%s', version='%s')\n",
308 scan_result, 304 scan_result,
309 scan_result >= 1 ? method : "N/A", 305 scan_result >= 1 ? method : "N/A",
310 scan_result >= 2 ? path : "N/A", 306 scan_result >= 2 ? path : "N/A",
311 scan_result >= 3 ? version : "N/A"); 307 scan_result >= 3 ? version : "N/A");
312 fflush(stdout); 308 fflush(stdout);
313 309
314 if (scan_result != 3) 310 if (scan_result != 3)
315 { 311 {
316 printf("ERROR: Failed to parse request line\n"); 312 Seobeo_Log(SEOBEO_ERROR, "Failed to parse request line\n");
317 fflush(stdout); 313 fflush(stdout);
318 return -1; 314 return -1;
319 } 315 }
320 316
321 // Copy strings to arena and store in hashmap 317 // Copy strings to arena and store in hashmap
322 printf("DEBUG: Allocating method_copy\n"); 318 Seobeo_Log(SEOBEO_DEBUG, "Allocating method_copy\n");
323 fflush(stdout); 319 fflush(stdout);
324 char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1); 320 char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1);
325 if (!method_copy) { printf("ERROR: Failed to allocate method_copy\n"); return -1; } 321 if (!method_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate method_copy\n"); return -1; }
326 strcpy(method_copy, method); 322 strcpy(method_copy, method);
327 323
328 printf("DEBUG: Allocating version_copy\n"); 324 Seobeo_Log(SEOBEO_DEBUG, "Allocating version_copy\n");
329 fflush(stdout); 325 fflush(stdout);
330 char *version_copy = Dowa_Arena_Allocate(p_arena, strlen(version) + 1); 326 char *version_copy = Dowa_Arena_Allocate(p_arena, strlen(version) + 1);
331 if (!version_copy) { printf("ERROR: Failed to allocate version_copy\n"); return -1; } 327 if (!version_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate version_copy\n"); return -1; }
332 strcpy(version_copy, version); 328 strcpy(version_copy, version);
333 329
334 printf("DEBUG: Pushing HTTP_Method and Version to map\n"); 330 Seobeo_Log(SEOBEO_DEBUG, "Pushing HTTP_Method and Version to map\n");
335 fflush(stdout); 331 fflush(stdout);
336 Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena); 332 Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena);
337 Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena); 333 Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena);
338 printf("DEBUG: Map now has %zu entries\n", Dowa_Array_Length(*pp_map)); 334 Seobeo_Log(SEOBEO_DEBUG, "Map now has %zu entries\n", Dowa_Array_Length(*pp_map));
339 fflush(stdout); 335 fflush(stdout);
340 336
341 // 1) Separate raw path and query string 337 // 1) Separate raw path and query string
342 char *raw_path = path; 338 char *raw_path = path;
343 char *query_start = strchr(raw_path, '?'); 339 char *query_start = strchr(raw_path, '?');
435 if (p_cl_kv) 431 if (p_cl_kv)
436 { 432 {
437 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; 433 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value;
438 size_t body_len = atoi(content_length_str); 434 size_t body_len = atoi(content_length_str);
439 435
440 printf("DEBUG: Content-Length=%zu, reading body in chunks...\n", body_len); 436 Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len);
441 fflush(stdout); 437 fflush(stdout);
442 438
443 // Allocate buffer for entire body 439 // Allocate buffer for entire body
444 char *body = Dowa_Arena_Allocate(p_arena, body_len + 1); 440 char *body = Dowa_Arena_Allocate(p_arena, body_len + 1);
445 if (!body) 441 if (!body)
446 { 442 {
447 printf("ERROR: Failed to allocate %zu bytes for body\n", body_len); 443 Seobeo_Log(SEOBEO_ERROR, "Failed to allocate %zu bytes for body\n", body_len);
448 fflush(stdout); 444 fflush(stdout);
449 return -1; 445 return -1;
450 } 446 }
451 447
452 size_t total_read = 0; 448 size_t total_read = 0;
462 { 458 {
463 memcpy(body + total_read, p_handle->read_buffer, to_copy); 459 memcpy(body + total_read, p_handle->read_buffer, to_copy);
464 total_read += to_copy; 460 total_read += to_copy;
465 Seobeo_Handle_Consume(p_handle, (uint32)to_copy); 461 Seobeo_Handle_Consume(p_handle, (uint32)to_copy);
466 462
467 printf("DEBUG: Copied %zu bytes, total %zu/%zu\n", to_copy, total_read, body_len); 463 Seobeo_Log(SEOBEO_DEBUG, "Copied %zu bytes, total %zu/%zu\n", to_copy, total_read, body_len);
468 fflush(stdout); 464 fflush(stdout);
469 } 465 }
470 466
471 // If we still need more data, read another chunk 467 // If we still need more data, read another chunk
472 if (total_read < body_len) 468 if (total_read < body_len)
473 { 469 {
474 int r = Seobeo_Handle_Read(p_handle); 470 int r = Seobeo_Handle_Read(p_handle);
475 if (r < 0) 471 if (r < 0)
476 { 472 {
477 printf("ERROR: Read failed with %d\n", r); 473 Seobeo_Log(SEOBEO_ERROR, "Read failed with %d\n", r);
478 fflush(stdout); 474 fflush(stdout);
479 return -1; 475 return -1;
480 } 476 }
481 if (r == 0) 477 if (r == 0)
482 { 478 {
487 } 483 }
488 } 484 }
489 } 485 }
490 486
491 body[body_len] = '\0'; 487 body[body_len] = '\0';
492 printf("DEBUG: Body fully received (%zu bytes)\n", body_len); 488 Seobeo_Log(SEOBEO_DEBUG, "Body fully received (%zu bytes)\n", body_len);
493 fflush(stdout); 489 fflush(stdout);
494 490
495 // Body is arena-allocated 491 // Body is arena-allocated
496 Dowa_HashMap_Push_Arena(*pp_map, "Body", body, p_arena); 492 Dowa_HashMap_Push_Arena(*pp_map, "Body", body, p_arena);
497 } 493 }
527 523
528 Seobeo_Handle *p_server_handle = 524 Seobeo_Handle *p_server_handle =
529 Seobeo_Stream_Handle_Server_Create(NULL, port); 525 Seobeo_Stream_Handle_Server_Create(NULL, port);
530 if (p_server_handle->socket < 0) return 1; 526 if (p_server_handle->socket < 0) return 1;
531 527
532 printf("Listening on port %s\n", port); 528 Seobeo_Log(SEOBEO_INFO, "Listening on port %s\n", port);
533 529
534 // Fork‐based fallback 530 // Fork‐based fallback
535 if (mode == SEOBEO_MODE_FORK) 531 if (mode == SEOBEO_MODE_FORK)
536 { 532 {
537 printf("FORK MODE\n"); 533 Seobeo_Log(SEOBEO_INFO, "Server mode: FORK\n");
538 struct sigaction sa; 534 struct sigaction sa;
539 sa.sa_handler = SigchildHandler; 535 sa.sa_handler = SigchildHandler;
540 sigemptyset(&sa.sa_mask); 536 sigemptyset(&sa.sa_mask);
541 sa.sa_flags = SA_RESTART; 537 sa.sa_flags = SA_RESTART;
542 sigaction(SIGCHLD, &sa, NULL); 538 sigaction(SIGCHLD, &sa, NULL);
556 } 552 }
557 } 553 }
558 554
559 if (mode == SEOBEO_MODE_EDGE) 555 if (mode == SEOBEO_MODE_EDGE)
560 { 556 {
561 printf("EDGE MODE\n"); 557 Seobeo_Log(SEOBEO_INFO, "Server mode: EDGE\n");
562 Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache); 558 Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache);
563 } 559 }
564 560
565 return -1; 561 return -1;
566 } 562 }
598 if (!p_request_body) { Seobeo_Handle_Destroy(h); return -1; } 594 if (!p_request_body) { Seobeo_Handle_Destroy(h); return -1; }
599 595
600 while (1) 596 while (1)
601 { 597 {
602 int n = Seobeo_Handle_Read(h); 598 int n = Seobeo_Handle_Read(h);
603 printf("Size: %d\n", n); 599 Seobeo_Log(SEOBEO_DEBUG, "Received size: %d bytes\n", n);
604 if (n > 0) 600 if (n > 0)
605 { 601 {
606 // TODO: Maybe directly use arena inside of the struct? idk if that is useful or not... 602 // TODO: Maybe directly use arena inside of the struct? idk if that is useful or not...
607 memcpy(p_request_body + used, h->read_buffer, h->read_buffer_len); 603 memcpy(p_request_body + used, h->read_buffer, h->read_buffer_len);
608 used += h->read_buffer_len; 604 used += h->read_buffer_len;
613 continue; 609 continue;
614 }else if (n == -2) 610 }else if (n == -2)
615 { 611 {
616 // Debug 612 // Debug
617 // peer closed; we’ve got everything 613 // peer closed; we’ve got everything
618 printf("\n\nCLOSED\n\n"); 614 Seobeo_Log(SEOBEO_DEBUG, "Connection closed by client\n");
619 break; 615 break;
620 }else 616 }else
621 { 617 {
622 Dowa_Arena_Free(p_request_arena); 618 Dowa_Arena_Free(p_request_arena);
623 Seobeo_Handle_Destroy(h); 619 Seobeo_Handle_Destroy(h);
624 return -1; 620 return -1;
625 } 621 }
626 } 622 }
627 623
628 // Debug 624 // Debug
629 printf("Request body %s", p_request_body); 625 Seobeo_Log(SEOBEO_DEBUG, "Request body: %s\n", p_request_body);
630 Dowa_Arena_Free(p_request_arena); 626 Dowa_Arena_Free(p_request_arena);
631 Seobeo_Handle_Destroy(h); 627 Seobeo_Handle_Destroy(h);
632 return 0; 628 return 0;
633 } 629 }
630
631 /* Router logic */
632 struct Seobeo_Route_Struct {
633 char *method; // "GET", "POST", "PUT", "DELETE"
634 char *path_pattern; // "/v1/users/:id/posts/:post_id"
635 Seobeo_Route_Handler handler;
636
637 // Pre-parsed path segments for efficient matching
638 char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"]
639 boolean *is_param; // [false, false, true, false, true]
640 size_t segment_count;
641 };
642
643 static Seobeo_Route *g_routes = NULL;
644
645 void Seobeo_Router_Init()
646 {
647 Dowa_Array_Reserve(g_routes, 20);
648 }
649
650 void Seobeo_Router_Register(const char *method, const char *path_pattern, Seobeo_Route_Handler handler)
651 {
652 Seobeo_Route route = {0};
653
654 route.method = strdup(method);
655 route.path_pattern = strdup(path_pattern);
656 route.handler = handler;
657 route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL);
658 route.segment_count = Dowa_Array_Length(route.path_segments);
659 route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count);
660
661 for (size_t i = 0; i < route.segment_count; i++)
662 route.is_param[i] = (route.path_segments[i][0] == ':');
663
664 Dowa_Array_Push(g_routes, route);
665 }
666
667 // Match route and extract path parameters
668 static boolean match_route_and_extract(
669 Seobeo_Route *route,
670 const char *request_path,
671 Seobeo_Request_Entry **pp_request_map,
672 Dowa_Arena *p_arena)
673 {
674 Dowa_Arena *p_temp_arena = Dowa_Arena_Create(1024);
675 char **request_segments = Dowa_String_Split(request_path, "/", strlen(request_path), 1, p_temp_arena);
676 size_t request_segment_count = Dowa_Array_Length(request_segments);
677 // Check segment count matches
678 if (request_segment_count != route->segment_count)
679 {
680 Dowa_Arena_Free(p_temp_arena);
681 return FALSE;
682 }
683
684 for (size_t i = 0; i < route->segment_count; i++)
685 {
686 // parameters
687 if (route->is_param[i])
688 {
689 char *param_name = route->path_segments[i]; // e.g., ":id"
690 char *param_value = request_segments[i]; // e.g., "123"
691
692 // Should Copy to arena
693 char *key = Dowa_String_Copy_Arena(param_name, p_arena);
694 char *value = Dowa_String_Copy_Arena(param_value, p_arena);
695 Dowa_HashMap_Push_Arena(*pp_request_map, key, value, p_arena);
696 }
697 else
698 {
699 // Does not match.
700 if (strcmp(route->path_segments[i], request_segments[i]) != 0)
701 {
702 Dowa_Arena_Free(p_temp_arena);
703 return FALSE;
704 }
705 }
706 }
707
708 Dowa_Arena_Free(p_temp_arena);
709 return TRUE;
710 }
711
712 Seobeo_Route_Handler Seobeo_Router_Find_Handler(const char *method,
713 const char *path,
714 Seobeo_Request_Entry **pp_request_map,
715 Dowa_Arena *p_arena) {
716 if (g_routes == NULL)
717 {
718 return NULL;
719 }
720
721 size_t route_count = Dowa_Array_Length(g_routes);
722 for (size_t i = 0; i < route_count; i++)
723 {
724 Seobeo_Route *route = &g_routes[i];
725 if (strcmp(route->method, method) != 0)
726 {
727 continue;
728 }
729
730 if (match_route_and_extract(route, path, pp_request_map, p_arena))
731 {
732 return route->handler;
733 }
734 }
735
736 return NULL;
737 }
738
739 void Seobeo_Router_Send_Response(
740 Seobeo_Handle *p_handle,
741 Seobeo_Request_Entry *p_response_map,
742 Dowa_Arena *p_arena)
743 {
744 if (p_response_map == NULL)
745 {
746 char *header = Dowa_Arena_Allocate(p_arena, 1024);
747 Seobeo_Web_Header_Generate(header, HTTP_INTERNAL_ERROR, "text/plain", 21);
748 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header));
749 Seobeo_Handle_Queue(p_handle, (uint8_t*)"Internal Server Error", 21);
750 Seobeo_Handle_Flush(p_handle);
751 return;
752 }
753
754 // Header
755 int status = HTTP_OK;
756 void *p_status_kv = Dowa_HashMap_Get_Ptr(p_response_map, "status");
757 if (p_status_kv)
758 {
759 const char *status_str = ((Seobeo_Request_Entry*)p_status_kv)->value;
760 status = atoi(status_str);
761 }
762
763 // Body
764 const char *body = "";
765 void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body");
766 if (p_body_kv)
767 {
768 body = ((Seobeo_Request_Entry*)p_body_kv)->value;
769 }
770
771 // Default text plain
772 const char *content_type = "text/html";
773 void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type");
774 if (p_content_type_kv)
775 {
776 content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value;
777 }
778
779 // Check for custom content-length (for binary data)
780 size_t body_length = strlen(body);
781 void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length");
782 if (p_content_length_kv)
783 {
784 const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value;
785 body_length = atoi(content_length_str);
786 }
787
788 // All other types are default thoguht to be headers.
789 char *header = Dowa_Arena_Allocate(p_arena, 4096);
790 Seobeo_Web_Header_Generate(header, status, content_type, body_length);
791 for (int i = 0; i < Dowa_Array_Length(p_response_map); i++)
792 {
793 if (
794 strstr(p_response_map[i].key, "status") ||
795 strstr(p_response_map[i].key, "body") ||
796 strstr(p_response_map[i].key, "content-type") ||
797 strstr(p_response_map[i].key, "content-length") // Skip custom content-length
798 )
799 continue;
800
801 int32 current_header_len = strlen(header);
802 char *temp = malloc(sizeof(char) * 1024);
803 sprintf(temp, "%s: %s\r\n\r\n", p_response_map[i].key, p_response_map[i].value);
804 memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp));
805 free(temp);
806 }
807
808 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header));
809 Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); // Use actual body length
810 Seobeo_Handle_Flush(p_handle);
811 }
812
813 void Seobeo_Router_Destroy()
814 {
815 if (g_routes == NULL)
816 return;
817
818 size_t route_count = Dowa_Array_Length(g_routes);
819 for (size_t i = 0; i < route_count; i++)
820 {
821 Seobeo_Route *route = &g_routes[i];
822 if (route->method) free(route->method);
823 if (route->path_pattern) free(route->path_pattern);
824 if (route->path_segments) Dowa_Array_Free(route->path_segments);
825 if (route->is_param) free(route->is_param);
826 }
827 Dowa_Array_Free(g_routes);
828 g_routes = NULL;
829 }
830
831 static char *Seobeo_Log_Level_String(Seobeo_Log_Level level)
832 {
833 switch(level) {
834 case SEOBEO_DEBUG: return "DEBUG";
835 case SEOBEO_INFO: return "INFO";
836 case SEOBEO_WARNING: return "WARNING";
837 case SEOBEO_ERROR: return "ERROR";
838 default: return "INFO";
839 }
840 }
841
842 int Seobeo_Log(Seobeo_Log_Level level, const char * restrict format, ...)
843 {
844 #ifndef SEOBEO_ENABLE_DEBUG
845 if (level == SEOBEO_DEBUG)
846 return 0;
847 #endif
848
849 int result;
850 va_list args;
851 printf("[%s] ", Seobeo_Log_Level_String(level));
852 va_start(args, format);
853 result = vprintf(format, args);
854 va_end(args);
855
856 return result;
857 }
858