Mercurial
comparison seobeo/s_web.c @ 96:70401cf61e97
[Seobeo] Added logging.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 02 Jan 2026 19:16:17 -0800 |
| parents | 655ea0b661fd |
| children | 65e5a5b89a4e |
comparison
equal
deleted
inserted
replaced
| 95:b51f8cce9170 | 96:70401cf61e97 |
|---|---|
| 91 } | 91 } |
| 92 | 92 |
| 93 void Seobeo_Web_HandleClientRequest(Seobeo_Handle *p_cli_handle, | 93 void Seobeo_Web_HandleClientRequest(Seobeo_Handle *p_cli_handle, |
| 94 Seobeo_Cache_Entry *p_html_cache) | 94 Seobeo_Cache_Entry *p_html_cache) |
| 95 { | 95 { |
| 96 Seobeo_Log(SEOBEO_INFO, "Client is from %s\n", p_cli_handle->host); | |
| 96 Dowa_Arena *p_request_arena = Dowa_Arena_Create(1*1024*1024); // 1MB for request parsing | 97 Dowa_Arena *p_request_arena = Dowa_Arena_Create(1*1024*1024); // 1MB for request parsing |
| 97 if (!p_request_arena) { perror("Dowa_Arena_Create request"); goto clean_up; } | 98 if (!p_request_arena) { perror("Dowa_Arena_Create request"); goto clean_up; } |
| 98 | 99 |
| 99 Dowa_Arena *p_response_arena = Dowa_Arena_Create(5*1024*1024); // 1MB for response | 100 Dowa_Arena *p_response_arena = Dowa_Arena_Create(5*1024*1024); // 5MB for response |
| 100 if (!p_response_arena) { perror("Dowa_Arena_Create response"); goto clean_up; } | 101 if (!p_response_arena) { perror("Dowa_Arena_Create response"); goto clean_up; } |
| 101 | 102 |
| 102 void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)1024*5); // 5Kb | 103 void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)1024*5); // 5Kb |
| 103 if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; } | 104 if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; } |
| 104 | 105 |
| 105 // Parse request headers into hashmap using arena | |
| 106 Seobeo_Request_Entry *p_req_map = NULL; | 106 Seobeo_Request_Entry *p_req_map = NULL; |
| 107 int parse_result = Seobeo_Web_Header_Parse(p_cli_handle, &p_req_map, p_request_arena); | 107 int parse_result = Seobeo_Web_Header_Parse(p_cli_handle, &p_req_map, p_request_arena); |
| 108 | 108 |
| 109 // Treat EAGAIN (return code 1) as success - headers were parsed, body may still be coming | |
| 110 if (parse_result != 0 && parse_result != 1) | 109 if (parse_result != 0 && parse_result != 1) |
| 111 { | 110 { |
| 112 printf("ERROR: Seobeo_Web_Header_Parse failed with code %d\n", parse_result); | 111 Seobeo_Log(SEOBEO_ERROR, "Seobeo_Web_Header_Parse failed with code %d\n", parse_result); |
| 113 fflush(stdout); | 112 fflush(stdout); |
| 114 Seobeo_Web_Header_Generate(p_response_header, | 113 Seobeo_Web_Header_Generate(p_response_header, |
| 115 HTTP_BAD_REQUEST, | 114 HTTP_BAD_REQUEST, |
| 116 "text/plain", 0); | 115 "text/plain", 0); |
| 117 Seobeo_Handle_Queue(p_cli_handle, | 116 Seobeo_Handle_Queue(p_cli_handle, |
| 119 (uint32)strlen(p_response_header)); | 118 (uint32)strlen(p_response_header)); |
| 120 Seobeo_Handle_Flush(p_cli_handle); | 119 Seobeo_Handle_Flush(p_cli_handle); |
| 121 goto clean_up; | 120 goto clean_up; |
| 122 } | 121 } |
| 123 | 122 |
| 124 printf("DEBUG: Parse completed with code %d\n", parse_result); | 123 Seobeo_Log(SEOBEO_DEBUG, "Parse completed with code %d\n", parse_result); |
| 125 fflush(stdout); | 124 fflush(stdout); |
| 126 | 125 |
| 127 // Extract method (GET, POST, etc.) | |
| 128 void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method"); | 126 void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method"); |
| 129 const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL; | 127 const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL; |
| 130 | 128 |
| 131 printf("DEBUG: Parsed request, method=%s\n", method ? method : "NULL"); | 129 Seobeo_Log(SEOBEO_DEBUG, "Parsed request, method=%s\n", method ? method : "NULL"); |
| 132 fflush(stdout); | 130 fflush(stdout); |
| 133 | 131 |
| 134 if (!method) | 132 if (!method) |
| 135 { | 133 { |
| 136 printf("ERROR: No HTTP method found in request\n"); | 134 Seobeo_Log(SEOBEO_ERROR, "No HTTP method found in request\n"); |
| 137 fflush(stdout); | 135 fflush(stdout); |
| 138 Seobeo_Web_Header_Generate(p_response_header, | 136 Seobeo_Web_Header_Generate(p_response_header, |
| 139 HTTP_BAD_REQUEST, | 137 HTTP_BAD_REQUEST, |
| 140 "text/plain", 0); | 138 "text/plain", 0); |
| 141 Seobeo_Handle_Queue(p_cli_handle, | 139 Seobeo_Handle_Queue(p_cli_handle, |
| 223 else if (strstr(file_path, ".gif")) mime = "image/gif"; | 221 else if (strstr(file_path, ".gif")) mime = "image/gif"; |
| 224 else if (strstr(file_path, ".svg")) mime = "image/svg+xml"; | 222 else if (strstr(file_path, ".svg")) mime = "image/svg+xml"; |
| 225 else if (strstr(file_path, ".ico")) mime = "image/x-icon"; | 223 else if (strstr(file_path, ".ico")) mime = "image/x-icon"; |
| 226 else if (strstr(file_path, ".json")) mime = "application/json"; | 224 else if (strstr(file_path, ".json")) mime = "application/json"; |
| 227 | 225 |
| 228 printf("File path: %s\nBody Size: %zu\n", file_path, body_size); | 226 Seobeo_Log(SEOBEO_DEBUG, "File path: %s\nBody Size: %zu\n", file_path, body_size); |
| 229 | 227 |
| 230 Seobeo_Web_Header_Generate(p_response_header, | 228 Seobeo_Web_Header_Generate(p_response_header, |
| 231 HTTP_OK, | 229 HTTP_OK, |
| 232 mime, | 230 mime, |
| 233 body_size); | 231 body_size); |
| 237 (uint32)strlen(p_response_header)); | 235 (uint32)strlen(p_response_header)); |
| 238 Seobeo_Handle_Queue(p_cli_handle, | 236 Seobeo_Handle_Queue(p_cli_handle, |
| 239 (const uint8*)file_content, | 237 (const uint8*)file_content, |
| 240 (uint32)body_size); | 238 (uint32)body_size); |
| 241 Seobeo_Handle_Flush(p_cli_handle); | 239 Seobeo_Handle_Flush(p_cli_handle); |
| 242 printf("DONE\n\n\n"); | 240 Seobeo_Log(SEOBEO_DEBUG, "Request handled successfully\n"); |
| 243 } | 241 } |
| 244 else | 242 else |
| 245 { | 243 { |
| 246 Seobeo_Web_Header_Generate(p_response_header, | 244 Seobeo_Web_Header_Generate(p_response_header, |
| 247 HTTP_FORBIDDEN, | 245 HTTP_FORBIDDEN, |
| 252 Seobeo_Handle_Flush(p_cli_handle); | 250 Seobeo_Handle_Flush(p_cli_handle); |
| 253 } | 251 } |
| 254 goto clean_up; | 252 goto clean_up; |
| 255 | 253 |
| 256 clean_up: | 254 clean_up: |
| 257 printf("clean up\n\n"); | 255 Seobeo_Log(SEOBEO_INFO, "Clean up all Arenas\n"); |
| 258 if (p_cli_handle) | 256 if (p_cli_handle) |
| 259 Seobeo_Handle_Destroy(p_cli_handle); | 257 Seobeo_Handle_Destroy(p_cli_handle); |
| 260 if (p_request_arena) | 258 if (p_request_arena) |
| 261 Dowa_Arena_Free(p_request_arena); | 259 Dowa_Arena_Free(p_request_arena); |
| 262 if (p_response_arena) | 260 if (p_response_arena) |
| 293 // Debug: Print the first line of the request | 291 // Debug: Print the first line of the request |
| 294 char *first_line_end = strstr(buf, "\r\n"); | 292 char *first_line_end = strstr(buf, "\r\n"); |
| 295 if (first_line_end) | 293 if (first_line_end) |
| 296 { | 294 { |
| 297 size_t first_line_len = first_line_end - buf; | 295 size_t first_line_len = first_line_end - buf; |
| 298 printf("DEBUG: Request line (first %zu bytes): '", first_line_len > 200 ? 200 : first_line_len); | 296 Seobeo_Log(SEOBEO_DEBUG, "Request line (first %zu bytes)\n", first_line_len > 200 ? 200 : first_line_len); |
| 299 fwrite(buf, 1, first_line_len > 200 ? 200 : first_line_len, stdout); | |
| 300 printf("'\n"); | |
| 301 fflush(stdout); | 297 fflush(stdout); |
| 302 } | 298 } |
| 303 | 299 |
| 304 // This seems kinda bad ? | 300 // This seems kinda bad ? |
| 305 char method[16], path[256], version[16]; | 301 char method[16], path[256], version[16]; |
| 306 int scan_result = sscanf(buf, "%15s %255s %15s", method, path, version); | 302 int scan_result = sscanf(buf, "%15s %255s %15s", method, path, version); |
| 307 printf("DEBUG: sscanf returned %d (method='%s', path='%s', version='%s')\n", | 303 Seobeo_Log(SEOBEO_DEBUG, "sscanf returned %d (method='%s', path='%s', version='%s')\n", |
| 308 scan_result, | 304 scan_result, |
| 309 scan_result >= 1 ? method : "N/A", | 305 scan_result >= 1 ? method : "N/A", |
| 310 scan_result >= 2 ? path : "N/A", | 306 scan_result >= 2 ? path : "N/A", |
| 311 scan_result >= 3 ? version : "N/A"); | 307 scan_result >= 3 ? version : "N/A"); |
| 312 fflush(stdout); | 308 fflush(stdout); |
| 313 | 309 |
| 314 if (scan_result != 3) | 310 if (scan_result != 3) |
| 315 { | 311 { |
| 316 printf("ERROR: Failed to parse request line\n"); | 312 Seobeo_Log(SEOBEO_ERROR, "Failed to parse request line\n"); |
| 317 fflush(stdout); | 313 fflush(stdout); |
| 318 return -1; | 314 return -1; |
| 319 } | 315 } |
| 320 | 316 |
| 321 // Copy strings to arena and store in hashmap | 317 // Copy strings to arena and store in hashmap |
| 322 printf("DEBUG: Allocating method_copy\n"); | 318 Seobeo_Log(SEOBEO_DEBUG, "Allocating method_copy\n"); |
| 323 fflush(stdout); | 319 fflush(stdout); |
| 324 char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1); | 320 char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1); |
| 325 if (!method_copy) { printf("ERROR: Failed to allocate method_copy\n"); return -1; } | 321 if (!method_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate method_copy\n"); return -1; } |
| 326 strcpy(method_copy, method); | 322 strcpy(method_copy, method); |
| 327 | 323 |
| 328 printf("DEBUG: Allocating version_copy\n"); | 324 Seobeo_Log(SEOBEO_DEBUG, "Allocating version_copy\n"); |
| 329 fflush(stdout); | 325 fflush(stdout); |
| 330 char *version_copy = Dowa_Arena_Allocate(p_arena, strlen(version) + 1); | 326 char *version_copy = Dowa_Arena_Allocate(p_arena, strlen(version) + 1); |
| 331 if (!version_copy) { printf("ERROR: Failed to allocate version_copy\n"); return -1; } | 327 if (!version_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate version_copy\n"); return -1; } |
| 332 strcpy(version_copy, version); | 328 strcpy(version_copy, version); |
| 333 | 329 |
| 334 printf("DEBUG: Pushing HTTP_Method and Version to map\n"); | 330 Seobeo_Log(SEOBEO_DEBUG, "Pushing HTTP_Method and Version to map\n"); |
| 335 fflush(stdout); | 331 fflush(stdout); |
| 336 Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena); | 332 Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena); |
| 337 Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena); | 333 Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena); |
| 338 printf("DEBUG: Map now has %zu entries\n", Dowa_Array_Length(*pp_map)); | 334 Seobeo_Log(SEOBEO_DEBUG, "Map now has %zu entries\n", Dowa_Array_Length(*pp_map)); |
| 339 fflush(stdout); | 335 fflush(stdout); |
| 340 | 336 |
| 341 // 1) Separate raw path and query string | 337 // 1) Separate raw path and query string |
| 342 char *raw_path = path; | 338 char *raw_path = path; |
| 343 char *query_start = strchr(raw_path, '?'); | 339 char *query_start = strchr(raw_path, '?'); |
| 435 if (p_cl_kv) | 431 if (p_cl_kv) |
| 436 { | 432 { |
| 437 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; | 433 const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; |
| 438 size_t body_len = atoi(content_length_str); | 434 size_t body_len = atoi(content_length_str); |
| 439 | 435 |
| 440 printf("DEBUG: Content-Length=%zu, reading body in chunks...\n", body_len); | 436 Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len); |
| 441 fflush(stdout); | 437 fflush(stdout); |
| 442 | 438 |
| 443 // Allocate buffer for entire body | 439 // Allocate buffer for entire body |
| 444 char *body = Dowa_Arena_Allocate(p_arena, body_len + 1); | 440 char *body = Dowa_Arena_Allocate(p_arena, body_len + 1); |
| 445 if (!body) | 441 if (!body) |
| 446 { | 442 { |
| 447 printf("ERROR: Failed to allocate %zu bytes for body\n", body_len); | 443 Seobeo_Log(SEOBEO_ERROR, "Failed to allocate %zu bytes for body\n", body_len); |
| 448 fflush(stdout); | 444 fflush(stdout); |
| 449 return -1; | 445 return -1; |
| 450 } | 446 } |
| 451 | 447 |
| 452 size_t total_read = 0; | 448 size_t total_read = 0; |
| 462 { | 458 { |
| 463 memcpy(body + total_read, p_handle->read_buffer, to_copy); | 459 memcpy(body + total_read, p_handle->read_buffer, to_copy); |
| 464 total_read += to_copy; | 460 total_read += to_copy; |
| 465 Seobeo_Handle_Consume(p_handle, (uint32)to_copy); | 461 Seobeo_Handle_Consume(p_handle, (uint32)to_copy); |
| 466 | 462 |
| 467 printf("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); |
| 468 fflush(stdout); | 464 fflush(stdout); |
| 469 } | 465 } |
| 470 | 466 |
| 471 // If we still need more data, read another chunk | 467 // If we still need more data, read another chunk |
| 472 if (total_read < body_len) | 468 if (total_read < body_len) |
| 473 { | 469 { |
| 474 int r = Seobeo_Handle_Read(p_handle); | 470 int r = Seobeo_Handle_Read(p_handle); |
| 475 if (r < 0) | 471 if (r < 0) |
| 476 { | 472 { |
| 477 printf("ERROR: Read failed with %d\n", r); | 473 Seobeo_Log(SEOBEO_ERROR, "Read failed with %d\n", r); |
| 478 fflush(stdout); | 474 fflush(stdout); |
| 479 return -1; | 475 return -1; |
| 480 } | 476 } |
| 481 if (r == 0) | 477 if (r == 0) |
| 482 { | 478 { |
| 487 } | 483 } |
| 488 } | 484 } |
| 489 } | 485 } |
| 490 | 486 |
| 491 body[body_len] = '\0'; | 487 body[body_len] = '\0'; |
| 492 printf("DEBUG: Body fully received (%zu bytes)\n", body_len); | 488 Seobeo_Log(SEOBEO_DEBUG, "Body fully received (%zu bytes)\n", body_len); |
| 493 fflush(stdout); | 489 fflush(stdout); |
| 494 | 490 |
| 495 // Body is arena-allocated | 491 // Body is arena-allocated |
| 496 Dowa_HashMap_Push_Arena(*pp_map, "Body", body, p_arena); | 492 Dowa_HashMap_Push_Arena(*pp_map, "Body", body, p_arena); |
| 497 } | 493 } |
| 527 | 523 |
| 528 Seobeo_Handle *p_server_handle = | 524 Seobeo_Handle *p_server_handle = |
| 529 Seobeo_Stream_Handle_Server_Create(NULL, port); | 525 Seobeo_Stream_Handle_Server_Create(NULL, port); |
| 530 if (p_server_handle->socket < 0) return 1; | 526 if (p_server_handle->socket < 0) return 1; |
| 531 | 527 |
| 532 printf("Listening on port %s\n", port); | 528 Seobeo_Log(SEOBEO_INFO, "Listening on port %s\n", port); |
| 533 | 529 |
| 534 // Fork‐based fallback | 530 // Fork‐based fallback |
| 535 if (mode == SEOBEO_MODE_FORK) | 531 if (mode == SEOBEO_MODE_FORK) |
| 536 { | 532 { |
| 537 printf("FORK MODE\n"); | 533 Seobeo_Log(SEOBEO_INFO, "Server mode: FORK\n"); |
| 538 struct sigaction sa; | 534 struct sigaction sa; |
| 539 sa.sa_handler = SigchildHandler; | 535 sa.sa_handler = SigchildHandler; |
| 540 sigemptyset(&sa.sa_mask); | 536 sigemptyset(&sa.sa_mask); |
| 541 sa.sa_flags = SA_RESTART; | 537 sa.sa_flags = SA_RESTART; |
| 542 sigaction(SIGCHLD, &sa, NULL); | 538 sigaction(SIGCHLD, &sa, NULL); |
| 556 } | 552 } |
| 557 } | 553 } |
| 558 | 554 |
| 559 if (mode == SEOBEO_MODE_EDGE) | 555 if (mode == SEOBEO_MODE_EDGE) |
| 560 { | 556 { |
| 561 printf("EDGE MODE\n"); | 557 Seobeo_Log(SEOBEO_INFO, "Server mode: EDGE\n"); |
| 562 Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache); | 558 Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache); |
| 563 } | 559 } |
| 564 | 560 |
| 565 return -1; | 561 return -1; |
| 566 } | 562 } |
| 598 if (!p_request_body) { Seobeo_Handle_Destroy(h); return -1; } | 594 if (!p_request_body) { Seobeo_Handle_Destroy(h); return -1; } |
| 599 | 595 |
| 600 while (1) | 596 while (1) |
| 601 { | 597 { |
| 602 int n = Seobeo_Handle_Read(h); | 598 int n = Seobeo_Handle_Read(h); |
| 603 printf("Size: %d\n", n); | 599 Seobeo_Log(SEOBEO_DEBUG, "Received size: %d bytes\n", n); |
| 604 if (n > 0) | 600 if (n > 0) |
| 605 { | 601 { |
| 606 // TODO: Maybe directly use arena inside of the struct? idk if that is useful or not... | 602 // 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); | 603 memcpy(p_request_body + used, h->read_buffer, h->read_buffer_len); |
| 608 used += h->read_buffer_len; | 604 used += h->read_buffer_len; |
| 613 continue; | 609 continue; |
| 614 }else if (n == -2) | 610 }else if (n == -2) |
| 615 { | 611 { |
| 616 // Debug | 612 // Debug |
| 617 // peer closed; we’ve got everything | 613 // peer closed; we’ve got everything |
| 618 printf("\n\nCLOSED\n\n"); | 614 Seobeo_Log(SEOBEO_DEBUG, "Connection closed by client\n"); |
| 619 break; | 615 break; |
| 620 }else | 616 }else |
| 621 { | 617 { |
| 622 Dowa_Arena_Free(p_request_arena); | 618 Dowa_Arena_Free(p_request_arena); |
| 623 Seobeo_Handle_Destroy(h); | 619 Seobeo_Handle_Destroy(h); |
| 624 return -1; | 620 return -1; |
| 625 } | 621 } |
| 626 } | 622 } |
| 627 | 623 |
| 628 // Debug | 624 // Debug |
| 629 printf("Request body %s", p_request_body); | 625 Seobeo_Log(SEOBEO_DEBUG, "Request body: %s\n", p_request_body); |
| 630 Dowa_Arena_Free(p_request_arena); | 626 Dowa_Arena_Free(p_request_arena); |
| 631 Seobeo_Handle_Destroy(h); | 627 Seobeo_Handle_Destroy(h); |
| 632 return 0; | 628 return 0; |
| 633 } | 629 } |
| 630 | |
| 631 /* Router logic */ | |
| 632 struct Seobeo_Route_Struct { | |
| 633 char *method; // "GET", "POST", "PUT", "DELETE" | |
| 634 char *path_pattern; // "/v1/users/:id/posts/:post_id" | |
| 635 Seobeo_Route_Handler handler; | |
| 636 | |
| 637 // Pre-parsed path segments for efficient matching | |
| 638 char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"] | |
| 639 boolean *is_param; // [false, false, true, false, true] | |
| 640 size_t segment_count; | |
| 641 }; | |
| 642 | |
| 643 static Seobeo_Route *g_routes = NULL; | |
| 644 | |
| 645 void Seobeo_Router_Init() | |
| 646 { | |
| 647 Dowa_Array_Reserve(g_routes, 20); | |
| 648 } | |
| 649 | |
| 650 void Seobeo_Router_Register(const char *method, const char *path_pattern, Seobeo_Route_Handler handler) | |
| 651 { | |
| 652 Seobeo_Route route = {0}; | |
| 653 | |
| 654 route.method = strdup(method); | |
| 655 route.path_pattern = strdup(path_pattern); | |
| 656 route.handler = handler; | |
| 657 route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL); | |
| 658 route.segment_count = Dowa_Array_Length(route.path_segments); | |
| 659 route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count); | |
| 660 | |
| 661 for (size_t i = 0; i < route.segment_count; i++) | |
| 662 route.is_param[i] = (route.path_segments[i][0] == ':'); | |
| 663 | |
| 664 Dowa_Array_Push(g_routes, route); | |
| 665 } | |
| 666 | |
| 667 // Match route and extract path parameters | |
| 668 static boolean match_route_and_extract( | |
| 669 Seobeo_Route *route, | |
| 670 const char *request_path, | |
| 671 Seobeo_Request_Entry **pp_request_map, | |
| 672 Dowa_Arena *p_arena) | |
| 673 { | |
| 674 Dowa_Arena *p_temp_arena = Dowa_Arena_Create(1024); | |
| 675 char **request_segments = Dowa_String_Split(request_path, "/", strlen(request_path), 1, p_temp_arena); | |
| 676 size_t request_segment_count = Dowa_Array_Length(request_segments); | |
| 677 // Check segment count matches | |
| 678 if (request_segment_count != route->segment_count) | |
| 679 { | |
| 680 Dowa_Arena_Free(p_temp_arena); | |
| 681 return FALSE; | |
| 682 } | |
| 683 | |
| 684 for (size_t i = 0; i < route->segment_count; i++) | |
| 685 { | |
| 686 // parameters | |
| 687 if (route->is_param[i]) | |
| 688 { | |
| 689 char *param_name = route->path_segments[i]; // e.g., ":id" | |
| 690 char *param_value = request_segments[i]; // e.g., "123" | |
| 691 | |
| 692 // Should Copy to arena | |
| 693 char *key = Dowa_String_Copy_Arena(param_name, p_arena); | |
| 694 char *value = Dowa_String_Copy_Arena(param_value, p_arena); | |
| 695 Dowa_HashMap_Push_Arena(*pp_request_map, key, value, p_arena); | |
| 696 } | |
| 697 else | |
| 698 { | |
| 699 // Does not match. | |
| 700 if (strcmp(route->path_segments[i], request_segments[i]) != 0) | |
| 701 { | |
| 702 Dowa_Arena_Free(p_temp_arena); | |
| 703 return FALSE; | |
| 704 } | |
| 705 } | |
| 706 } | |
| 707 | |
| 708 Dowa_Arena_Free(p_temp_arena); | |
| 709 return TRUE; | |
| 710 } | |
| 711 | |
| 712 Seobeo_Route_Handler Seobeo_Router_Find_Handler(const char *method, | |
| 713 const char *path, | |
| 714 Seobeo_Request_Entry **pp_request_map, | |
| 715 Dowa_Arena *p_arena) { | |
| 716 if (g_routes == NULL) | |
| 717 { | |
| 718 return NULL; | |
| 719 } | |
| 720 | |
| 721 size_t route_count = Dowa_Array_Length(g_routes); | |
| 722 for (size_t i = 0; i < route_count; i++) | |
| 723 { | |
| 724 Seobeo_Route *route = &g_routes[i]; | |
| 725 if (strcmp(route->method, method) != 0) | |
| 726 { | |
| 727 continue; | |
| 728 } | |
| 729 | |
| 730 if (match_route_and_extract(route, path, pp_request_map, p_arena)) | |
| 731 { | |
| 732 return route->handler; | |
| 733 } | |
| 734 } | |
| 735 | |
| 736 return NULL; | |
| 737 } | |
| 738 | |
| 739 void Seobeo_Router_Send_Response( | |
| 740 Seobeo_Handle *p_handle, | |
| 741 Seobeo_Request_Entry *p_response_map, | |
| 742 Dowa_Arena *p_arena) | |
| 743 { | |
| 744 if (p_response_map == NULL) | |
| 745 { | |
| 746 char *header = Dowa_Arena_Allocate(p_arena, 1024); | |
| 747 Seobeo_Web_Header_Generate(header, HTTP_INTERNAL_ERROR, "text/plain", 21); | |
| 748 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); | |
| 749 Seobeo_Handle_Queue(p_handle, (uint8_t*)"Internal Server Error", 21); | |
| 750 Seobeo_Handle_Flush(p_handle); | |
| 751 return; | |
| 752 } | |
| 753 | |
| 754 // Header | |
| 755 int status = HTTP_OK; | |
| 756 void *p_status_kv = Dowa_HashMap_Get_Ptr(p_response_map, "status"); | |
| 757 if (p_status_kv) | |
| 758 { | |
| 759 const char *status_str = ((Seobeo_Request_Entry*)p_status_kv)->value; | |
| 760 status = atoi(status_str); | |
| 761 } | |
| 762 | |
| 763 // Body | |
| 764 const char *body = ""; | |
| 765 void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body"); | |
| 766 if (p_body_kv) | |
| 767 { | |
| 768 body = ((Seobeo_Request_Entry*)p_body_kv)->value; | |
| 769 } | |
| 770 | |
| 771 // Default text plain | |
| 772 const char *content_type = "text/html"; | |
| 773 void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type"); | |
| 774 if (p_content_type_kv) | |
| 775 { | |
| 776 content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value; | |
| 777 } | |
| 778 | |
| 779 // Check for custom content-length (for binary data) | |
| 780 size_t body_length = strlen(body); | |
| 781 void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length"); | |
| 782 if (p_content_length_kv) | |
| 783 { | |
| 784 const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value; | |
| 785 body_length = atoi(content_length_str); | |
| 786 } | |
| 787 | |
| 788 // All other types are default thoguht to be headers. | |
| 789 char *header = Dowa_Arena_Allocate(p_arena, 4096); | |
| 790 Seobeo_Web_Header_Generate(header, status, content_type, body_length); | |
| 791 for (int i = 0; i < Dowa_Array_Length(p_response_map); i++) | |
| 792 { | |
| 793 if ( | |
| 794 strstr(p_response_map[i].key, "status") || | |
| 795 strstr(p_response_map[i].key, "body") || | |
| 796 strstr(p_response_map[i].key, "content-type") || | |
| 797 strstr(p_response_map[i].key, "content-length") // Skip custom content-length | |
| 798 ) | |
| 799 continue; | |
| 800 | |
| 801 int32 current_header_len = strlen(header); | |
| 802 char *temp = malloc(sizeof(char) * 1024); | |
| 803 sprintf(temp, "%s: %s\r\n\r\n", p_response_map[i].key, p_response_map[i].value); | |
| 804 memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp)); | |
| 805 free(temp); | |
| 806 } | |
| 807 | |
| 808 Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); | |
| 809 Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); // Use actual body length | |
| 810 Seobeo_Handle_Flush(p_handle); | |
| 811 } | |
| 812 | |
| 813 void Seobeo_Router_Destroy() | |
| 814 { | |
| 815 if (g_routes == NULL) | |
| 816 return; | |
| 817 | |
| 818 size_t route_count = Dowa_Array_Length(g_routes); | |
| 819 for (size_t i = 0; i < route_count; i++) | |
| 820 { | |
| 821 Seobeo_Route *route = &g_routes[i]; | |
| 822 if (route->method) free(route->method); | |
| 823 if (route->path_pattern) free(route->path_pattern); | |
| 824 if (route->path_segments) Dowa_Array_Free(route->path_segments); | |
| 825 if (route->is_param) free(route->is_param); | |
| 826 } | |
| 827 Dowa_Array_Free(g_routes); | |
| 828 g_routes = NULL; | |
| 829 } | |
| 830 | |
| 831 static char *Seobeo_Log_Level_String(Seobeo_Log_Level level) | |
| 832 { | |
| 833 switch(level) { | |
| 834 case SEOBEO_DEBUG: return "DEBUG"; | |
| 835 case SEOBEO_INFO: return "INFO"; | |
| 836 case SEOBEO_WARNING: return "WARNING"; | |
| 837 case SEOBEO_ERROR: return "ERROR"; | |
| 838 default: return "INFO"; | |
| 839 } | |
| 840 } | |
| 841 | |
| 842 int Seobeo_Log(Seobeo_Log_Level level, const char * restrict format, ...) | |
| 843 { | |
| 844 #ifndef SEOBEO_ENABLE_DEBUG | |
| 845 if (level == SEOBEO_DEBUG) | |
| 846 return 0; | |
| 847 #endif | |
| 848 | |
| 849 int result; | |
| 850 va_list args; | |
| 851 printf("[%s] ", Seobeo_Log_Level_String(level)); | |
| 852 va_start(args, format); | |
| 853 result = vprintf(format, args); | |
| 854 va_end(args); | |
| 855 | |
| 856 return result; | |
| 857 } | |
| 858 |