Mercurial
view seobeo/s_web.c @ 198:008ca7780c8a
S3 upload got it to work.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sat, 14 Feb 2026 16:18:14 -0800 |
| parents | a69485d9f2e1 |
| children |
line wrap: on
line source
#include "seobeo/seobeo.h" #include <strings.h> #include <time.h> static char g_folder_path[512] = "."; char* Seobeo_Web_LoadFile(const char *file_path, size_t *p_file_size) { char full_path[1024]; snprintf(full_path, sizeof(full_path), "%s/%s", g_folder_path, file_path); FILE *p_file = fopen(full_path, "rb"); if (!p_file) return NULL; fseek(p_file, 0, SEEK_END); size_t file_size = ftell(p_file); fseek(p_file, 0, SEEK_SET); char *p_content = (char*)malloc(file_size + 1); if (!p_content) { fclose(p_file); return NULL; } fread(p_content, 1, file_size, p_file); p_content[file_size] = '\0'; fclose(p_file); if (p_file_size) *p_file_size = file_size; return p_content; } void Seobeo_Web_Header_Generate( void *buffer, int status, const char *content_type, const int content_length) { Seobeo_Web_Header_Generate_KeepAlive(buffer, status, content_type, content_length, FALSE); } void Seobeo_Web_Header_Generate_KeepAlive( void *buffer, int status, const char *content_type, const int content_length, boolean keep_alive) { const char *status_text; switch(status) { case HTTP_OK: status_text = "OK"; break; case HTTP_CREATED: status_text = "Created"; break; case HTTP_MOVED_PERMANENTLY: status_text = "Moved Permanently"; break; case HTTP_FOUND: status_text = "Found"; break; case HTTP_BAD_REQUEST: status_text = "Bad Request"; break; case HTTP_UNAUTHORIZED: status_text = "Unauthorized"; break; case HTTP_FORBIDDEN: status_text = "Forbidden"; break; case HTTP_NOT_FOUND: status_text = "Not Found"; break; case HTTP_INTERNAL_ERROR: status_text = "Internal Server Error"; break; default: status_text = "Unknown"; break; } sprintf( buffer, "HTTP/1.1 %d %s\r\n" "Content-Type: %s\r\n" "Content-Length: %d\r\n" "Connection: %s\r\n" "\r\n", status, status_text, content_type, content_length, keep_alive ? "keep-alive" : "close" ); } // Default arena size (5MB) - will allocate more if Content-Length requires it #define DEFAULT_ARENA_SIZE (5 * 1024 * 1024) // Helper to check if Connection header contains a specific value (case-insensitive) static boolean connection_header_contains(const char *header_value, const char *target) { if (!header_value || !target) return FALSE; // Make a copy to tokenize char *copy = strdup(header_value); if (!copy) return FALSE; boolean found = FALSE; char *token = strtok(copy, ", \t"); while (token) { if (strcasecmp(token, target) == 0) { found = TRUE; break; } token = strtok(NULL, ", \t"); } free(copy); return found; } // Returns TRUE if connection should be kept alive, FALSE if it should be closed boolean Seobeo_Web_ClientHandle_Request(Seobeo_Handle *p_cli_handle, Seobeo_Cache_Entry *p_html_cache, boolean use_keep_alive) { boolean should_keep_alive = FALSE; // Start with default arena size (5MB) size_t arena_size = DEFAULT_ARENA_SIZE; // We'll peek at Content-Length to potentially allocate larger arena // For now, start with default and handle body reading separately Dowa_Arena *p_request_arena = Dowa_Arena_Create(arena_size); if (!p_request_arena) { perror("Dowa_Arena_Create request"); return FALSE; } Dowa_Arena *p_response_arena = Dowa_Arena_Create(arena_size); if (!p_response_arena) { perror("Dowa_Arena_Create response"); Dowa_Arena_Free(p_request_arena); return FALSE; } void *p_response_header = Dowa_Arena_Allocate(p_response_arena, 1024*5); // 5Kb if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up_arenas; } Seobeo_Request_Entry *p_req_map = NULL; int parse_result = Seobeo_Web_Header_Parse(p_cli_handle, &p_req_map, p_request_arena); // Handle parse errors if (parse_result < 0) { // Fatal error or connection closed Seobeo_Log(SEOBEO_DEBUG, "Seobeo_Web_Header_Parse failed with code %d\n", parse_result); if (parse_result == -2) { // Connection closed by client - don't send error response goto clean_up_arenas; } Seobeo_Web_Header_Generate(p_response_header, HTTP_BAD_REQUEST, "text/plain", 0); Seobeo_Handle_Queue(p_cli_handle, (const uint8*)p_response_header, (uint32)strlen(p_response_header)); Seobeo_Handle_Flush(p_cli_handle); goto clean_up_arenas; } // Determine keep-alive based on HTTP version and Connection header // HTTP/1.1: keep-alive by default unless "Connection: close" // HTTP/1.0: close by default unless "Connection: keep-alive" void *p_ver_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Version"); const char *http_version = p_ver_kv ? ((Seobeo_Request_Entry*)p_ver_kv)->value : "HTTP/1.0"; boolean is_http11 = (strstr(http_version, "1.1") != NULL); void *p_conn_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Connection"); const char *conn_header = p_conn_kv ? ((Seobeo_Request_Entry*)p_conn_kv)->value : NULL; void *p_real_ip_kv = Dowa_HashMap_Get_Ptr(p_req_map, "X-Real-IP"); const char *real_ip = p_real_ip_kv ? ((Seobeo_Request_Entry*)p_real_ip_kv)->value : NULL; if (!real_ip) real_ip = p_cli_handle->host; if (conn_header) { if (connection_header_contains(conn_header, "close")) should_keep_alive = FALSE; else if (connection_header_contains(conn_header, "keep-alive")) should_keep_alive = use_keep_alive; else should_keep_alive = is_http11 && use_keep_alive; // Unknown value, use version default } else should_keep_alive = is_http11 && use_keep_alive; void *p_method_kv = Dowa_HashMap_Get_Ptr(p_req_map, "HTTP_Method"); const char *method = p_method_kv ? ((Seobeo_Request_Entry*)p_method_kv)->value : NULL; void *p_path_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Path"); const char *path = p_path_kv ? ((Seobeo_Request_Entry*)p_path_kv)->value : "/"; if (!method) { Seobeo_Log(SEOBEO_DEBUG, "No HTTP method found in request\n"); Seobeo_Web_Header_Generate(p_response_header, HTTP_BAD_REQUEST, "text/plain", 0); Seobeo_Handle_Queue(p_cli_handle, (const uint8*)p_response_header, (uint32)strlen(p_response_header)); Seobeo_Handle_Flush(p_cli_handle); should_keep_alive = FALSE; goto clean_up_arenas; } // --- Check for WebSocket upgrade request --- #ifdef SEOBEO_WEBSOCKET_SERVER Seobeo_Log(SEOBEO_DEBUG, "Web socket path \n"); if (Seobeo_WebSocket_Server_Handle_Upgrade(p_cli_handle, p_req_map, path)) { Seobeo_Log(SEOBEO_INFO, "WebSocket connection established\n"); Dowa_Arena_Free(p_request_arena); Dowa_Arena_Free(p_response_arena); return FALSE; // WebSocket takes over, don't keep-alive in HTTP sense } #endif // --- Try to match streaming route first --- Seobeo_Stream_Handler stream_handler = Seobeo_Router_Find_Stream_Handler(method, path, &p_req_map, p_request_arena); if (stream_handler != NULL) { stream_handler(p_cli_handle, p_req_map, p_response_arena); goto clean_up_arenas; } // --- Try to match API route --- Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena); if (handler != NULL) { Seobeo_Request_Entry *p_response_map = handler(p_req_map, p_response_arena); Seobeo_Router_Send_Response_KeepAlive(p_cli_handle, p_response_map, p_response_arena, should_keep_alive); goto clean_up_arenas; } // --- Static files fallback for GET (use original large arena logic) --- if (strcmp(method, "GET") == 0) { char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)5 * 1024); if (!path || strcmp(path, "/") == 0) { strcpy(file_path, "index.html"); } else { size_t L = strlen(path); if (path[0] == '/') { if (strchr(path, '.') == NULL) snprintf(file_path, 512, "%.*s/index.html", (int)(L-1), path+1); else snprintf(file_path, 512, "%.*s", (int)(L-1), path+1); } else strcpy(file_path, path); } void *p_file_kv = Dowa_HashMap_Get_Ptr(p_html_cache, file_path); const char *file_content = NULL; size_t body_size = 0; if (p_file_kv) { Seobeo_Cached_File *cached = ((Seobeo_Cache_Entry*)p_file_kv)->value; file_content = cached->content; body_size = cached->size; } else { file_content = Seobeo_Web_LoadFile(file_path, &body_size); if (file_content) { Seobeo_Cached_File *cached = malloc(sizeof(Seobeo_Cached_File)); cached->content = (char*)file_content; cached->size = body_size; Dowa_HashMap_Push(p_html_cache, file_path, cached); } } if (!file_content) { Seobeo_Web_Header_Generate_KeepAlive(p_response_header, HTTP_NOT_FOUND, "text/html", 0, should_keep_alive); Seobeo_Handle_Queue(p_cli_handle, (const uint8*)p_response_header, (uint32)strlen(p_response_header)); Seobeo_Handle_Flush(p_cli_handle); goto clean_up_arenas; } const char *mime = "application/octet-stream"; if (strstr(file_path, ".html")) mime = "text/html; charset=utf-8"; else if (strstr(file_path, ".css")) mime = "text/css"; else if (strstr(file_path, ".js")) mime = "application/javascript"; else if (strstr(file_path, ".png")) mime = "image/png"; else if (strstr(file_path, ".jpg") || strstr(file_path, ".jpeg")) mime = "image/jpeg"; else if (strstr(file_path, ".gif")) mime = "image/gif"; else if (strstr(file_path, ".svg")) mime = "image/svg+xml"; else if (strstr(file_path, ".ico")) mime = "image/x-icon"; else if (strstr(file_path, ".webp")) mime = "image/webp"; else if (strstr(file_path, ".json")) mime = "application/json"; else if (strstr(file_path, ".wasm")) mime = "application/wasm"; else if (strstr(file_path, ".xml")) mime = "application/xml"; else if (strstr(file_path, ".pdf")) mime = "application/pdf"; else if (strstr(file_path, ".txt")) mime = "text/plain"; else if (strstr(file_path, ".mp4")) mime = "video/mp4"; else if (strstr(file_path, ".webm")) mime = "video/webm"; else if (strstr(file_path, ".mp3")) mime = "audio/mpeg"; else if (strstr(file_path, ".woff2")) mime = "font/woff2"; else if (strstr(file_path, ".woff")) mime = "font/woff"; else if (strstr(file_path, ".ttf")) mime = "font/ttf"; else if (strstr(file_path, ".webp")) mime = "image/webp"; else if (strstr(file_path, ".glb")) mime = "model/gltf-binary"; else if (strstr(file_path, ".gltf")) mime = "model/gltf+json"; Seobeo_Web_Header_Generate_KeepAlive(p_response_header, HTTP_OK, mime, body_size, should_keep_alive); Seobeo_Handle_Queue(p_cli_handle, (const uint8*)p_response_header, (uint32)strlen(p_response_header)); Seobeo_Handle_Queue(p_cli_handle, (const uint8*)file_content, (uint32)body_size); Seobeo_Handle_Flush(p_cli_handle); } else { Seobeo_Web_Header_Generate_KeepAlive(p_response_header, HTTP_NOT_FOUND, "text/plain", 0, should_keep_alive); Seobeo_Handle_Queue(p_cli_handle, (const uint8*)p_response_header, (uint32)strlen(p_response_header)); Seobeo_Handle_Flush(p_cli_handle); } clean_up_arenas: if (p_request_arena) Dowa_Arena_Free(p_request_arena); if (p_response_arena) Dowa_Arena_Free(p_response_arena); return should_keep_alive; } int Seobeo_Web_Header_Parse(Seobeo_Handle *p_handle, Seobeo_Request_Entry **pp_map, Dowa_Arena *p_arena) { while (1) { int r = Seobeo_Handle_Read(p_handle); if (r < 0) return -1; // fatal error if (r == -2) return -2; // connection closed TODO: Add this as part of Handle struct. if (p_handle->read_buffer_len >= 4 && strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL) break; if (r == 0) { Seobeo_Log(SEOBEO_INFO, "Waiting?\n"); continue; // EAGAIN, try again later TODO: Add this as part of Handle struct. } } // "METHOD SP PATH SP VERSION CRLF" char *buf = (char*)p_handle->read_buffer; char *hdr_end = strstr(buf, "\r\n\r\n"); size_t hdr_len = hdr_end - buf + 4; // Debug: Print the first line of the request char *first_line_end = strstr(buf, "\r\n"); if (first_line_end) { size_t first_line_len = first_line_end - buf; Seobeo_Log(SEOBEO_DEBUG, "Request line (first %zu bytes)\n", first_line_len > 200 ? 200 : first_line_len); } // This seems kinda bad ? char method[16], path[256], version[16]; int scan_result = sscanf(buf, "%15s %255s %15s", method, path, version); Seobeo_Log(SEOBEO_DEBUG, "sscanf returned %d (method='%s', path='%s', version='%s')\n", scan_result, scan_result >= 1 ? method : "N/A", scan_result >= 2 ? path : "N/A", scan_result >= 3 ? version : "N/A"); if (scan_result != 3) { Seobeo_Log(SEOBEO_ERROR, "Failed to parse request line\n"); return -1; } Seobeo_Log(SEOBEO_DEBUG, "Allocating method_copy\n"); char *method_copy = Dowa_Arena_Allocate(p_arena, strlen(method) + 1); if (!method_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate method_copy\n"); return -1; } strcpy(method_copy, method); Seobeo_Log(SEOBEO_DEBUG, "Allocating version_copy\n"); char *version_copy = Dowa_Arena_Allocate(p_arena, strlen(version) + 1); if (!version_copy) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate version_copy\n"); return -1; } strcpy(version_copy, version); Seobeo_Log(SEOBEO_DEBUG, "Pushing HTTP_Method and Version to map\n"); Dowa_HashMap_Push_Arena(*pp_map, "HTTP_Method", method_copy, p_arena); Dowa_HashMap_Push_Arena(*pp_map, "Version", version_copy, p_arena); Seobeo_Log(SEOBEO_DEBUG, "Map now has %zu entries\n", Dowa_Array_Length(*pp_map)); char *raw_path = path; char *query_start = strchr(raw_path, '?'); char *query_str = NULL; if (query_start) { *query_start = '\0'; // now raw_path ends before '?' query_str = query_start + 1; } char *path_copy = Dowa_Arena_Allocate(p_arena, strlen(raw_path) + 1); if (!path_copy) return -1; strcpy(path_copy, raw_path); Dowa_HashMap_Push_Arena(*pp_map, "Path", path_copy, p_arena); // Store full query string for proxying if (query_str && *query_str) { char *qs_copy = Dowa_Arena_Allocate(p_arena, strlen(query_str) + 1); if (qs_copy) { strcpy(qs_copy, query_str); Dowa_HashMap_Push_Arena(*pp_map, "QueryString", qs_copy, p_arena); } } // GET Params if (query_str && *query_str) { char *cur = query_str; while (cur && *cur) { char *next_amp = strchr(cur, '&'); char *pair_end = next_amp ? next_amp : cur + strlen(cur); char *eq = memchr(cur, '=', pair_end - cur); if (eq) { size_t key_len = eq - cur; size_t val_len = pair_end - (eq + 1); char key_buf[256]; // Adding query_ in front to separate GET params snprintf(key_buf, sizeof(key_buf), "query_%.*s", (int)key_len, cur); char *val_copy = Dowa_Arena_Allocate(p_arena, val_len + 1); if (!val_copy) return -1; memcpy(val_copy, eq + 1, val_len); val_copy[val_len] = '\0'; Dowa_HashMap_Push_Arena(*pp_map, key_buf, val_copy, p_arena); } cur = next_amp ? next_amp + 1 : NULL; } } // int qp = Dowa_HashMap_Get_Position(map, "QueryParams"); // Dowa_HashEntry *p_qp_entry = map->entries[qp]; // printf("query param key: %s\n", p_qp_entry->key); // printf("query param value: %s\n",(char *)Dowa_HashMap_Get(p_qp_entry->buffer, "hello")); // Parse headers char *line = buf + strlen(method) + 1 + strlen(path) + 1 + strlen(version) + 2; while (line < hdr_end) { char *next = strstr(line, "\r\n"); if (!next) break; char *colon = memchr(line, ':', next - line); if (colon) { size_t key_len = colon - line; size_t value_len = next - colon - 1; char *val_start = colon + 1; if (*val_start == ' ') { val_start++; value_len--; } char *key = Dowa_Arena_Allocate(p_arena, key_len + 1); if (!key) return -1; memcpy(key, line, key_len); key[key_len] = '\0'; char *val = Dowa_Arena_Allocate(p_arena, value_len + 1); if (!val) return -1; memcpy(val, val_start, value_len); val[value_len] = '\0'; Dowa_HashMap_Push_Arena(*pp_map, key, val, p_arena); } line = next + 2; } Seobeo_Handle_Consume(p_handle, (uint32)hdr_len); // Reading Body void *p_cl_kv = Dowa_HashMap_Get_Ptr(*pp_map, "Content-Length"); if (p_cl_kv) { const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; size_t body_len = atoi(content_length_str); Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len); char *body = Dowa_Arena_Allocate(p_arena, body_len + 1); if (!body) { Seobeo_Log(SEOBEO_ERROR, "Failed to allocate %zu bytes for body\n", body_len); return -1; } size_t total_read = 0; while (total_read < body_len) { size_t available = p_handle->read_buffer_len; size_t to_copy = (body_len - total_read) < available ? (body_len - total_read) : available; if (to_copy > 0) { memcpy(body + total_read, p_handle->read_buffer, to_copy); total_read += to_copy; Seobeo_Handle_Consume(p_handle, (uint32)to_copy); Seobeo_Log(SEOBEO_DEBUG, "Copied %zu bytes, total %zu/%zu\n", to_copy, total_read, body_len); } if (total_read < body_len) { int r = Seobeo_Handle_Read(p_handle); if (r < 0) { Seobeo_Log(SEOBEO_ERROR, "Read failed with %d\n", r); return -1; } if (r == 0) { // No data available yet, continue waiting // printf("DEBUG: Waiting for more data... (have %zu/%zu bytes)\n", total_read, body_len); continue; } } } body[body_len] = '\0'; Seobeo_Log(SEOBEO_DEBUG, "Body fully received (%zu bytes)\n", body_len); Dowa_HashMap_Push_Arena(*pp_map, "Body", body, p_arena); } return 0; } void SigchildHandler(int s) { (void)s; // waitpid() might overwrite errno, so we save and restore it: int saved_errno = errno; while(waitpid(-1, NULL, WNOHANG) > 0); errno = saved_errno; } int Seobeo_Web_Server_Start( const char *folder_path, const char *port, Seobeo_ServerMode mode, int thread_count) { if (folder_path) strncpy(g_folder_path, folder_path, sizeof(g_folder_path) - 1); Seobeo_Cache_Entry *p_html_cache = NULL; Seobeo_Handle *p_server_handle = Seobeo_Stream_Handle_Server_Create(NULL, port); if (p_server_handle->socket < 0) return 1; Seobeo_Log(SEOBEO_INFO, "Listening on port %s\n", port); if (mode == SEOBEO_MODE_FORK) { Seobeo_Log(SEOBEO_INFO, "Server mode: FORK\n"); struct sigaction sa; sa.sa_handler = SigchildHandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGCHLD, &sa, NULL); while (1) { Seobeo_Handle *p_cli_handle = Seobeo_Stream_Handle_Server_Accept(p_server_handle); if (!p_cli_handle) continue; if (fork() == 0) { Seobeo_Web_ClientHandle_Request(p_cli_handle, p_html_cache, FALSE); _exit(0); } } } if (mode == SEOBEO_MODE_EDGE) { Seobeo_Log(SEOBEO_INFO, "Server mode: EDGE\n"); Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache); } return -1; } /* Router logic */ struct Seobeo_Route_Struct { char *method; // "GET", "POST", "PUT", "DELETE" char *path_pattern; // "/v1/users/:id/posts/:post_id" Seobeo_Route_Handler handler; Seobeo_Stream_Handler stream_handler; // For streaming responses // Pre-parsed path segments for efficient matching char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"] boolean *is_param; // [false, false, true, false, true] size_t segment_count; }; static Seobeo_Route *g_routes = NULL; void Seobeo_Router_Init() { Dowa_Array_Reserve(g_routes, 20); } void Seobeo_Router_Register(const char *method, const char *path_pattern, Seobeo_Route_Handler handler) { Seobeo_Route route = {0}; route.method = strdup(method); route.path_pattern = strdup(path_pattern); route.handler = handler; route.stream_handler = NULL; route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL); route.segment_count = Dowa_Array_Length(route.path_segments); route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count); for (size_t i = 0; i < route.segment_count; i++) route.is_param[i] = (route.path_segments[i][0] == ':'); Dowa_Array_Push(g_routes, route); } void Seobeo_Router_Register_Stream(const char *method, const char *path_pattern, Seobeo_Stream_Handler handler) { Seobeo_Route route = {0}; route.method = strdup(method); route.path_pattern = strdup(path_pattern); route.handler = NULL; route.stream_handler = handler; route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL); route.segment_count = Dowa_Array_Length(route.path_segments); route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count); for (size_t i = 0; i < route.segment_count; i++) route.is_param[i] = (route.path_segments[i][0] == ':'); Dowa_Array_Push(g_routes, route); } static boolean match_route_and_extract( Seobeo_Route *route, const char *request_path, Seobeo_Request_Entry **pp_request_map, Dowa_Arena *p_arena) { Dowa_Arena *p_temp_arena = Dowa_Arena_Create(1024); char **request_segments = Dowa_String_Split(request_path, "/", strlen(request_path), 1, p_temp_arena); size_t request_segment_count = Dowa_Array_Length(request_segments); // Check segment count matches if (request_segment_count != route->segment_count) { Dowa_Arena_Free(p_temp_arena); return FALSE; } for (size_t i = 0; i < route->segment_count; i++) { // parameters if (route->is_param[i]) { char *param_name = route->path_segments[i]; // e.g., ":id" char *param_value = request_segments[i]; // e.g., "123" // Should Copy to arena char *key = Dowa_String_Copy_Arena(param_name, p_arena); char *value = Dowa_String_Copy_Arena(param_value, p_arena); Dowa_HashMap_Push_Arena(*pp_request_map, key, value, p_arena); } else { // Does not match. if (strcmp(route->path_segments[i], request_segments[i]) != 0) { Dowa_Arena_Free(p_temp_arena); return FALSE; } } } Dowa_Arena_Free(p_temp_arena); return TRUE; } Seobeo_Route_Handler Seobeo_Router_Find_Handler(const char *method, const char *path, Seobeo_Request_Entry **pp_request_map, Dowa_Arena *p_arena) { if (g_routes == NULL) { return NULL; } size_t route_count = Dowa_Array_Length(g_routes); for (size_t i = 0; i < route_count; i++) { Seobeo_Route *route = &g_routes[i]; if (strcmp(route->method, method) != 0) { continue; } if (match_route_and_extract(route, path, pp_request_map, p_arena)) { return route->handler; } } return NULL; } Seobeo_Stream_Handler Seobeo_Router_Find_Stream_Handler( const char *method, const char *path, Seobeo_Request_Entry **pp_request_map, Dowa_Arena *p_arena) { if (g_routes == NULL || method == NULL || path == NULL) return NULL; size_t route_count = Dowa_Array_Length(g_routes); for (size_t i = 0; i < route_count; i++) { Seobeo_Route *route = &g_routes[i]; if (strcmp(route->method, method) != 0) continue; if (route->stream_handler && match_route_and_extract(route, path, pp_request_map, p_arena)) return route->stream_handler; } return NULL; } void Seobeo_Router_Send_Response( Seobeo_Handle *p_handle, Seobeo_Request_Entry *p_response_map, Dowa_Arena *p_arena) { Seobeo_Router_Send_Response_KeepAlive(p_handle, p_response_map, p_arena, FALSE); } void Seobeo_Router_Send_Response_KeepAlive( Seobeo_Handle *p_handle, Seobeo_Request_Entry *p_response_map, Dowa_Arena *p_arena, boolean keep_alive) { if (p_response_map == NULL) { char *header = Dowa_Arena_Allocate(p_arena, 1024); Seobeo_Web_Header_Generate_KeepAlive(header, HTTP_INTERNAL_ERROR, "text/plain", 21, keep_alive); Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); Seobeo_Handle_Queue(p_handle, (uint8_t*)"Internal Server Error", 21); Seobeo_Handle_Flush(p_handle); return; } int status = HTTP_OK; void *p_status_kv = Dowa_HashMap_Get_Ptr(p_response_map, "status"); if (p_status_kv) { const char *status_str = ((Seobeo_Request_Entry*)p_status_kv)->value; status = atoi(status_str); } const char *body = ""; void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body"); if (p_body_kv) body = ((Seobeo_Request_Entry*)p_body_kv)->value; const char *content_type = "text/html"; void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type"); if (p_content_type_kv) content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value; // TODO: Update this to be integer size_t body_length; void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length"); if (p_content_length_kv) { const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value; body_length = atoi(content_length_str); } else body_length = strlen(body); char *header = Dowa_Arena_Allocate(p_arena, 4096); Seobeo_Web_Header_Generate_KeepAlive(header, status, content_type, body_length, keep_alive); for (int i = 0; i < Dowa_Array_Length(p_response_map); i++) { if ( strstr(p_response_map[i].key, "status") || strstr(p_response_map[i].key, "body") || strstr(p_response_map[i].key, "content-type") || strstr(p_response_map[i].key, "content-length") ) continue; int32 current_header_len = strlen(header); char *temp = malloc(sizeof(char) * 1024); sprintf(temp, "%s: %s\r\n\r\n", p_response_map[i].key, p_response_map[i].value); memcpy(&header[current_header_len - 2 /* \r\n */], temp, strlen(temp)); free(temp); } printf("hEADER %s\n", header); Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); Seobeo_Handle_Flush(p_handle); } void Seobeo_Router_Destroy() { if (g_routes == NULL) return; size_t route_count = Dowa_Array_Length(g_routes); for (size_t i = 0; i < route_count; i++) { Seobeo_Route *route = &g_routes[i]; if (route->method) free(route->method); if (route->path_pattern) free(route->path_pattern); if (route->path_segments) Dowa_Array_Free(route->path_segments); if (route->is_param) free(route->is_param); } Dowa_Array_Free(g_routes); g_routes = NULL; } // Written by AI. I don't know what it does. void Seobeo_Url_Decode(char *dst, const char *src) { char a, b; while (*src) { /* Check if we have a % followed by two valid hex characters */ if (*src == '%' && src[1] && src[2]) { a = src[1]; b = src[2]; /* Manual isxdigit check and conversion for 'a' */ int a_val = -1; if (a >= '0' && a <= '9') a_val = a - '0'; else if (a >= 'a' && a <= 'f') a_val = a - 'a' + 10; else if (a >= 'A' && a <= 'F') a_val = a - 'A' + 10; /* Manual isxdigit check and conversion for 'b' */ int b_val = -1; if (b >= '0' && b <= '9') b_val = b - '0'; else if (b >= 'a' && b <= 'f') b_val = b - 'a' + 10; else if (b >= 'A' && b <= 'F') b_val = b - 'A' + 10; /* If both were valid hex, combine them */ if (a_val != -1 && b_val != -1) { *dst++ = (char)((a_val << 4) | b_val); src += 3; continue; } } /* Handle '+' as space, otherwise copy character literally */ if (*src == '+') { *dst++ = ' '; } else { *dst++ = *src; } src++; } *dst = '\0'; }