Mercurial
diff seobeo/s_web.c @ 185:dfdd66825396
Merged in keep alive changes and mrjunejune changes.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Fri, 23 Jan 2026 22:22:30 -0800 |
| parents | 8c74204fd362 |
| children | 8cf4ec5e2191 |
line wrap: on
line diff
--- a/seobeo/s_web.c Fri Jan 23 21:09:49 2026 -0800 +++ b/seobeo/s_web.c Fri Jan 23 22:22:30 2026 -0800 @@ -1,4 +1,6 @@ #include "seobeo/seobeo.h" +#include <strings.h> +#include <time.h> static char g_folder_path[512] = "."; @@ -36,6 +38,14 @@ 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) { @@ -50,37 +60,76 @@ case HTTP_INTERNAL_ERROR: status_text = "Internal Server Error"; break; default: status_text = "Unknown"; break; } - + sprintf( - buffer, + buffer, "HTTP/1.1 %d %s\r\n" "Content-Type: %s\r\n" "Content-Length: %d\r\n" - "Connection: close\r\n" - "\r\n", - status, status_text, content_type, content_length + "Connection: %s\r\n" + "\r\n", + status, status_text, content_type, content_length, + keep_alive ? "keep-alive" : "close" ); } -void Seobeo_Web_HandleClientRequest(Seobeo_Handle *p_cli_handle, - Seobeo_Cache_Entry *p_html_cache) +// 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) { - // TODO: This should be splitted up instead of handling up to 50 MB as it will fail more often... - Dowa_Arena *p_request_arena = Dowa_Arena_Create(50*1024*1024); // 50 MB because of files... - if (!p_request_arena) { perror("Dowa_Arena_Create request"); goto clean_up; } + if (!header_value || !target) return FALSE; + + // Make a copy to tokenize + char *copy = strdup(header_value); + if (!copy) return FALSE; - Dowa_Arena *p_response_arena = Dowa_Arena_Create(50*1024*1024); // 50 MB for response - if (!p_response_arena) { perror("Dowa_Arena_Create response"); goto clean_up; } + 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; } + 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); - if (parse_result != 0 && parse_result != 1) - { - Seobeo_Log(SEOBEO_ERROR, "Seobeo_Web_Header_Parse failed with code %d\n", parse_result); + // 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); @@ -88,40 +137,41 @@ (const uint8*)p_response_header, (uint32)strlen(p_response_header)); Seobeo_Handle_Flush(p_cli_handle); - goto clean_up; + goto clean_up_arenas; } - Seobeo_Log(SEOBEO_DEBUG, "Parse completed with code %d\n", parse_result); + // 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; - // Recording IP to see who is ddosing or any web scrappers... - 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; - // Fallback - if (!real_ip) + if (conn_header) { - void *p_forwarded_kv = Dowa_HashMap_Get_Ptr(p_req_map, "X-Forwarded-For"); - real_ip = p_forwarded_kv ? ((Seobeo_Request_Entry*)p_forwarded_kv)->value : NULL; + 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 } - // Fallback - if (!real_ip) - real_ip = p_cli_handle->host; + 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_log = Dowa_HashMap_Get_Ptr(p_req_map, "Path"); - const char *path_log = p_path_kv_log ? ((Seobeo_Request_Entry*)p_path_kv_log)->value : "/"; - - Seobeo_Log(SEOBEO_INFO, "%s - %s %s\n", - real_ip ? real_ip : "unknown", - method ? method : "UNKNOWN", - path_log); - - Seobeo_Log(SEOBEO_DEBUG, "Parsed request, method=%s\n", method ? method : "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_ERROR, "No HTTP method found in request\n"); + Seobeo_Log(SEOBEO_DEBUG, "No HTTP method found in request\n"); Seobeo_Web_Header_Generate(p_response_header, HTTP_BAD_REQUEST, "text/plain", 0); @@ -129,23 +179,18 @@ (const uint8*)p_response_header, (uint32)strlen(p_response_header)); Seobeo_Handle_Flush(p_cli_handle); - goto clean_up; + should_keep_alive = FALSE; + goto clean_up_arenas; } - 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 : "/"; - // --- Check for WebSocket upgrade request --- #ifdef SEOBEO_WEBSOCKET_SERVER - Seobeo_Log(SEOBEO_DEBUG, "Web soceket path \n"); if (Seobeo_WebSocket_Server_Handle_Upgrade(p_cli_handle, p_req_map, path)) { Seobeo_Log(SEOBEO_INFO, "WebSocket connection established\n"); - if (p_request_arena) - Dowa_Arena_Free(p_request_arena); - if (p_response_arena) - Dowa_Arena_Free(p_response_arena); - return; + 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 @@ -154,16 +199,14 @@ if (handler != NULL) { Seobeo_Request_Entry *p_response_map = handler(p_req_map, p_response_arena); - Seobeo_Router_Send_Response(p_cli_handle, p_response_map, p_response_arena); - goto clean_up; + 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 --- + // --- Static files fallback for GET (use original large arena logic) --- if (strcmp(method, "GET") == 0) { - void *p_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Path"); - const char *path = p_kv ? ((Seobeo_Request_Entry*)p_kv)->value : NULL; - char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)5 * 1024); // 5Kb only for path + char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)5 * 1024); if (!path || strcmp(path, "/") == 0) { @@ -183,14 +226,12 @@ strcpy(file_path, path); } - // Check if file is in cache, load if not 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) { - // File is cached - use stored size for binary file support Seobeo_Cached_File *cached = ((Seobeo_Cache_Entry*)p_file_kv)->value; file_content = cached->content; body_size = cached->size; @@ -209,41 +250,46 @@ if (!file_content) { - Seobeo_Web_Header_Generate(p_response_header, + Seobeo_Web_Header_Generate_KeepAlive(p_response_header, HTTP_NOT_FOUND, - "text/html", 0); + "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; + goto clean_up_arenas; } - Seobeo_Log(SEOBEO_DEBUG, "Serving Static Files\n"); - // Serve static file 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, ".webp")) mime = "image/webp"; 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_Log(SEOBEO_DEBUG, "File path: %s\nBody Size: %zu\n", file_path, body_size); - Seobeo_Web_Header_Generate(p_response_header, + Seobeo_Web_Header_Generate_KeepAlive(p_response_header, HTTP_OK, mime, - body_size); + body_size, should_keep_alive); Seobeo_Handle_Queue(p_cli_handle, (const uint8*)p_response_header, @@ -252,32 +298,26 @@ (const uint8*)file_content, (uint32)body_size); Seobeo_Handle_Flush(p_cli_handle); - Seobeo_Log(SEOBEO_DEBUG, "Request handled successfully\n"); } else { - Seobeo_Web_Header_Generate(p_response_header, - HTTP_FORBIDDEN, - "text/plain", 0); + 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); } - goto clean_up; -clean_up: - Seobeo_Log(SEOBEO_INFO, "Clean up all Arenas\n"); - if (p_cli_handle) - Seobeo_Handle_Destroy(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; + return should_keep_alive; } - int Seobeo_Web_Header_Parse(Seobeo_Handle *p_handle, Seobeo_Request_Entry **pp_map, Dowa_Arena *p_arena) { while (1) @@ -546,8 +586,7 @@ if (fork() == 0) { - Seobeo_Web_HandleClientRequest(p_cli_handle, - p_html_cache); + Seobeo_Web_ClientHandle_Request(p_cli_handle, p_html_cache, FALSE); _exit(0); } } @@ -675,10 +714,19 @@ 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(header, HTTP_INTERNAL_ERROR, "text/plain", 21); + 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); @@ -716,14 +764,14 @@ } char *header = Dowa_Arena_Allocate(p_arena, 4096); - Seobeo_Web_Header_Generate(header, status, content_type, body_length); + 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") + strstr(p_response_map[i].key, "content-length") ) continue; @@ -735,7 +783,7 @@ } Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); - Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); + Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); Seobeo_Handle_Flush(p_handle); } @@ -756,6 +804,3 @@ Dowa_Array_Free(g_routes); g_routes = NULL; } - -// Logging functions moved to s_logging.c -