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