Mercurial
diff seobeo/s_web.c @ 183:a8976a008a9d
[BenchMark] Added bun bench mark to test seoboe vs other popular benchmarks.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Fri, 23 Jan 2026 21:19:08 -0800 |
| parents | bdcc610eeed8 |
| children | 8c74204fd362 |
line wrap: on
line diff
--- a/seobeo/s_web.c Thu Jan 22 21:23:17 2026 -0800 +++ b/seobeo/s_web.c Fri Jan 23 21:19:08 2026 -0800 @@ -1,4 +1,6 @@ #include "seobeo/seobeo.h" +#include <strings.h> // for strcasecmp +#include <time.h> // for time_t 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,44 @@ (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) - { - 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 (conn_header) { + // Explicit Connection header - check for keep-alive or close + 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 { + // Unknown value, use version default + should_keep_alive = is_http11 && use_keep_alive; + } + } else { + // No Connection header - use HTTP version defaults + should_keep_alive = is_http11 && use_keep_alive; } - // Fallback - if (!real_ip) - real_ip = p_cli_handle->host; 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 +182,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 +202,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 +229,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 +253,28 @@ 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, ".json")) mime = "application/json"; - else if (strstr(file_path, ".wasm")) mime = "application/wasm"; - else if (strstr(file_path, ".mp4")) mime = "video/mp4"; - else if (strstr(file_path, ".webm")) mime = "video/webm"; - 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 +283,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 +571,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 +699,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 +749,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 +768,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); }