comparison seobeo/s_web.c @ 195:f8f5004a920a

Merging back hg-web-tip
author MrJuneJune <me@mrjunejune.com>
date Tue, 27 Jan 2026 06:51:44 -0800
parents a69485d9f2e1
children
comparison
equal deleted inserted replaced
189:14cc84ba35a0 195:f8f5004a920a
149 boolean is_http11 = (strstr(http_version, "1.1") != NULL); 149 boolean is_http11 = (strstr(http_version, "1.1") != NULL);
150 150
151 void *p_conn_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Connection"); 151 void *p_conn_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Connection");
152 const char *conn_header = p_conn_kv ? ((Seobeo_Request_Entry*)p_conn_kv)->value : NULL; 152 const char *conn_header = p_conn_kv ? ((Seobeo_Request_Entry*)p_conn_kv)->value : NULL;
153 153
154 void *p_real_ip_kv = Dowa_HashMap_Get_Ptr(p_req_map, "X-Real-IP");
155 const char *real_ip = p_real_ip_kv ? ((Seobeo_Request_Entry*)p_real_ip_kv)->value : NULL;
156 if (!real_ip)
157 real_ip = p_cli_handle->host;
158
154 if (conn_header) 159 if (conn_header)
155 { 160 {
156 if (connection_header_contains(conn_header, "close")) 161 if (connection_header_contains(conn_header, "close"))
157 should_keep_alive = FALSE; 162 should_keep_alive = FALSE;
158 else if (connection_header_contains(conn_header, "keep-alive")) 163 else if (connection_header_contains(conn_header, "keep-alive"))
183 goto clean_up_arenas; 188 goto clean_up_arenas;
184 } 189 }
185 190
186 // --- Check for WebSocket upgrade request --- 191 // --- Check for WebSocket upgrade request ---
187 #ifdef SEOBEO_WEBSOCKET_SERVER 192 #ifdef SEOBEO_WEBSOCKET_SERVER
193 Seobeo_Log(SEOBEO_DEBUG, "Web socket path \n");
188 if (Seobeo_WebSocket_Server_Handle_Upgrade(p_cli_handle, p_req_map, path)) 194 if (Seobeo_WebSocket_Server_Handle_Upgrade(p_cli_handle, p_req_map, path))
189 { 195 {
190 Seobeo_Log(SEOBEO_INFO, "WebSocket connection established\n"); 196 Seobeo_Log(SEOBEO_INFO, "WebSocket connection established\n");
191 Dowa_Arena_Free(p_request_arena); 197 Dowa_Arena_Free(p_request_arena);
192 Dowa_Arena_Free(p_response_arena); 198 Dowa_Arena_Free(p_response_arena);
193 return FALSE; // WebSocket takes over, don't keep-alive in HTTP sense 199 return FALSE; // WebSocket takes over, don't keep-alive in HTTP sense
194 } 200 }
195 #endif 201 #endif
196 202
197 // --- Try to match API route first --- 203 // --- Try to match streaming route first ---
204 Seobeo_Stream_Handler stream_handler = Seobeo_Router_Find_Stream_Handler(method, path, &p_req_map, p_request_arena);
205 if (stream_handler != NULL)
206 {
207 stream_handler(p_cli_handle, p_req_map, p_response_arena);
208 goto clean_up_arenas;
209 }
210
211 // --- Try to match API route ---
198 Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena); 212 Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena);
199 if (handler != NULL) 213 if (handler != NULL)
200 { 214 {
201 Seobeo_Request_Entry *p_response_map = handler(p_req_map, p_response_arena); 215 Seobeo_Request_Entry *p_response_map = handler(p_req_map, p_response_arena);
202 Seobeo_Router_Send_Response_KeepAlive(p_cli_handle, p_response_map, p_response_arena, should_keep_alive); 216 Seobeo_Router_Send_Response_KeepAlive(p_cli_handle, p_response_map, p_response_arena, should_keep_alive);
331 if (p_handle->read_buffer_len >= 4 && 345 if (p_handle->read_buffer_len >= 4 &&
332 strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL) 346 strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL)
333 break; 347 break;
334 348
335 if (r == 0) 349 if (r == 0)
336 return 1; // EAGAIN, try again later TODO: Add this as part of Handle struct. 350 {
351 Seobeo_Log(SEOBEO_INFO, "Waiting?\n");
352 continue; // EAGAIN, try again later TODO: Add this as part of Handle struct.
353 }
337 } 354 }
338 355
339 // "METHOD SP PATH SP VERSION CRLF" 356 // "METHOD SP PATH SP VERSION CRLF"
340 char *buf = (char*)p_handle->read_buffer; 357 char *buf = (char*)p_handle->read_buffer;
341 char *hdr_end = strstr(buf, "\r\n\r\n"); 358 char *hdr_end = strstr(buf, "\r\n\r\n");
446 while (line < hdr_end) 463 while (line < hdr_end)
447 { 464 {
448 char *next = strstr(line, "\r\n"); 465 char *next = strstr(line, "\r\n");
449 if (!next) break; 466 if (!next) break;
450 467
451 // split at colon
452 char *colon = memchr(line, ':', next - line); 468 char *colon = memchr(line, ':', next - line);
453 if (colon) 469 if (colon)
454 { 470 {
455 size_t key_len = colon - line; 471 size_t key_len = colon - line;
456 size_t value_len = next - colon - 1; 472 size_t value_len = next - colon - 1;
470 char *val = Dowa_Arena_Allocate(p_arena, value_len + 1); 486 char *val = Dowa_Arena_Allocate(p_arena, value_len + 1);
471 if (!val) return -1; 487 if (!val) return -1;
472 memcpy(val, val_start, value_len); 488 memcpy(val, val_start, value_len);
473 val[value_len] = '\0'; 489 val[value_len] = '\0';
474 490
475 // Both key and value are arena-allocated, hashmap will use them
476 Dowa_HashMap_Push_Arena(*pp_map, key, val, p_arena); 491 Dowa_HashMap_Push_Arena(*pp_map, key, val, p_arena);
477 } 492 }
478 493
479 line = next + 2; 494 line = next + 2;
480 } 495 }
488 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; 503 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value;
489 size_t body_len = atoi(content_length_str); 504 size_t body_len = atoi(content_length_str);
490 505
491 Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len); 506 Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len);
492 507
493 // Allocate buffer for entire body
494 char *body = Dowa_Arena_Allocate(p_arena, body_len + 1); 508 char *body = Dowa_Arena_Allocate(p_arena, body_len + 1);
495 if (!body) 509 if (!body)
496 { 510 {
497 Seobeo_Log(SEOBEO_ERROR, "Failed to allocate %zu bytes for body\n", body_len); 511 Seobeo_Log(SEOBEO_ERROR, "Failed to allocate %zu bytes for body\n", body_len);
498 return -1; 512 return -1;
604 /* Router logic */ 618 /* Router logic */
605 struct Seobeo_Route_Struct { 619 struct Seobeo_Route_Struct {
606 char *method; // "GET", "POST", "PUT", "DELETE" 620 char *method; // "GET", "POST", "PUT", "DELETE"
607 char *path_pattern; // "/v1/users/:id/posts/:post_id" 621 char *path_pattern; // "/v1/users/:id/posts/:post_id"
608 Seobeo_Route_Handler handler; 622 Seobeo_Route_Handler handler;
623 Seobeo_Stream_Handler stream_handler; // For streaming responses
609 624
610 // Pre-parsed path segments for efficient matching 625 // Pre-parsed path segments for efficient matching
611 char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"] 626 char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"]
612 boolean *is_param; // [false, false, true, false, true] 627 boolean *is_param; // [false, false, true, false, true]
613 size_t segment_count; 628 size_t segment_count;
625 Seobeo_Route route = {0}; 640 Seobeo_Route route = {0};
626 641
627 route.method = strdup(method); 642 route.method = strdup(method);
628 route.path_pattern = strdup(path_pattern); 643 route.path_pattern = strdup(path_pattern);
629 route.handler = handler; 644 route.handler = handler;
645 route.stream_handler = NULL;
646 route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL);
647 route.segment_count = Dowa_Array_Length(route.path_segments);
648 route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count);
649
650 for (size_t i = 0; i < route.segment_count; i++)
651 route.is_param[i] = (route.path_segments[i][0] == ':');
652
653 Dowa_Array_Push(g_routes, route);
654 }
655
656 void Seobeo_Router_Register_Stream(const char *method, const char *path_pattern, Seobeo_Stream_Handler handler)
657 {
658 Seobeo_Route route = {0};
659
660 route.method = strdup(method);
661 route.path_pattern = strdup(path_pattern);
662 route.handler = NULL;
663 route.stream_handler = handler;
630 route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL); 664 route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL);
631 route.segment_count = Dowa_Array_Length(route.path_segments); 665 route.segment_count = Dowa_Array_Length(route.path_segments);
632 route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count); 666 route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count);
633 667
634 for (size_t i = 0; i < route.segment_count; i++) 668 for (size_t i = 0; i < route.segment_count; i++)
707 } 741 }
708 742
709 return NULL; 743 return NULL;
710 } 744 }
711 745
746 Seobeo_Stream_Handler Seobeo_Router_Find_Stream_Handler(
747 const char *method,
748 const char *path,
749 Seobeo_Request_Entry **pp_request_map,
750 Dowa_Arena *p_arena)
751 {
752 if (g_routes == NULL || method == NULL || path == NULL)
753 return NULL;
754
755 size_t route_count = Dowa_Array_Length(g_routes);
756 for (size_t i = 0; i < route_count; i++)
757 {
758 Seobeo_Route *route = &g_routes[i];
759 if (strcmp(route->method, method) != 0)
760 continue;
761
762 if (route->stream_handler && match_route_and_extract(route, path, pp_request_map, p_arena))
763 return route->stream_handler;
764 }
765
766 return NULL;
767 }
768
712 void Seobeo_Router_Send_Response( 769 void Seobeo_Router_Send_Response(
713 Seobeo_Handle *p_handle, 770 Seobeo_Handle *p_handle,
714 Seobeo_Request_Entry *p_response_map, 771 Seobeo_Request_Entry *p_response_map,
715 Dowa_Arena *p_arena) 772 Dowa_Arena *p_arena)
716 { 773 {
742 } 799 }
743 800
744 const char *body = ""; 801 const char *body = "";
745 void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body"); 802 void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body");
746 if (p_body_kv) 803 if (p_body_kv)
747 {
748 body = ((Seobeo_Request_Entry*)p_body_kv)->value; 804 body = ((Seobeo_Request_Entry*)p_body_kv)->value;
749 }
750 805
751 const char *content_type = "text/html"; 806 const char *content_type = "text/html";
752 void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type"); 807 void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type");
753 if (p_content_type_kv) 808 if (p_content_type_kv)
754 {
755 content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value; 809 content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value;
756 } 810
757 811 // TODO: Update this to be integer
758 size_t body_length = strlen(body); 812 size_t body_length;
759 void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length"); 813 void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length");
760 if (p_content_length_kv) 814 if (p_content_length_kv)
761 { 815 {
762 const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value; 816 const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value;
763 body_length = atoi(content_length_str); 817 body_length = atoi(content_length_str);
764 } 818 }
819 else
820 body_length = strlen(body);
765 821
766 char *header = Dowa_Arena_Allocate(p_arena, 4096); 822 char *header = Dowa_Arena_Allocate(p_arena, 4096);
767 Seobeo_Web_Header_Generate_KeepAlive(header, status, content_type, body_length, keep_alive); 823 Seobeo_Web_Header_Generate_KeepAlive(header, status, content_type, body_length, keep_alive);
768 for (int i = 0; i < Dowa_Array_Length(p_response_map); i++) 824 for (int i = 0; i < Dowa_Array_Length(p_response_map); i++)
769 { 825 {
780 sprintf(temp, "%s: %s\r\n\r\n", p_response_map[i].key, p_response_map[i].value); 836 sprintf(temp, "%s: %s\r\n\r\n", p_response_map[i].key, p_response_map[i].value);
781 memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp)); 837 memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp));
782 free(temp); 838 free(temp);
783 } 839 }
784 840
841 printf("hEADER %s\n", header);
842
785 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); 843 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header));
786 Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); 844 Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length);
787 Seobeo_Handle_Flush(p_handle); 845 Seobeo_Handle_Flush(p_handle);
788 } 846 }
789 847
802 if (route->is_param) free(route->is_param); 860 if (route->is_param) free(route->is_param);
803 } 861 }
804 Dowa_Array_Free(g_routes); 862 Dowa_Array_Free(g_routes);
805 g_routes = NULL; 863 g_routes = NULL;
806 } 864 }
865
866 // Written by AI. I don't know what it does.
867 void Seobeo_Url_Decode(char *dst, const char *src)
868 {
869 char a, b;
870 while (*src) {
871 /* Check if we have a % followed by two valid hex characters */
872 if (*src == '%' && src[1] && src[2]) {
873 a = src[1];
874 b = src[2];
875
876 /* Manual isxdigit check and conversion for 'a' */
877 int a_val = -1;
878 if (a >= '0' && a <= '9') a_val = a - '0';
879 else if (a >= 'a' && a <= 'f') a_val = a - 'a' + 10;
880 else if (a >= 'A' && a <= 'F') a_val = a - 'A' + 10;
881
882 /* Manual isxdigit check and conversion for 'b' */
883 int b_val = -1;
884 if (b >= '0' && b <= '9') b_val = b - '0';
885 else if (b >= 'a' && b <= 'f') b_val = b - 'a' + 10;
886 else if (b >= 'A' && b <= 'F') b_val = b - 'A' + 10;
887
888 /* If both were valid hex, combine them */
889 if (a_val != -1 && b_val != -1) {
890 *dst++ = (char)((a_val << 4) | b_val);
891 src += 3;
892 continue;
893 }
894 }
895
896 /* Handle '+' as space, otherwise copy character literally */
897 if (*src == '+') {
898 *dst++ = ' ';
899 } else {
900 *dst++ = *src;
901 }
902 src++;
903 }
904 *dst = '\0';
905 }