Mercurial
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) |