comparison seobeo/s_web.c @ 7:114cad94008f

[Seobeo] Updated to support thread and edge server calls.
author June Park <parkjune1995@gmail.com>
date Mon, 29 Sep 2025 17:00:38 -0700
parents
children fb2cff495a60
comparison
equal deleted inserted replaced
6:1e61008b9980 7:114cad94008f
1 #include "seobeo/seobeo.h"
2
3 void Seobeo_Web_GenerateResponseHeader(void *buffer, int status,
4 const char *content_type, const int content_length)
5 {
6 const char *status_text;
7 switch(status)
8 {
9 case HTTP_OK: status_text = "OK"; break;
10 case HTTP_CREATED: status_text = "Created"; break;
11 case HTTP_MOVED_PERMANENTLY: status_text = "Moved Permanently"; break;
12 case HTTP_FOUND: status_text = "Found"; break;
13 case HTTP_BAD_REQUEST: status_text = "Bad Request"; break;
14 case HTTP_UNAUTHORIZED: status_text = "Unauthorized"; break;
15 case HTTP_FORBIDDEN: status_text = "Forbidden"; break;
16 case HTTP_NOT_FOUND: status_text = "Not Found"; break;
17 case HTTP_INTERNAL_ERROR: status_text = "Internal Server Error"; break;
18 default: status_text = "Unknown"; break;
19 }
20
21 sprintf(
22 buffer,
23 "HTTP/2.2 %d %s\r\n"
24 "Content-Type: %s\r\n"
25 "Content-Length: %d\r\n"
26 "Connection: close\r\n"
27 "\r\n",
28 status, status_text, content_type, content_length
29 );
30 }
31
32 void Seobeo_Web_HandleClientRequest(Seobeo_PHandle p_cli_handle,
33 Dowa_PHashMap p_html_cache)
34 {
35 Dowa_PArena p_response_arena = Dowa_Arena_Create(8192);
36 Dowa_PHashMap p_req_map = NULL;
37
38 Dowa_PHashEntry entry = NULL;
39 Dowa_PHashMap p_current = p_html_cache;
40 char *slash;
41
42 if (!p_response_arena) { perror("Dowa_Arena_Initialize"); goto clean_up; }
43
44 void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)2048);
45 if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; }
46
47 p_req_map = Dowa_HashMap_Create(32);
48 if (Seobeo_Web_ParseClientHeader(p_cli_handle, p_req_map) != 0)
49 {
50 // malformed request or closed — respond 400
51 Seobeo_Web_GenerateResponseHeader(p_response_header,
52 HTTP_BAD_REQUEST,
53 "text/plain", 0);
54 Seobeo_Handle_Queue(p_cli_handle,
55 (const uint8*)p_response_header,
56 (uint32)strlen(p_response_header));
57 Seobeo_Handle_Flush(p_cli_handle);
58 goto clean_up;
59 }
60
61 const char *path = (const char*)Dowa_HashMap_Get(p_req_map, "Path");
62
63 char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)512);
64
65 if (!path || strcmp(path, "/") == 0)
66 {
67 strcpy(file_path, "index.html");
68 }
69 else
70 {
71 size_t L = strlen(path);
72 // strip leading '/'
73 if (path[0] == '/')
74 {
75 if (strchr(path, '.') == NULL)
76 snprintf(file_path, 512, "%.*s/index.html", (int)(L-1), path+1);
77 else
78 snprintf(file_path, 512, "%.*s", (int)(L-1), path+1);
79 }
80 else
81 {
82 // Probably never get here?
83 strcpy(file_path, path);
84 }
85 }
86
87 // printf("\n\nfile_path: %s\n", file_path);
88
89 // Recursively go though the path until it gets to a file
90 while ((slash = strchr(file_path, '/')))
91 {
92 *slash = '\0'; // e.g. file_path="foo", slash+1="index.html"
93 char *dir = file_path; // "foo"
94 file_path = slash + 1; // "index.html"
95
96 p_current = Dowa_HashMap_Get(p_current, dir);
97 if (!p_current) { perror("No value"); goto clean_up; }
98 }
99
100 size_t pos = Dowa_HashMap_GetPosition(p_current, file_path);
101 entry = p_current->entries[pos];
102
103 // Missing so 404
104 if (!entry)
105 {
106 Seobeo_Web_GenerateResponseHeader(p_response_header,
107 HTTP_NOT_FOUND,
108 "text/html", 0);
109 Seobeo_Handle_Queue(p_cli_handle,
110 (const uint8*)p_response_header,
111 (uint32)strlen(p_response_header));
112 Seobeo_Handle_Flush(p_cli_handle);
113 goto clean_up;
114 }
115
116
117 const char *mime = "application/octet-stream"; // Default binary
118 if (strstr(file_path, ".html")) mime = "text/html; charset=utf-8";
119 else if (strstr(file_path, ".css")) mime = "text/css";
120 else if (strstr(file_path, ".js")) mime = "application/javascript";
121 else if (strstr(file_path, ".png")) mime = "image/png";
122 else if (strstr(file_path, ".jpg") || strstr(file_path, ".jpeg")) mime = "image/jpeg";
123 else if (strstr(file_path, ".gif")) mime = "image/gif";
124 else if (strstr(file_path, ".svg")) mime = "image/svg+xml";
125 else if (strstr(file_path, ".ico")) mime = "image/x-icon";
126 else if (strstr(file_path, ".json")) mime = "application/json";
127
128 size_t body_size = entry->capacity;
129 Seobeo_Web_GenerateResponseHeader(p_response_header,
130 HTTP_OK,
131 mime,
132 body_size);
133 Seobeo_Handle_Queue(p_cli_handle,
134 (const uint8*)p_response_header,
135 (uint32)strlen(p_response_header));
136 Seobeo_Handle_Queue(p_cli_handle,
137 (const uint8*)entry->buffer,
138 (uint32)body_size);
139 Seobeo_Handle_Flush(p_cli_handle);
140
141 clean_up:
142 Seobeo_Handle_Destroy(p_cli_handle);
143 Dowa_Arena_Free(p_response_arena);
144 Dowa_HashMap_Free(p_req_map); // TODO: Maybe initilized hashmap within the Arena?
145 }
146
147 int Seobeo_Web_ParseClientHeader(Seobeo_PHandle p_handle, Dowa_PHashMap map)
148 {
149 // 1) Fill read_buffer until we see "\r\n\r\n"
150 while (1)
151 {
152 int r = Seobeo_Handle_Read(p_handle);
153 if (r < 0) return -1; // fatal error
154 if (r == -2) return -2; // connection closed TODO: Add this as part of Handle struct.
155
156 if (p_handle->read_buffer_len >= 4 &&
157 strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL)
158 {
159 break;
160 }
161 if (r == 0) return 1; // EAGAIN, try again later TODO: Add this as part of Handle struct.
162 }
163
164 // 2) Parse request‐line "METHOD SP PATH SP VERSION CRLF"
165 char *buf = (char*)p_handle->read_buffer;
166 char *hdr_end = strstr(buf, "\r\n\r\n");
167 size_t hdr_len = hdr_end - buf + 4;
168
169 char method[16], path[256], version[16];
170 if (sscanf(buf, "%15s %255s %15s", method, path, version) != 3)
171 {
172 return -1;
173 }
174
175 Dowa_HashMap_PushValueWithType(map, "Method", method, strlen(method) + 1, DOWA_HASH_MAP_TYPE_STRING);
176 Dowa_HashMap_PushValueWithType(map, "Path", path, strlen(path) + 1, DOWA_HASH_MAP_TYPE_STRING);
177 Dowa_HashMap_PushValueWithType(map, "Version", version, strlen(version) + 1, DOWA_HASH_MAP_TYPE_STRING);
178
179 // 3) Parse each header line until the blank line
180 char *line = buf + strlen(method) + 1 + strlen(path) + 1 + strlen(version) + 2;
181 while (line < hdr_end)
182 {
183 char *next = strstr(line, "\r\n");
184 if (!next) break;
185
186 // split at colon
187 char *colon = memchr(line, ':', next - line);
188 if (colon) {
189 size_t key_len = colon - line;
190 size_t value_len = next - colon - 1;
191
192 char *val_start = colon + 1;
193 if (*val_start == ' ')
194 {
195 val_start++;
196 value_len--;
197 }
198
199 char *key = malloc(key_len + 1);
200 memcpy(key, line, key_len);
201 key[key_len] = '\0';
202
203 char *val = malloc(value_len + 1);
204 memcpy(val, val_start, value_len);
205 val[value_len] = '\0';
206
207 Dowa_HashMap_PushValue(map, key, val, value_len + 1);
208
209 free(key);
210 free(val);
211 }
212
213 line = next + 2;
214 }
215 Seobeo_Handle_Consume(p_handle, (uint32)hdr_len);
216
217 // 4) If Content-Length was provided, read that much body
218 int content_length_pos = Dowa_HashMap_GetPosition(map, "Content-Length");
219 Dowa_PHashEntry p_content_length_entry = map->entries[content_length_pos];
220 if (p_content_length_entry)
221 {
222 size_t body_len = atoi((char*)p_content_length_entry->buffer);
223 while (p_handle->read_buffer_len < body_len)
224 {
225 int r = Seobeo_Handle_Read(p_handle);
226 if (r < 0) return -1;
227 if (r == 0) return 1; // wait for more data
228 }
229
230 char *body = malloc(body_len + 1);
231 memcpy(body, p_handle->read_buffer, body_len);
232 body[body_len] = '\0';
233
234 Dowa_HashMap_PushValue(map, "Body", body, body_len + 1);
235 free(body);
236
237 Seobeo_Handle_Consume(p_handle, (uint32)body_len);
238 }
239
240 return 0; // success; map now holds Method, Path, Version, headers, and optional Body
241 }
242
243 // TODO: Do epoll or kqueue depending on the OS.
244 void SigchildHandler(int s)
245 {
246 (void)s; // quiet unused variable warning
247
248 // waitpid() might overwrite errno, so we save and restore it:
249 int saved_errno = errno;
250
251 while(waitpid(-1, NULL, WNOHANG) > 0);
252
253 errno = saved_errno;
254 }
255
256 int Seobeo_Web_StartBasicHTTPServer(
257 const char *folder_path,
258 const char *port,
259 Seobeo_ServerMode mode,
260 int thread_count)
261 {
262 Dowa_PHashMap p_html_cache = Dowa_HashMap_Create(1024);
263 if (Dowa_HashMap_Cache_Folder(p_html_cache,
264 folder_path) != 0)
265 {
266 perror("Dowa_Cache_Folder");
267 return -1;
268 }
269
270 Seobeo_PHandle p_server_handle =
271 Seobeo_Stream_Handle_Create(NULL, port);
272 if (p_server_handle->socket < 0) return 1;
273 printf("Listening on port %s\n", port);
274
275 // Fork‐based fallback
276 if (mode == SEOBEO_MODE_FORK)
277 {
278 struct sigaction sa;
279 sa.sa_handler = SigchildHandler;
280 sigemptyset(&sa.sa_mask);
281 sa.sa_flags = SA_RESTART;
282 sigaction(SIGCHLD, &sa, NULL);
283
284 while (1) {
285 Seobeo_PHandle cli =
286 Seobeo_Stream_Handle_Accept(p_server_handle);
287 if (!cli) continue;
288
289 if (fork() == 0) {
290 Seobeo_Web_HandleClientRequest(cli,
291 p_html_cache);
292 _exit(0);
293 }
294 Seobeo_Handle_Destroy(cli);
295 }
296 }
297
298 if (mode == SEOBEO_MODE_EDGE)
299 {
300 Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache);
301 }
302
303 return -1;
304 }