comparison seobeo/s_web.c @ 260:1f9877b637e9

Add Copilot-powered cyberpunk JRPG chat Integrate the production JRPG chat with Seobeo streaming, Deita persistence, and a Bazel-managed Copilot SDK and LiteLLM inference stack. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <mrjunejune@users.noreply.github.com>
date Wed, 05 Aug 2026 09:19:41 -0700
parents 609d3c6aff4e
children 04fee26ecce0
comparison
equal deleted inserted replaced
259:667156fcd3e3 260:1f9877b637e9
1 #include "seobeo/seobeo.h" 1 #include "seobeo/seobeo.h"
2 #include <strings.h> 2 #include <strings.h>
3 #include <time.h> 3 #include <time.h>
4 4
5 static char g_folder_path[512] = "."; 5 static char g_folder_path[512] = ".";
6 static volatile sig_atomic_t g_server_stopping = 0;
7
8 void Seobeo_Web_Server_Stop(void)
9 {
10 g_server_stopping = 1;
11 }
12
13 boolean Seobeo_Web_Server_Is_Stopping(void)
14 {
15 return g_server_stopping ? TRUE : FALSE;
16 }
6 17
7 static boolean Seobeo_Static_Path_Is_Safe(const char *path) 18 static boolean Seobeo_Static_Path_Is_Safe(const char *path)
8 { 19 {
9 if (!path || strchr(path, '\\')) 20 if (!path || strchr(path, '\\'))
10 return FALSE; 21 return FALSE;
153 // We'll peek at Content-Length to potentially allocate larger arena 164 // We'll peek at Content-Length to potentially allocate larger arena
154 // For now, start with default and handle body reading separately 165 // For now, start with default and handle body reading separately
155 Dowa_Arena *p_request_arena = Dowa_Arena_Create(arena_size); 166 Dowa_Arena *p_request_arena = Dowa_Arena_Create(arena_size);
156 if (!p_request_arena) { perror("Dowa_Arena_Create request"); return FALSE; } 167 if (!p_request_arena) { perror("Dowa_Arena_Create request"); return FALSE; }
157 168
158 Dowa_Arena *p_response_arena = Dowa_Arena_Create(arena_size); 169 Dowa_Arena *p_response_arena = Dowa_Arena_Create(1024 * 1024 * 16);
159 if (!p_response_arena) { perror("Dowa_Arena_Create response"); Dowa_Arena_Free(p_request_arena); return FALSE; } 170 if (!p_response_arena) { perror("Dowa_Arena_Create response"); Dowa_Arena_Free(p_request_arena); return FALSE; }
160 171
161 void *p_response_header = Dowa_Arena_Allocate(p_response_arena, 1024*5); // 5Kb 172 void *p_response_header = Dowa_Arena_Allocate(p_response_arena, 1024*5); // 5Kb
162 if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up_arenas; } 173 if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up_arenas; }
163 174
268 // --- Try to match generic streaming route --- 279 // --- Try to match generic streaming route ---
269 Seobeo_Stream_Handler stream_handler = Seobeo_Router_Find_Stream_Handler(method, path, &p_req_map, p_request_arena); 280 Seobeo_Stream_Handler stream_handler = Seobeo_Router_Find_Stream_Handler(method, path, &p_req_map, p_request_arena);
270 if (stream_handler != NULL) 281 if (stream_handler != NULL)
271 { 282 {
272 stream_handler(p_cli_handle, p_req_map, p_response_arena); 283 stream_handler(p_cli_handle, p_req_map, p_response_arena);
284 should_keep_alive = p_cli_handle->is_sse;
273 goto clean_up_arenas; 285 goto clean_up_arenas;
274 } 286 }
275 287
276 // --- Try to match API route --- 288 // --- Try to match API route ---
277 Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena); 289 Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena);
657 const char *folder_path, 669 const char *folder_path,
658 const char *port, 670 const char *port,
659 Seobeo_ServerMode mode, 671 Seobeo_ServerMode mode,
660 int thread_count) 672 int thread_count)
661 { 673 {
674 g_server_stopping = 0;
662 if (folder_path) 675 if (folder_path)
663 strncpy(g_folder_path, folder_path, sizeof(g_folder_path) - 1); 676 strncpy(g_folder_path, folder_path, sizeof(g_folder_path) - 1);
664 677
665 Seobeo_Cache_Entry *p_html_cache = NULL; 678 Seobeo_Cache_Entry *p_html_cache = NULL;
666 679
682 sa.sa_handler = SigchildHandler; 695 sa.sa_handler = SigchildHandler;
683 sigemptyset(&sa.sa_mask); 696 sigemptyset(&sa.sa_mask);
684 sa.sa_flags = SA_RESTART; 697 sa.sa_flags = SA_RESTART;
685 sigaction(SIGCHLD, &sa, NULL); 698 sigaction(SIGCHLD, &sa, NULL);
686 699
687 while (1) 700 while (!Seobeo_Web_Server_Is_Stopping())
688 { 701 {
689 Seobeo_Handle *p_cli_handle = 702 Seobeo_Handle *p_cli_handle =
690 Seobeo_Stream_Handle_Server_Accept(p_server_handle); 703 Seobeo_Stream_Handle_Server_Accept(p_server_handle);
691 if (!p_cli_handle) continue; 704 if (!p_cli_handle) continue;
692 705
702 { 715 {
703 Seobeo_Log(SEOBEO_INFO, "Server mode: EDGE\n"); 716 Seobeo_Log(SEOBEO_INFO, "Server mode: EDGE\n");
704 Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache); 717 Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache);
705 } 718 }
706 719
707 return -1; 720 Seobeo_Handle_Destroy(p_server_handle);
721 return 0;
708 } 722 }
709 723
710 /* Router logic */ 724 /* Router logic */
711 struct Seobeo_Route_Struct { 725 struct Seobeo_Route_Struct {
712 char *method; // "GET", "POST", "PUT", "DELETE" 726 char *method; // "GET", "POST", "PUT", "DELETE"
764 route.is_param[i] = (route.path_segments[i][0] == ':'); 778 route.is_param[i] = (route.path_segments[i][0] == ':');
765 779
766 Dowa_Array_Push(g_routes, route); 780 Dowa_Array_Push(g_routes, route);
767 } 781 }
768 782
769 void Seobeo_Router_Register_SSE( 783 void Seobeo_Router_Register_SSE_Method(
784 const char *method,
770 const char *path_pattern, 785 const char *path_pattern,
771 Seobeo_SSE_Handler handler) 786 Seobeo_SSE_Handler handler)
772 { 787 {
773 Seobeo_Route route = {0}; 788 Seobeo_Route route = {0};
774 789
775 route.method = strdup("GET"); 790 route.method = strdup(method);
776 route.path_pattern = strdup(path_pattern); 791 route.path_pattern = strdup(path_pattern);
777 route.handler = NULL; 792 route.handler = NULL;
778 route.stream_handler = NULL; 793 route.stream_handler = NULL;
779 route.sse_handler = handler; 794 route.sse_handler = handler;
780 route.path_segments = Dowa_String_Split( 795 route.path_segments = Dowa_String_Split(
791 route.is_param[i] = (route.path_segments[i][0] == ':'); 806 route.is_param[i] = (route.path_segments[i][0] == ':');
792 807
793 Dowa_Array_Push(g_routes, route); 808 Dowa_Array_Push(g_routes, route);
794 } 809 }
795 810
811 void Seobeo_Router_Register_SSE(
812 const char *path_pattern,
813 Seobeo_SSE_Handler handler)
814 {
815 Seobeo_Router_Register_SSE_Method("GET", path_pattern, handler);
816 }
817
796 static boolean match_route_and_extract( 818 static boolean match_route_and_extract(
797 Seobeo_Route *route, 819 Seobeo_Route *route,
798 const char *request_path, 820 const char *request_path,
799 Seobeo_Request_Entry **pp_request_map, 821 Seobeo_Request_Entry **pp_request_map,
800 Dowa_Arena *p_arena) 822 Dowa_Arena *p_arena)