comparison seobeo/s_web.c @ 110:99c4530e4629

[Seobeo] Small Syntax fixes.
author June Park <parkjune1995@gmail.com>
date Sat, 03 Jan 2026 21:16:17 -0800
parents 3468e2fe8d88
children c39582f937e5
comparison
equal deleted inserted replaced
109:1c446ab6f945 110:99c4530e4629
1 #include "seobeo/seobeo.h" 1 #include "seobeo/seobeo.h"
2 2
3 // Global folder path for serving files
4 static char g_folder_path[512] = "."; 3 static char g_folder_path[512] = ".";
5 4
6 int Seobeo_Web_GenerateRequestHeader( 5 int Seobeo_Web_GenerateRequestHeader(
7 void *buffer, const char *host, 6 void *buffer, const char *host,
8 const char *path) 7 const char *path)
157 (uint32)strlen(p_response_header)); 156 (uint32)strlen(p_response_header));
158 Seobeo_Handle_Flush(p_cli_handle); 157 Seobeo_Handle_Flush(p_cli_handle);
159 goto clean_up; 158 goto clean_up;
160 } 159 }
161 160
162 // Extract path
163 void *p_path_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Path"); 161 void *p_path_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Path");
164 const char *path = p_path_kv ? ((Seobeo_Request_Entry*)p_path_kv)->value : "/"; 162 const char *path = p_path_kv ? ((Seobeo_Request_Entry*)p_path_kv)->value : "/";
165 163
166 // --- Try to match API route first --- 164 // --- Try to match API route first ---
167 Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena); 165 Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena);
170 Seobeo_Request_Entry *p_response_map = handler(p_req_map, p_response_arena); 168 Seobeo_Request_Entry *p_response_map = handler(p_req_map, p_response_arena);
171 Seobeo_Router_Send_Response(p_cli_handle, p_response_map, p_response_arena); 169 Seobeo_Router_Send_Response(p_cli_handle, p_response_map, p_response_arena);
172 goto clean_up; 170 goto clean_up;
173 } 171 }
174 172
175 // --- Handle different HTTP methods (static files fallback) --- 173 // --- Static files fallback for GET ---
176 if (strcmp(method, "GET") == 0) 174 if (strcmp(method, "GET") == 0)
177 { 175 {
178 void *p_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Path"); 176 void *p_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Path");
179 const char *path = p_kv ? ((Seobeo_Request_Entry*)p_kv)->value : NULL; 177 const char *path = p_kv ? ((Seobeo_Request_Entry*)p_kv)->value : NULL;
180 char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)5 * 1024); // 5Kb only for path 178 char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)5 * 1024); // 5Kb only for path
280 } 278 }
281 279
282 280
283 int Seobeo_Web_Header_Parse(Seobeo_Handle *p_handle, Seobeo_Request_Entry **pp_map, Dowa_Arena *p_arena) 281 int Seobeo_Web_Header_Parse(Seobeo_Handle *p_handle, Seobeo_Request_Entry **pp_map, Dowa_Arena *p_arena)
284 { 282 {
285 // 1) Fill read_buffer until we see "\r\n\r\n"
286 while (1) 283 while (1)
287 { 284 {
288 int r = Seobeo_Handle_Read(p_handle); 285 int r = Seobeo_Handle_Read(p_handle);
289 if (r < 0) 286 if (r < 0)
290 return -1; // fatal error 287 return -1; // fatal error
291 if (r == -2) 288 if (r == -2)
292 return -2; // connection closed TODO: Add this as part of Handle struct. 289 return -2; // connection closed TODO: Add this as part of Handle struct.
293 290
294 if (p_handle->read_buffer_len >= 4 && 291 if (p_handle->read_buffer_len >= 4 &&
295 strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL) 292 strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL)
296 {
297 break; 293 break;
298 } 294
299 if (r == 0) 295 if (r == 0)
300 return 1; // EAGAIN, try again later TODO: Add this as part of Handle struct. 296 return 1; // EAGAIN, try again later TODO: Add this as part of Handle struct.
301 } 297 }
302 298
303 // 2) Parse request‐line "METHOD SP PATH SP VERSION CRLF" 299 // "METHOD SP PATH SP VERSION CRLF"
304 char *buf = (char*)p_handle->read_buffer; 300 char *buf = (char*)p_handle->read_buffer;
305 char *hdr_end = strstr(buf, "\r\n\r\n"); 301 char *hdr_end = strstr(buf, "\r\n\r\n");
306 size_t hdr_len = hdr_end - buf + 4; 302 size_t hdr_len = hdr_end - buf + 4;
307 303
308 // Debug: Print the first line of the request 304 // Debug: Print the first line of the request
326 { 322 {
327 Seobeo_Log(SEOBEO_ERROR, "Failed to parse request line\n"); 323 Seobeo_Log(SEOBEO_ERROR, "Failed to parse request line\n");
328 return -1; 324 return -1;
329 } 325 }
330 326
331 // Copy strings to arena and store in hashmap
332 Seobeo_Log(SEOBEO_DEBUG, "Allocating method_copy\n"); 327 Seobeo_Log(SEOBEO_DEBUG, "Allocating method_copy\n");
333 char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1); 328 char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1);
334 if (!method_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate method_copy\n"); return -1; } 329 if (!method_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate method_copy\n"); return -1; }
335 strcpy(method_copy, method); 330 strcpy(method_copy, method);
336 331
342 Seobeo_Log(SEOBEO_DEBUG, "Pushing HTTP_Method and Version to map\n"); 337 Seobeo_Log(SEOBEO_DEBUG, "Pushing HTTP_Method and Version to map\n");
343 Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena); 338 Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena);
344 Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena); 339 Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena);
345 Seobeo_Log(SEOBEO_DEBUG, "Map now has %zu entries\n", Dowa_Array_Length(*pp_map)); 340 Seobeo_Log(SEOBEO_DEBUG, "Map now has %zu entries\n", Dowa_Array_Length(*pp_map));
346 341
347 // 1) Separate raw path and query string
348 char *raw_path = path; 342 char *raw_path = path;
349 char *query_start = strchr(raw_path, '?'); 343 char *query_start = strchr(raw_path, '?');
350 char *query_str = NULL; 344 char *query_str = NULL;
351 345
352 if (query_start) 346 if (query_start)
353 { 347 {
354 *query_start = '\0'; // now raw_path ends before '?' 348 *query_start = '\0'; // now raw_path ends before '?'
355 query_str = query_start + 1; 349 query_str = query_start + 1;
356 } 350 }
357 351
358 // push only the clean path
359 char *path_copy = Dowa_Arena_Allocate(p_arena, strlen(raw_path) + 1); 352 char *path_copy = Dowa_Arena_Allocate(p_arena, strlen(raw_path) + 1);
360 if (!path_copy) return -1; 353 if (!path_copy) return -1;
361 strcpy(path_copy, raw_path); 354 strcpy(path_copy, raw_path);
362 Dowa_HashMap_Push_Arena(*pp_map, "Path", path_copy, p_arena); 355 Dowa_HashMap_Push_Arena(*pp_map, "Path", path_copy, p_arena);
363 356
364 // 2) If there *is* a query, tokenize into the same map with "query_" prefix 357 // GET Params
365 if (query_str && *query_str) 358 if (query_str && *query_str)
366 { 359 {
367 char *cur = query_str; 360 char *cur = query_str;
368 while (cur && *cur) 361 while (cur && *cur)
369 { 362 {
375 { 368 {
376 size_t key_len = eq - cur; 369 size_t key_len = eq - cur;
377 size_t val_len = pair_end - (eq + 1); 370 size_t val_len = pair_end - (eq + 1);
378 371
379 char key_buf[256]; 372 char key_buf[256];
373 // Adding query_ in front to separate GET params
380 snprintf(key_buf, sizeof(key_buf), "query_%.*s", (int)key_len, cur); 374 snprintf(key_buf, sizeof(key_buf), "query_%.*s", (int)key_len, cur);
381 375
382 char *val_copy = Dowa_Arena_Allocate(p_arena, val_len + 1); 376 char *val_copy = Dowa_Arena_Allocate(p_arena, val_len + 1);
383 if (!val_copy) return -1; 377 if (!val_copy) return -1;
384 memcpy(val_copy, eq + 1, val_len); 378 memcpy(val_copy, eq + 1, val_len);
394 // int qp = Dowa_HashMap_Get_Position(map, "QueryParams"); 388 // int qp = Dowa_HashMap_Get_Position(map, "QueryParams");
395 // Dowa_HashEntry *p_qp_entry = map->entries[qp]; 389 // Dowa_HashEntry *p_qp_entry = map->entries[qp];
396 // printf("query param key: %s\n", p_qp_entry->key); 390 // printf("query param key: %s\n", p_qp_entry->key);
397 // printf("query param value: %s\n",(char *)Dowa_HashMap_Get(p_qp_entry->buffer, "hello")); 391 // printf("query param value: %s\n",(char *)Dowa_HashMap_Get(p_qp_entry->buffer, "hello"));
398 392
399 // 3) Parse each header line until the blank line 393 // Parse headers
400 char *line = buf + strlen(method) + 1 + strlen(path) + 1 + strlen(version) + 2; 394 char *line = buf + strlen(method) + 1 + strlen(path) + 1 + strlen(version) + 2;
401 while (line < hdr_end) 395 while (line < hdr_end)
402 { 396 {
403 char *next = strstr(line, "\r\n"); 397 char *next = strstr(line, "\r\n");
404 if (!next) break; 398 if (!next) break;
434 line = next + 2; 428 line = next + 2;
435 } 429 }
436 430
437 Seobeo_Handle_Consume(p_handle, (uint32)hdr_len); 431 Seobeo_Handle_Consume(p_handle, (uint32)hdr_len);
438 432
439 // 4) If Content-Length was provided, read that much body 433 // Reading Body
440 void *p_cl_kv = Dowa_HashMap_Get_Ptr(*pp_map, "Content-Length"); 434 void *p_cl_kv = Dowa_HashMap_Get_Ptr(*pp_map, "Content-Length");
441 if (p_cl_kv) 435 if (p_cl_kv)
442 { 436 {
443 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; 437 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value;
444 size_t body_len = atoi(content_length_str); 438 size_t body_len = atoi(content_length_str);
453 return -1; 447 return -1;
454 } 448 }
455 449
456 size_t total_read = 0; 450 size_t total_read = 0;
457 451
458 // Read body in chunks
459 while (total_read < body_len) 452 while (total_read < body_len)
460 { 453 {
461 // Copy what's currently in the read buffer
462 size_t available = p_handle->read_buffer_len; 454 size_t available = p_handle->read_buffer_len;
463 size_t to_copy = (body_len - total_read) < available ? (body_len - total_read) : available; 455 size_t to_copy = (body_len - total_read) < available ? (body_len - total_read) : available;
464 456
465 if (to_copy > 0) 457 if (to_copy > 0)
466 { 458 {
469 Seobeo_Handle_Consume(p_handle, (uint32)to_copy); 461 Seobeo_Handle_Consume(p_handle, (uint32)to_copy);
470 462
471 Seobeo_Log(SEOBEO_DEBUG, "Copied %zu bytes, total %zu/%zu\n", to_copy, total_read, body_len); 463 Seobeo_Log(SEOBEO_DEBUG, "Copied %zu bytes, total %zu/%zu\n", to_copy, total_read, body_len);
472 } 464 }
473 465
474 // If we still need more data, read another chunk
475 if (total_read < body_len) 466 if (total_read < body_len)
476 { 467 {
477 int r = Seobeo_Handle_Read(p_handle); 468 int r = Seobeo_Handle_Read(p_handle);
478 if (r < 0) 469 if (r < 0)
479 { 470 {
490 } 481 }
491 482
492 body[body_len] = '\0'; 483 body[body_len] = '\0';
493 Seobeo_Log(SEOBEO_DEBUG, "Body fully received (%zu bytes)\n", body_len); 484 Seobeo_Log(SEOBEO_DEBUG, "Body fully received (%zu bytes)\n", body_len);
494 485
495 // Body is arena-allocated
496 Dowa_HashMap_Push_Arena(*pp_map, "Body", body, p_arena); 486 Dowa_HashMap_Push_Arena(*pp_map, "Body", body, p_arena);
497 } 487 }
498 488
499 return 0; // success; map now holds Method, Path, Version, headers, and optional Body 489 return 0;
500 } 490 }
501 491
502 // TODO: Do epoll or kqueue depending on the OS.
503 void SigchildHandler(int s) 492 void SigchildHandler(int s)
504 { 493 {
505 (void)s; // quiet unused variable warning 494 (void)s;
506 495
507 // waitpid() might overwrite errno, so we save and restore it: 496 // waitpid() might overwrite errno, so we save and restore it:
508 int saved_errno = errno; 497 int saved_errno = errno;
509 498
510 while(waitpid(-1, NULL, WNOHANG) > 0); 499 while(waitpid(-1, NULL, WNOHANG) > 0);
516 const char *folder_path, 505 const char *folder_path,
517 const char *port, 506 const char *port,
518 Seobeo_ServerMode mode, 507 Seobeo_ServerMode mode,
519 int thread_count) 508 int thread_count)
520 { 509 {
521 // Store folder path globally
522 if (folder_path) 510 if (folder_path)
523 strncpy(g_folder_path, folder_path, sizeof(g_folder_path) - 1); 511 strncpy(g_folder_path, folder_path, sizeof(g_folder_path) - 1);
524 512
525 // Initialize empty cache - files will be loaded on-demand
526 Seobeo_Cache_Entry *p_html_cache = NULL; 513 Seobeo_Cache_Entry *p_html_cache = NULL;
527 514
528 Seobeo_Handle *p_server_handle = 515 Seobeo_Handle *p_server_handle =
529 Seobeo_Stream_Handle_Server_Create(NULL, port); 516 Seobeo_Stream_Handle_Server_Create(NULL, port);
530 if (p_server_handle->socket < 0) return 1; 517 if (p_server_handle->socket < 0) return 1;
531 518
532 Seobeo_Log(SEOBEO_INFO, "Listening on port %s\n", port); 519 Seobeo_Log(SEOBEO_INFO, "Listening on port %s\n", port);
533 520
534 // Fork‐based fallback
535 if (mode == SEOBEO_MODE_FORK) 521 if (mode == SEOBEO_MODE_FORK)
536 { 522 {
537 Seobeo_Log(SEOBEO_INFO, "Server mode: FORK\n"); 523 Seobeo_Log(SEOBEO_INFO, "Server mode: FORK\n");
538 struct sigaction sa; 524 struct sigaction sa;
539 sa.sa_handler = SigchildHandler; 525 sa.sa_handler = SigchildHandler;
605 { 591 {
606 // TODO: Maybe directly use arena inside of the struct? idk if that is useful or not... 592 // TODO: Maybe directly use arena inside of the struct? idk if that is useful or not...
607 memcpy(p_request_body + used, h->read_buffer, h->read_buffer_len); 593 memcpy(p_request_body + used, h->read_buffer, h->read_buffer_len);
608 used += h->read_buffer_len; 594 used += h->read_buffer_len;
609 Seobeo_Handle_Consume(h, (uint32)h->read_buffer_len); 595 Seobeo_Handle_Consume(h, (uint32)h->read_buffer_len);
610 }else if (n == 0) 596 }
597 else if (n == 0)
611 { 598 {
612 // Wait 599 // Wait
613 continue; 600 continue;
614 }else if (n == -2) 601 }
615 { 602 else if (n == -2)
616 // Debug 603 {
617 // peer closed; we’ve got everything
618 Seobeo_Log(SEOBEO_DEBUG, "Connection closed by client\n"); 604 Seobeo_Log(SEOBEO_DEBUG, "Connection closed by client\n");
619 break; 605 break;
620 }else 606 }
607 else
621 { 608 {
622 Dowa_Arena_Free(p_request_arena); 609 Dowa_Arena_Free(p_request_arena);
623 Seobeo_Handle_Destroy(h); 610 Seobeo_Handle_Destroy(h);
624 return -1; 611 return -1;
625 } 612 }
626 } 613 }
627 614
628 // Debug
629 Seobeo_Log(SEOBEO_DEBUG, "Request body: %s\n", p_request_body); 615 Seobeo_Log(SEOBEO_DEBUG, "Request body: %s\n", p_request_body);
630 Dowa_Arena_Free(p_request_arena); 616 Dowa_Arena_Free(p_request_arena);
631 Seobeo_Handle_Destroy(h); 617 Seobeo_Handle_Destroy(h);
632 return 0; 618 return 0;
633 } 619 }
666 route.is_param[i] = (route.path_segments[i][0] == ':'); 652 route.is_param[i] = (route.path_segments[i][0] == ':');
667 653
668 Dowa_Array_Push(g_routes, route); 654 Dowa_Array_Push(g_routes, route);
669 } 655 }
670 656
671 // Match route and extract path parameters
672 static boolean match_route_and_extract( 657 static boolean match_route_and_extract(
673 Seobeo_Route *route, 658 Seobeo_Route *route,
674 const char *request_path, 659 const char *request_path,
675 Seobeo_Request_Entry **pp_request_map, 660 Seobeo_Request_Entry **pp_request_map,
676 Dowa_Arena *p_arena) 661 Dowa_Arena *p_arena)
677 { 662 {
678 Dowa_Arena *p_temp_arena = Dowa_Arena_Create(1024); 663 Dowa_Arena *p_temp_arena = Dowa_Arena_Create(1024);
679 char **request_segments = Dowa_String_Split(request_path, "/", strlen(request_path), 1, p_temp_arena); 664 char **request_segments = Dowa_String_Split(request_path, "/", strlen(request_path), 1, p_temp_arena);
680 size_t request_segment_count = Dowa_Array_Length(request_segments); 665 size_t request_segment_count = Dowa_Array_Length(request_segments);
666
681 // Check segment count matches 667 // Check segment count matches
682 if (request_segment_count != route->segment_count) 668 if (request_segment_count != route->segment_count)
683 { 669 {
684 Dowa_Arena_Free(p_temp_arena); 670 Dowa_Arena_Free(p_temp_arena);
685 return FALSE; 671 return FALSE;
753 Seobeo_Handle_Queue(p_handle, (uint8_t*)"Internal Server Error", 21); 739 Seobeo_Handle_Queue(p_handle, (uint8_t*)"Internal Server Error", 21);
754 Seobeo_Handle_Flush(p_handle); 740 Seobeo_Handle_Flush(p_handle);
755 return; 741 return;
756 } 742 }
757 743
758 // Header
759 int status = HTTP_OK; 744 int status = HTTP_OK;
760 void *p_status_kv = Dowa_HashMap_Get_Ptr(p_response_map, "status"); 745 void *p_status_kv = Dowa_HashMap_Get_Ptr(p_response_map, "status");
761 if (p_status_kv) 746 if (p_status_kv)
762 { 747 {
763 const char *status_str = ((Seobeo_Request_Entry*)p_status_kv)->value; 748 const char *status_str = ((Seobeo_Request_Entry*)p_status_kv)->value;
764 status = atoi(status_str); 749 status = atoi(status_str);
765 } 750 }
766 751
767 // Body
768 const char *body = ""; 752 const char *body = "";
769 void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body"); 753 void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body");
770 if (p_body_kv) 754 if (p_body_kv)
771 { 755 {
772 body = ((Seobeo_Request_Entry*)p_body_kv)->value; 756 body = ((Seobeo_Request_Entry*)p_body_kv)->value;
773 } 757 }
774 758
775 // Default text plain
776 const char *content_type = "text/html"; 759 const char *content_type = "text/html";
777 void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type"); 760 void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type");
778 if (p_content_type_kv) 761 if (p_content_type_kv)
779 { 762 {
780 content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value; 763 content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value;
781 } 764 }
782 765
783 // Check for custom content-length (for binary data)
784 size_t body_length = strlen(body); 766 size_t body_length = strlen(body);
785 void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length"); 767 void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length");
786 if (p_content_length_kv) 768 if (p_content_length_kv)
787 { 769 {
788 const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value; 770 const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value;
789 body_length = atoi(content_length_str); 771 body_length = atoi(content_length_str);
790 } 772 }
791 773
792 // All other types are default thoguht to be headers.
793 char *header = Dowa_Arena_Allocate(p_arena, 4096); 774 char *header = Dowa_Arena_Allocate(p_arena, 4096);
794 Seobeo_Web_Header_Generate(header, status, content_type, body_length); 775 Seobeo_Web_Header_Generate(header, status, content_type, body_length);
795 for (int i = 0; i < Dowa_Array_Length(p_response_map); i++) 776 for (int i = 0; i < Dowa_Array_Length(p_response_map); i++)
796 { 777 {
797 if ( 778 if (
798 strstr(p_response_map[i].key, "status") || 779 strstr(p_response_map[i].key, "status") ||
799 strstr(p_response_map[i].key, "body") || 780 strstr(p_response_map[i].key, "body") ||
800 strstr(p_response_map[i].key, "content-type") || 781 strstr(p_response_map[i].key, "content-type") ||
801 strstr(p_response_map[i].key, "content-length") // Skip custom content-length 782 strstr(p_response_map[i].key, "content-length")
802 ) 783 )
803 continue; 784 continue;
804 785
805 int32 current_header_len = strlen(header); 786 int32 current_header_len = strlen(header);
806 char *temp = malloc(sizeof(char) * 1024); 787 char *temp = malloc(sizeof(char) * 1024);
808 memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp)); 789 memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp));
809 free(temp); 790 free(temp);
810 } 791 }
811 792
812 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); 793 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header));
813 Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); // Use actual body length 794 Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length);
814 Seobeo_Handle_Flush(p_handle); 795 Seobeo_Handle_Flush(p_handle);
815 } 796 }
816 797
817 void Seobeo_Router_Destroy() 798 void Seobeo_Router_Destroy()
818 { 799 {