Mercurial
view seobeo/s_web.c @ 58:ccb42d5bf8fd
[PostDog] Somewhat working copy. That would use for testing.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Sat, 20 Dec 2025 09:33:15 -0800 |
| parents | 84672efec192 |
| children | ea9ef388ab97 |
line wrap: on
line source
#include "seobeo/seobeo.h" int Seobeo_Web_GenerateRequestHeader(void *buffer, const char *host, const char *path) { return sprintf( buffer, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Connection: close\r\n" "accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n" "accept-language: en-GB,en;q=0.9,en-US;q=0.8,ko;q=0.7\r\n" "if-modified-since: Sat, 02 Aug 2025 22:51:58 GMT\r\n" "if-none-match: W/\"688e968e-5700\"\r\n" "priority: u=0, i\r\n" "sec-ch-ua: \"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140\"\r\n" "sec-ch-ua-mobile: 0\r\n" "sec-ch-ua-platform: \"macOS\"\r\n" "sec-fetch-dest: document\r\n" "sec-fetch-mode: navigate\r\n" "sec-fetch-site: none\r\n" "sec-fetch-user: 1\r\n" "upgrade-insecure-requests: 1\r\n" "\r\n", path, host ); } void Seobeo_Web_Header_Generate(void *buffer, int status, const char *content_type, const int content_length) { 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: close\r\n" "\r\n", status, status_text, content_type, content_length ); } void Seobeo_Web_HandleClientRequest(Seobeo_PHandle p_cli_handle, Dowa_PHashMap p_html_cache) { printf("p_cli_handle: %p", p_cli_handle); Dowa_PHashEntry entry = NULL; Dowa_PHashMap p_current = p_html_cache; char *slash; Dowa_PArena p_response_arena = Dowa_Arena_Create(1*1024*1024); if (!p_response_arena) { perror("Dowa_Arena_Initialize"); goto clean_up; } void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)2048); if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; } // Parse request headers into hashmap Dowa_PHashMap p_req_map = Dowa_HashMap_Create_With_Arena(100, p_response_arena); if (Seobeo_Web_Header_Parse(p_cli_handle, p_req_map) != 0) { 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; return; } // Dowa_HashMap_Print(p_req_map); // Extract method (GET, POST, etc.) const char *method = (const char*)Dowa_HashMap_Get(p_req_map, "HTTP_Method"); if (!method) { 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; } // --- Separate GET map for caching or routing --- Dowa_PHashMap p_get_map = Dowa_HashMap_Create(64); if (!p_get_map) { perror("Dowa_HashMap_Create (p_get_map)"); goto clean_up; } // --- Handle different HTTP methods --- if (strcmp(method, "GET") == 0) { const char *path = (const char*)Dowa_HashMap_Get(p_req_map, "Path"); char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)512); 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); } } // Store path for GET handling map Dowa_HashMap_Push_Value(p_get_map, "Path", file_path, strlen(file_path) + 1); // Walk through nested maps to find content while ((slash = strchr(file_path, '/'))) { *slash = '\0'; char *dir = file_path; file_path = slash + 1; printf("Directory: %s\n", dir); p_current = Dowa_HashMap_Get(p_current, dir); if (!p_current) { fprintf(stderr, "No value in hashmap key: %s\n\n", dir); Seobeo_Web_Header_Generate(p_response_header, HTTP_NOT_FOUND, "text/html", 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; } } size_t pos = Dowa_HashMap_Get_Position(p_current, file_path); entry = p_current->entries[pos]; if (!entry) { Seobeo_Web_Header_Generate(p_response_header, HTTP_NOT_FOUND, "text/html", 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; } 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, ".json")) mime = "application/json"; size_t body_size = entry->capacity; printf("key: %s\nBody Size: %zu\n", entry->key, body_size); Seobeo_Web_Header_Generate(p_response_header, HTTP_OK, mime, body_size); Seobeo_Handle_Queue(p_cli_handle, (const uint8*)p_response_header, (uint32)strlen(p_response_header)); Seobeo_Handle_Queue(p_cli_handle, (const uint8*)entry->buffer, (uint32)body_size); Seobeo_Handle_Flush(p_cli_handle); printf("DONE\n\n\n"); } else if (strcmp(method, "POST") == 0) { // --- TODO: Add POST logic here --- Seobeo_Web_Header_Generate(p_response_header, HTTP_NOT_FOUND, "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); } else if (strcmp(method, "PUT") == 0) { // --- TODO: Add PUT logic here --- Seobeo_Web_Header_Generate(p_response_header, HTTP_NOT_FOUND, "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); } else if (strcmp(method, "DELETE") == 0) { // --- TODO: Add DELETE logic here --- Seobeo_Web_Header_Generate(p_response_header, HTTP_NOT_FOUND, "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); } else { // Unknown or unsupported method Seobeo_Web_Header_Generate(p_response_header, HTTP_FORBIDDEN, "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; clean_up: printf("clean up\n\n"); return; // if (p_cli_handle) // Seobeo_Handle_Destroy(p_cli_handle); // if (p_response_arena) // Dowa_Arena_Destroy(p_response_arena); } int Seobeo_Web_Header_Parse(Seobeo_PHandle p_handle, Dowa_PHashMap map) { // 1) Fill read_buffer until we see "\r\n\r\n" 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) return 1; // EAGAIN, try again later TODO: Add this as part of Handle struct. } // 2) Parse request‐line "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; // This seems kinda bad ? char method[16], path[256], version[16]; if (sscanf(buf, "%15s %255s %15s", method, path, version) != 3) { return -1; } Dowa_HashMap_Push_Value_With_Type(map, "HTTP_Method", method, strlen(method) + 1, DOWA_HASH_MAP_TYPE_STRING); printf("Method: %s Pointer %p\n\n", Dowa_HashMap_Get(map, "HTTP_Method"), map); Dowa_HashMap_Push_Value_With_Type(map, "Version", version, strlen(version) + 1, DOWA_HASH_MAP_TYPE_STRING); // 1) Separate raw path and query string 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; } // push only the clean path Dowa_HashMap_Push_Value_With_Type( map, "Path", raw_path, strlen(raw_path) + 1, DOWA_HASH_MAP_TYPE_STRING); // 2) If there *is* a query, tokenize into a sub-map if (query_str && *query_str) { // create nested map for GET params Dowa_PHashMap p_query_map = Dowa_HashMap_Create_With_Arena(100, map->p_arena); char *cur = query_str; while (cur && *cur) { // find the next '&' char *next_amp = strchr(cur, '&'); // if none, treat end-of-string as the boundary char *pair_end = next_amp ? next_amp : cur + strlen(cur); // find '=' in [cur, pair_end) char *eq = memchr(cur, '=', pair_end - cur); if (eq) { size_t key_len = eq - cur; size_t val_len = pair_end - (eq + 1); // extract key char key_buf[key_len + 1]; memcpy(key_buf, cur, key_len); key_buf[key_len] = '\0'; // extract value char val_buf[val_len + 1]; memcpy(val_buf, eq + 1, val_len); val_buf[val_len] = '\0'; printf("key: '%s', value: '%s'\n", key_buf, val_buf); // push into map with strlen(val_buf)+1 to include '\0' Dowa_HashMap_Push_Value_With_Type( p_query_map, key_buf, val_buf, (uint32_t)(val_len + 1), DOWA_HASH_MAP_TYPE_STRING); } // advance past '&' if present, else end loop cur = next_amp ? next_amp + 1 : NULL; } if ( Dowa_HashMap_Push_Value_With_Type_NoCopy( map, "QueryParams", p_query_map, sizeof(p_query_map), DOWA_HASH_MAP_TYPE_HASHMAP) == -1 ) { printf("Something went wrong...\n\n"); } } // int qp = Dowa_HashMap_Get_Position(map, "QueryParams"); // Dowa_PHashEntry 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")); // 3) Parse each header line until the blank line 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; // split at colon 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 = malloc(key_len + 1); memcpy(key, line, key_len); key[key_len] = '\0'; char *val = malloc(value_len + 1); memcpy(val, val_start, value_len); val[value_len] = '\0'; Dowa_HashMap_Push_Value_With_Type(map, key, val, value_len + 1, DOWA_HASH_MAP_TYPE_STRING); // printf("Capacity: %d, Length: %d ", (int)map->p_arena->capacity, (int)map->p_arena->offset); // printf("value_len: %d, key: %s value %s position: %d\n\n", (int)value_len + 1, key, val, Dowa_HashMap_Get_Position(map, key)); // printf("Method: %s Position: %d Pointer %p\n\n", Dowa_HashMap_Get(map, "HTTP_Method"), Dowa_HashMap_Get_Position(map, "HTTP_Method"), map); Dowa_Free(key); Dowa_Free(val); } line = next + 2; } Seobeo_Handle_Consume(p_handle, (uint32)hdr_len); // 4) If Content-Length was provided, read that much body int content_length_pos = Dowa_HashMap_Get_Position(map, "Content-Length"); Dowa_PHashEntry p_content_length_entry = map->entries[content_length_pos]; if (p_content_length_entry) { size_t body_len = atoi((char*)p_content_length_entry->buffer); while (p_handle->read_buffer_len < body_len) { int r = Seobeo_Handle_Read(p_handle); if (r < 0) return -1; if (r == 0) return 1; // wait for more data } char *body = malloc(body_len + 1); memcpy(body, p_handle->read_buffer, body_len); body[body_len] = '\0'; Dowa_HashMap_Push_Value(map, "Body", body, body_len + 1); Dowa_Free(body); Seobeo_Handle_Consume(p_handle, (uint32)body_len); } return 0; // success; map now holds Method, Path, Version, headers, and optional Body } // TODO: Do epoll or kqueue depending on the OS. void SigchildHandler(int s) { (void)s; // quiet unused variable warning // 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) { Dowa_PHashMap p_html_cache = Dowa_HashMap_Create(200); if (Dowa_HashMap_Cache_Folder(p_html_cache, folder_path) != 0) { perror("Dowa_Cache_Folder"); return -1; } Seobeo_PHandle p_server_handle = Seobeo_Stream_Handle_Server_Create(NULL, port); if (p_server_handle->socket < 0) return 1; printf("Listening on port %s\n", port); // Fork‐based fallback if (mode == SEOBEO_MODE_FORK) { printf("FORK MODE\n"); struct sigaction sa; sa.sa_handler = SigchildHandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGCHLD, &sa, NULL); while (1) { Seobeo_PHandle p_cli_handle = Seobeo_Stream_Handle_Server_Accept(p_server_handle); if (!p_cli_handle) continue; if (fork() == 0) { Seobeo_Web_HandleClientRequest(p_cli_handle, p_html_cache); _exit(0); } } } if (mode == SEOBEO_MODE_EDGE) { printf("EDGE MODE\n"); Seobeo_Web_Edge_2(p_server_handle, p_html_cache); // Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache); } return -1; } int Seobeo_Web_Client_Get(const char *host, const char *port, const char *path) { Seobeo_PHandle h = Seobeo_Stream_Handle_Client_Create(host, port, TRUE); if (!h || h->socket < 0) { if (h) Seobeo_Handle_Destroy(h); return -1; } Dowa_PArena p_request_arena = Dowa_Arena_Create(1 * 1024 * 1024); if (!p_request_arena) { perror("Dowa_Arena_Create"); return -1; } void *p_request_header = Dowa_Arena_Allocate(p_request_arena, 4096); if (!p_request_header) { perror("Dowa_Arena_Allocate"); return -1; } int request_len = Seobeo_Web_GenerateRequestHeader(p_request_header, host, path); // DEBUG // printf("request: %s\n", (char *)p_request_header); Seobeo_Handle_Queue(h, (uint8 *)p_request_header, (uint32)request_len); if (Seobeo_Handle_Flush(h) < 0) { perror("Seobeo_Handle_Flush"); Seobeo_Handle_Destroy(h); return -1; } // Response size_t cap = 1024*8, used = 0; char *p_request_body = Dowa_Arena_Allocate(p_request_arena, cap); if (!p_request_body) { Seobeo_Handle_Destroy(h); return -1; } while (1) { int n = Seobeo_Handle_Read(h); printf("Size: %d\n", n); if (n > 0) { // TODO: Maybe directly use arena inside of the struct? idk if that is useful or not... memcpy(p_request_body + used, h->read_buffer, h->read_buffer_len); used += h->read_buffer_len; Seobeo_Handle_Consume(h, (uint32)h->read_buffer_len); }else if (n == 0) { // Wait continue; }else if (n == -2) { // Debug // peer closed; we’ve got everything printf("\n\nCLOSED\n\n"); break; }else { Dowa_Arena_Destroy(p_request_arena); Seobeo_Handle_Destroy(h); return -1; } } // Debug printf("Request body %s", p_request_body); Dowa_Arena_Destroy(p_request_arena); Seobeo_Handle_Destroy(h); return 0; } void Seobeo_Web_SSL_Init() { SSL_load_error_strings(); OpenSSL_add_ssl_algorithms(); } void Seobeo_Web_SSL_Cleanup(void) { EVP_cleanup(); // I don't think these are needed... }