Mercurial
view seobeo/s_web.c @ 12:de54585a40f1
Adding bun and node modules.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Thu, 02 Oct 2025 14:39:48 -0700 |
| parents | 114cad94008f |
| children | fb2cff495a60 |
line wrap: on
line source
#include "seobeo/seobeo.h" void Seobeo_Web_GenerateResponseHeader(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/2.2 %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) { Dowa_PArena p_response_arena = Dowa_Arena_Create(8192); Dowa_PHashMap p_req_map = NULL; Dowa_PHashEntry entry = NULL; Dowa_PHashMap p_current = p_html_cache; char *slash; 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; } p_req_map = Dowa_HashMap_Create(32); if (Seobeo_Web_ParseClientHeader(p_cli_handle, p_req_map) != 0) { // malformed request or closed — respond 400 Seobeo_Web_GenerateResponseHeader(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; } 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); // strip leading '/' 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 { // Probably never get here? strcpy(file_path, path); } } // printf("\n\nfile_path: %s\n", file_path); // Recursively go though the path until it gets to a file while ((slash = strchr(file_path, '/'))) { *slash = '\0'; // e.g. file_path="foo", slash+1="index.html" char *dir = file_path; // "foo" file_path = slash + 1; // "index.html" p_current = Dowa_HashMap_Get(p_current, dir); if (!p_current) { perror("No value"); goto clean_up; } } size_t pos = Dowa_HashMap_GetPosition(p_current, file_path); entry = p_current->entries[pos]; // Missing so 404 if (!entry) { Seobeo_Web_GenerateResponseHeader(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"; // Default binary 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; Seobeo_Web_GenerateResponseHeader(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); clean_up: Seobeo_Handle_Destroy(p_cli_handle); Dowa_Arena_Free(p_response_arena); Dowa_HashMap_Free(p_req_map); // TODO: Maybe initilized hashmap within the Arena? } int Seobeo_Web_ParseClientHeader(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; char method[16], path[256], version[16]; if (sscanf(buf, "%15s %255s %15s", method, path, version) != 3) { return -1; } Dowa_HashMap_PushValueWithType(map, "Method", method, strlen(method) + 1, DOWA_HASH_MAP_TYPE_STRING); Dowa_HashMap_PushValueWithType(map, "Path", path, strlen(path) + 1, DOWA_HASH_MAP_TYPE_STRING); Dowa_HashMap_PushValueWithType(map, "Version", version, strlen(version) + 1, DOWA_HASH_MAP_TYPE_STRING); // 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_PushValue(map, key, val, value_len + 1); free(key); 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_GetPosition(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_PushValue(map, "Body", body, body_len + 1); 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_StartBasicHTTPServer( const char *folder_path, const char *port, Seobeo_ServerMode mode, int thread_count) { Dowa_PHashMap p_html_cache = Dowa_HashMap_Create(1024); 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_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) { struct sigaction sa; sa.sa_handler = SigchildHandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGCHLD, &sa, NULL); while (1) { Seobeo_PHandle cli = Seobeo_Stream_Handle_Accept(p_server_handle); if (!cli) continue; if (fork() == 0) { Seobeo_Web_HandleClientRequest(cli, p_html_cache); _exit(0); } Seobeo_Handle_Destroy(cli); } } if (mode == SEOBEO_MODE_EDGE) { Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache); } return -1; }