comparison seobeo/s_web.c @ 175:71ad34a8bc9a hg-web

[HgWeb] Can stream hg response now. Added react page for hg web since we use json anyway.
author MrJuneJune <me@mrjunejune.com>
date Tue, 20 Jan 2026 06:06:47 -0800
parents 827c6ac504cd
children 8cf4ec5e2191
comparison
equal deleted inserted replaced
174:1ba8c1df082c 175:71ad34a8bc9a
94 Seobeo_Log(SEOBEO_DEBUG, "Parse completed with code %d\n", parse_result); 94 Seobeo_Log(SEOBEO_DEBUG, "Parse completed with code %d\n", parse_result);
95 95
96 // Recording IP to see who is ddosing or any web scrappers... 96 // Recording IP to see who is ddosing or any web scrappers...
97 void *p_real_ip_kv = Dowa_HashMap_Get_Ptr(p_req_map, "X-Real-IP"); 97 void *p_real_ip_kv = Dowa_HashMap_Get_Ptr(p_req_map, "X-Real-IP");
98 const char *real_ip = p_real_ip_kv ? ((Seobeo_Request_Entry*)p_real_ip_kv)->value : NULL; 98 const char *real_ip = p_real_ip_kv ? ((Seobeo_Request_Entry*)p_real_ip_kv)->value : NULL;
99
99 // Fallback 100 // Fallback
100 if (!real_ip) 101 if (!real_ip)
101 { 102 {
102 void *p_forwarded_kv = Dowa_HashMap_Get_Ptr(p_req_map, "X-Forwarded-For"); 103 void *p_forwarded_kv = Dowa_HashMap_Get_Ptr(p_req_map, "X-Forwarded-For");
103 real_ip = p_forwarded_kv ? ((Seobeo_Request_Entry*)p_forwarded_kv)->value : NULL; 104 real_ip = p_forwarded_kv ? ((Seobeo_Request_Entry*)p_forwarded_kv)->value : NULL;
104 } 105 }
105 // Fallback 106
106 if (!real_ip) 107 if (!real_ip)
107 real_ip = p_cli_handle->host; 108 real_ip = p_cli_handle->host;
108 109
109 void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method"); 110 void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method");
110 const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL; 111 const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL;
135 void *p_path_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Path"); 136 void *p_path_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Path");
136 const char *path = p_path_kv ? ((Seobeo_Request_Entry*)p_path_kv)->value : "/"; 137 const char *path = p_path_kv ? ((Seobeo_Request_Entry*)p_path_kv)->value : "/";
137 138
138 // --- Check for WebSocket upgrade request --- 139 // --- Check for WebSocket upgrade request ---
139 #ifdef SEOBEO_WEBSOCKET_SERVER 140 #ifdef SEOBEO_WEBSOCKET_SERVER
140 Seobeo_Log(SEOBEO_DEBUG, "Web soceket path \n"); 141 Seobeo_Log(SEOBEO_DEBUG, "Web socket path \n");
141 if (Seobeo_WebSocket_Server_Handle_Upgrade(p_cli_handle, p_req_map, path)) 142 if (Seobeo_WebSocket_Server_Handle_Upgrade(p_cli_handle, p_req_map, path))
142 { 143 {
143 Seobeo_Log(SEOBEO_INFO, "WebSocket connection established\n"); 144 Seobeo_Log(SEOBEO_INFO, "WebSocket connection established\n");
144 if (p_request_arena) 145 if (p_request_arena)
145 Dowa_Arena_Free(p_request_arena); 146 Dowa_Arena_Free(p_request_arena);
147 Dowa_Arena_Free(p_response_arena); 148 Dowa_Arena_Free(p_response_arena);
148 return; 149 return;
149 } 150 }
150 #endif 151 #endif
151 152
152 // --- Try to match API route first --- 153 // --- Try to match streaming route first ---
154 Seobeo_Stream_Handler stream_handler = Seobeo_Router_Find_Stream_Handler(method, path, &p_req_map, p_request_arena);
155 if (stream_handler != NULL)
156 {
157 stream_handler(p_cli_handle, p_req_map, p_response_arena);
158 goto clean_up;
159 }
160
161 // --- Try to match API route ---
153 Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena); 162 Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena);
154 if (handler != NULL) 163 if (handler != NULL)
155 { 164 {
156 Seobeo_Request_Entry *p_response_map = handler(p_req_map, p_response_arena); 165 Seobeo_Request_Entry *p_response_map = handler(p_req_map, p_response_arena);
157 Seobeo_Router_Send_Response(p_cli_handle, p_response_map, p_response_arena); 166 Seobeo_Router_Send_Response(p_cli_handle, p_response_map, p_response_arena);
291 if (p_handle->read_buffer_len >= 4 && 300 if (p_handle->read_buffer_len >= 4 &&
292 strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL) 301 strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL)
293 break; 302 break;
294 303
295 if (r == 0) 304 if (r == 0)
296 return 1; // EAGAIN, try again later TODO: Add this as part of Handle struct. 305 {
306 Seobeo_Log(SEOBEO_INFO, "Waiting?\n");
307 continue; // EAGAIN, try again later TODO: Add this as part of Handle struct.
308 }
297 } 309 }
298 310
299 // "METHOD SP PATH SP VERSION CRLF" 311 // "METHOD SP PATH SP VERSION CRLF"
300 char *buf = (char*)p_handle->read_buffer; 312 char *buf = (char*)p_handle->read_buffer;
301 char *hdr_end = strstr(buf, "\r\n\r\n"); 313 char *hdr_end = strstr(buf, "\r\n\r\n");
406 while (line < hdr_end) 418 while (line < hdr_end)
407 { 419 {
408 char *next = strstr(line, "\r\n"); 420 char *next = strstr(line, "\r\n");
409 if (!next) break; 421 if (!next) break;
410 422
411 // split at colon
412 char *colon = memchr(line, ':', next - line); 423 char *colon = memchr(line, ':', next - line);
413 if (colon) 424 if (colon)
414 { 425 {
415 size_t key_len = colon - line; 426 size_t key_len = colon - line;
416 size_t value_len = next - colon - 1; 427 size_t value_len = next - colon - 1;
430 char *val = Dowa_Arena_Allocate(p_arena, value_len + 1); 441 char *val = Dowa_Arena_Allocate(p_arena, value_len + 1);
431 if (!val) return -1; 442 if (!val) return -1;
432 memcpy(val, val_start, value_len); 443 memcpy(val, val_start, value_len);
433 val[value_len] = '\0'; 444 val[value_len] = '\0';
434 445
435 // Both key and value are arena-allocated, hashmap will use them
436 Dowa_HashMap_Push_Arena(*pp_map, key, val, p_arena); 446 Dowa_HashMap_Push_Arena(*pp_map, key, val, p_arena);
437 } 447 }
438 448
439 line = next + 2; 449 line = next + 2;
440 } 450 }
448 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; 458 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value;
449 size_t body_len = atoi(content_length_str); 459 size_t body_len = atoi(content_length_str);
450 460
451 Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len); 461 Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len);
452 462
453 // Allocate buffer for entire body
454 char *body = Dowa_Arena_Allocate(p_arena, body_len + 1); 463 char *body = Dowa_Arena_Allocate(p_arena, body_len + 1);
455 if (!body) 464 if (!body)
456 { 465 {
457 Seobeo_Log(SEOBEO_ERROR, "Failed to allocate %zu bytes for body\n", body_len); 466 Seobeo_Log(SEOBEO_ERROR, "Failed to allocate %zu bytes for body\n", body_len);
458 return -1; 467 return -1;
565 /* Router logic */ 574 /* Router logic */
566 struct Seobeo_Route_Struct { 575 struct Seobeo_Route_Struct {
567 char *method; // "GET", "POST", "PUT", "DELETE" 576 char *method; // "GET", "POST", "PUT", "DELETE"
568 char *path_pattern; // "/v1/users/:id/posts/:post_id" 577 char *path_pattern; // "/v1/users/:id/posts/:post_id"
569 Seobeo_Route_Handler handler; 578 Seobeo_Route_Handler handler;
579 Seobeo_Stream_Handler stream_handler; // For streaming responses
570 580
571 // Pre-parsed path segments for efficient matching 581 // Pre-parsed path segments for efficient matching
572 char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"] 582 char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"]
573 boolean *is_param; // [false, false, true, false, true] 583 boolean *is_param; // [false, false, true, false, true]
574 size_t segment_count; 584 size_t segment_count;
586 Seobeo_Route route = {0}; 596 Seobeo_Route route = {0};
587 597
588 route.method = strdup(method); 598 route.method = strdup(method);
589 route.path_pattern = strdup(path_pattern); 599 route.path_pattern = strdup(path_pattern);
590 route.handler = handler; 600 route.handler = handler;
601 route.stream_handler = NULL;
602 route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL);
603 route.segment_count = Dowa_Array_Length(route.path_segments);
604 route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count);
605
606 for (size_t i = 0; i < route.segment_count; i++)
607 route.is_param[i] = (route.path_segments[i][0] == ':');
608
609 Dowa_Array_Push(g_routes, route);
610 }
611
612 void Seobeo_Router_Register_Stream(const char *method, const char *path_pattern, Seobeo_Stream_Handler handler)
613 {
614 Seobeo_Route route = {0};
615
616 route.method = strdup(method);
617 route.path_pattern = strdup(path_pattern);
618 route.handler = NULL;
619 route.stream_handler = handler;
591 route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL); 620 route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL);
592 route.segment_count = Dowa_Array_Length(route.path_segments); 621 route.segment_count = Dowa_Array_Length(route.path_segments);
593 route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count); 622 route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count);
594 623
595 for (size_t i = 0; i < route.segment_count; i++) 624 for (size_t i = 0; i < route.segment_count; i++)
668 } 697 }
669 698
670 return NULL; 699 return NULL;
671 } 700 }
672 701
702 Seobeo_Stream_Handler Seobeo_Router_Find_Stream_Handler(
703 const char *method,
704 const char *path,
705 Seobeo_Request_Entry **pp_request_map,
706 Dowa_Arena *p_arena)
707 {
708 if (g_routes == NULL || method == NULL || path == NULL)
709 return NULL;
710
711 size_t route_count = Dowa_Array_Length(g_routes);
712 for (size_t i = 0; i < route_count; i++)
713 {
714 Seobeo_Route *route = &g_routes[i];
715 if (strcmp(route->method, method) != 0)
716 continue;
717
718 if (route->stream_handler && match_route_and_extract(route, path, pp_request_map, p_arena))
719 return route->stream_handler;
720 }
721
722 return NULL;
723 }
724
673 void Seobeo_Router_Send_Response( 725 void Seobeo_Router_Send_Response(
674 Seobeo_Handle *p_handle, 726 Seobeo_Handle *p_handle,
675 Seobeo_Request_Entry *p_response_map, 727 Seobeo_Request_Entry *p_response_map,
676 Dowa_Arena *p_arena) 728 Dowa_Arena *p_arena)
677 { 729 {
694 } 746 }
695 747
696 const char *body = ""; 748 const char *body = "";
697 void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body"); 749 void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body");
698 if (p_body_kv) 750 if (p_body_kv)
699 {
700 body = ((Seobeo_Request_Entry*)p_body_kv)->value; 751 body = ((Seobeo_Request_Entry*)p_body_kv)->value;
701 }
702 752
703 const char *content_type = "text/html"; 753 const char *content_type = "text/html";
704 void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type"); 754 void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type");
705 if (p_content_type_kv) 755 if (p_content_type_kv)
706 {
707 content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value; 756 content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value;
708 } 757
709 758 // TODO: Update this to be integer
710 size_t body_length = strlen(body); 759 size_t body_length;
711 void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length"); 760 void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length");
712 if (p_content_length_kv) 761 if (p_content_length_kv)
713 { 762 {
714 const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value; 763 const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value;
715 body_length = atoi(content_length_str); 764 body_length = atoi(content_length_str);
716 } 765 }
766 else
767 body_length = strlen(body);
717 768
718 char *header = Dowa_Arena_Allocate(p_arena, 4096); 769 char *header = Dowa_Arena_Allocate(p_arena, 4096);
719 Seobeo_Web_Header_Generate(header, status, content_type, body_length); 770 Seobeo_Web_Header_Generate(header, status, content_type, body_length);
720 for (int i = 0; i < Dowa_Array_Length(p_response_map); i++) 771 for (int i = 0; i < Dowa_Array_Length(p_response_map); i++)
721 { 772 {
732 sprintf(temp, "%s: %s\r\n\r\n", p_response_map[i].key, p_response_map[i].value); 783 sprintf(temp, "%s: %s\r\n\r\n", p_response_map[i].key, p_response_map[i].value);
733 memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp)); 784 memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp));
734 free(temp); 785 free(temp);
735 } 786 }
736 787
788 printf("hEADER %s\n", header);
789
737 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); 790 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header));
738 Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); 791 Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length);
739 Seobeo_Handle_Flush(p_handle); 792 Seobeo_Handle_Flush(p_handle);
740 } 793 }
741 794