@velox0/cerver 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,12 +3,19 @@
3
3
  const { execFileSync } = require("child_process");
4
4
  const path = require("path");
5
5
  const fs = require("fs");
6
+ const { findProjectRoot } = require("../config");
6
7
 
7
8
  /**
8
9
  * Run the compiled binary from dist/.
9
10
  */
10
11
  function run(opts) {
11
- const projectDir = process.cwd();
12
+ const projectDir = findProjectRoot();
13
+ if (!projectDir) {
14
+ console.log("Not a cerver project");
15
+ process.exit(1);
16
+ }
17
+ process.chdir(projectDir);
18
+
12
19
  const binaryPath = path.join(projectDir, "dist", "server");
13
20
 
14
21
  if (!fs.existsSync(binaryPath)) {
@@ -45,12 +45,17 @@ function supportsFlag(cc, flag) {
45
45
  */
46
46
  function compile(distDir, runtimeDir, opts) {
47
47
  const cc = detectCompiler();
48
- const serverC = path.join(distDir, "server.c");
49
48
  const outputBin = path.join(distDir, "server");
50
49
 
51
- if (!fs.existsSync(serverC)) {
50
+ /* Collect all generated source files from distDir */
51
+ const generatedSources = fs
52
+ .readdirSync(distDir)
53
+ .filter((f) => f.endsWith(".c"))
54
+ .map((f) => path.join(distDir, f));
55
+
56
+ if (generatedSources.length === 0) {
52
57
  throw new Error(
53
- `cerver: generated source not found at ${serverC}`
58
+ `cerver: no generated C source files found in ${distDir}`
54
59
  );
55
60
  }
56
61
 
@@ -68,7 +73,7 @@ function compile(distDir, runtimeDir, opts) {
68
73
  "-Wno-unused-parameter",
69
74
  "-o",
70
75
  outputBin,
71
- serverC,
76
+ ...generatedSources,
72
77
  ...runtimeSources,
73
78
  `-I${runtimeDir}`,
74
79
  "-lpthread",
package/lib/config.js CHANGED
@@ -53,4 +53,23 @@ function loadConfig(projectDir) {
53
53
  return config;
54
54
  }
55
55
 
56
- module.exports = { loadConfig, DEFAULTS };
56
+ function findProjectRoot(startDir = process.cwd()) {
57
+ let currentDir = startDir;
58
+
59
+ while (true) {
60
+ if (fs.existsSync(path.join(currentDir, "cerver.config.js"))) {
61
+ return currentDir;
62
+ }
63
+
64
+ const parentDir = path.dirname(currentDir);
65
+ if (parentDir === currentDir) {
66
+ break;
67
+ }
68
+ currentDir = parentDir;
69
+ }
70
+
71
+ return null;
72
+ }
73
+
74
+ module.exports = { loadConfig, findProjectRoot, DEFAULTS };
75
+
@@ -4,15 +4,15 @@ const fs = require("fs");
4
4
  const path = require("path");
5
5
 
6
6
  /**
7
- * Discover route files under app/routes/ and map them to URL paths.
7
+ * Discover route files under routes/ and map them to URL paths.
8
8
  *
9
9
  * File-based routing convention:
10
- * app/routes/index.js → /
11
- * app/routes/page.js → /page
12
- * app/routes/group/item.js → /group/item
13
- * app/routes/item/[id].js → /item/:id
10
+ * routes/index.js → /
11
+ * routes/page.js → /page
12
+ * routes/group/item.js → /group/item
13
+ * routes/item/[id].js → /item/:id
14
14
  *
15
- * @param {string} routesDir - Absolute path to app/routes/
15
+ * @param {string} routesDir - Absolute path to routes/
16
16
  * @returns {Array<{ filePath: string, urlPath: string }>}
17
17
  */
18
18
  function discoverRoutes(routesDir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velox0/cerver",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Compile restricted JavaScript server logic into optimized native C binaries",
5
5
  "main": "bin/cerver.js",
6
6
  "bin": {
@@ -30,4 +30,4 @@
30
30
  "chokidar": "^3.6.0",
31
31
  "commander": "^13.1.0"
32
32
  }
33
- }
33
+ }
package/runtime/cerver.h CHANGED
@@ -119,6 +119,9 @@ typedef struct {
119
119
 
120
120
  /* Keep-alive control: set to 1 to force close after response */
121
121
  int _force_close;
122
+
123
+ /* Internal file descriptor for sendfile serving */
124
+ int _file_fd;
122
125
  } cerver_response_t;
123
126
 
124
127
  /* Response helpers — called by generated handler code */
@@ -233,6 +236,9 @@ struct cerver_server {
233
236
  /* Worker pool */
234
237
  int worker_count;
235
238
  cerver_worker_t* workers;
239
+
240
+ /* Route trie for radix/trie-based routing */
241
+ void* route_trie;
236
242
  };
237
243
 
238
244
  /* Server lifecycle */
@@ -262,6 +268,10 @@ int cerver_write_response(int fd, const cerver_response_t* res, int keepalive);
262
268
 
263
269
  int cerver_route_match(const cerver_route_t* route, cerver_request_t* req);
264
270
  cerver_handler_fn cerver_dispatch(cerver_server_t* srv, cerver_request_t* req);
271
+ void* cerver_trie_create(void);
272
+ void cerver_trie_insert(void* trie, const char* pattern, const char* method,
273
+ cerver_handler_fn handler);
274
+ void cerver_trie_free(void* trie);
265
275
 
266
276
  /* ------------------------------------------------------------------ */
267
277
  /* MIME (internal) */
@@ -12,6 +12,64 @@
12
12
  #include <string.h>
13
13
  #include <unistd.h>
14
14
  #include <sys/uio.h>
15
+ #include <errno.h>
16
+
17
+ #ifdef __linux__
18
+ #include <sys/sendfile.h>
19
+ static ssize_t cerver_sendfile(int out_fd, int in_fd, off_t offset, size_t count) {
20
+ off_t off = offset;
21
+ return sendfile(out_fd, in_fd, &off, count);
22
+ }
23
+ #elif defined(__APPLE__)
24
+ #include <sys/types.h>
25
+ #include <sys/socket.h>
26
+ static ssize_t cerver_sendfile(int out_fd, int in_fd, off_t offset, size_t count) {
27
+ off_t len = (off_t)count;
28
+ int res = sendfile(in_fd, out_fd, offset, &len, NULL, 0);
29
+ if (res == 0) {
30
+ return (ssize_t)len;
31
+ }
32
+ if (len > 0) {
33
+ return (ssize_t)len;
34
+ }
35
+ /* Fallback to read-write copy if not a socket or unsupported on this descriptor type */
36
+ char buf[8192];
37
+ if (lseek(in_fd, offset, SEEK_SET) == -1) return -1;
38
+ size_t to_read = count > sizeof(buf) ? sizeof(buf) : count;
39
+ ssize_t n_read = read(in_fd, buf, to_read);
40
+ if (n_read <= 0) return n_read;
41
+
42
+ size_t written = 0;
43
+ while (written < (size_t)n_read) {
44
+ ssize_t n_write = write(out_fd, buf + written, (size_t)n_read - written);
45
+ if (n_write < 0) {
46
+ if (errno == EINTR) continue;
47
+ return -1;
48
+ }
49
+ written += (size_t)n_write;
50
+ }
51
+ return (ssize_t)written;
52
+ }
53
+ #else
54
+ static ssize_t cerver_sendfile(int out_fd, int in_fd, off_t offset, size_t count) {
55
+ char buf[8192];
56
+ if (lseek(in_fd, offset, SEEK_SET) == -1) return -1;
57
+ size_t to_read = count > sizeof(buf) ? sizeof(buf) : count;
58
+ ssize_t n_read = read(in_fd, buf, to_read);
59
+ if (n_read <= 0) return n_read;
60
+
61
+ size_t written = 0;
62
+ while (written < (size_t)n_read) {
63
+ ssize_t n_write = write(out_fd, buf + written, (size_t)n_read - written);
64
+ if (n_write < 0) {
65
+ if (errno == EINTR) continue;
66
+ return -1;
67
+ }
68
+ written += (size_t)n_write;
69
+ }
70
+ return (ssize_t)written;
71
+ }
72
+ #endif
15
73
 
16
74
  /* ------------------------------------------------------------------ */
17
75
  /* Status text lookup */
@@ -93,40 +151,89 @@ int cerver_write_response(int fd, const cerver_response_t* res, int keepalive) {
93
151
  hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "\r\n");
94
152
 
95
153
  /*
96
- * Use writev() to send header + body in a single syscall.
97
- * This avoids Nagle interaction and reduces context switches.
154
+ * Use writev() or sendfile() to send response, or copy to contiguous
155
+ * buffer if body is small to avoid writev round-trips.
98
156
  */
99
- if (res->body && res->body_len > 0) {
100
- struct iovec iov[2];
101
- iov[0].iov_base = header;
102
- iov[0].iov_len = (size_t)hlen;
103
- iov[1].iov_base = (void*)res->body;
104
- iov[1].iov_len = res->body_len;
105
-
106
- size_t total = iov[0].iov_len + iov[1].iov_len;
107
- size_t written = 0;
157
+ if (res->_body_owned == 3) {
158
+ /* Send header first */
159
+ size_t header_total = (size_t)hlen;
160
+ size_t header_written = 0;
161
+ while (header_written < header_total) {
162
+ ssize_t n = write(fd, header + header_written, header_total - header_written);
163
+ if (n < 0) {
164
+ if (errno == EINTR) continue;
165
+ return -1;
166
+ }
167
+ header_written += (size_t)n;
168
+ }
108
169
 
109
- while (written < total) {
110
- ssize_t n = writev(fd, iov, 2);
111
- if (n < 0) return -1;
112
- written += (size_t)n;
170
+ /* Zero-copy body sending via sendfile(2) */
171
+ size_t body_total = res->body_len;
172
+ size_t body_sent = 0;
173
+ while (body_sent < body_total) {
174
+ ssize_t n = cerver_sendfile(fd, res->_file_fd, (off_t)body_sent, body_total - body_sent);
175
+ if (n < 0) {
176
+ if (errno == EINTR) continue;
177
+ return -1;
178
+ }
179
+ if (n == 0) break; /* EOF */
180
+ body_sent += (size_t)n;
181
+ }
182
+ } else if (res->body && res->body_len > 0) {
183
+ if ((size_t)hlen + res->body_len <= sizeof(header)) {
184
+ /* Small response optimization: copy body into header buffer and send in one syscall */
185
+ memcpy(header + hlen, res->body, res->body_len);
186
+ size_t total = (size_t)hlen + res->body_len;
187
+ size_t written = 0;
188
+ while (written < total) {
189
+ ssize_t n = write(fd, header + written, total - written);
190
+ if (n < 0) {
191
+ if (errno == EINTR) continue;
192
+ return -1;
193
+ }
194
+ written += (size_t)n;
195
+ }
196
+ } else {
197
+ /* Large response: use writev to send header + body */
198
+ struct iovec iov[2];
199
+ iov[0].iov_base = header;
200
+ iov[0].iov_len = (size_t)hlen;
201
+ iov[1].iov_base = (void*)res->body;
202
+ iov[1].iov_len = res->body_len;
203
+
204
+ size_t total = iov[0].iov_len + iov[1].iov_len;
205
+ size_t written = 0;
113
206
 
114
- /* Adjust iov for partial writes */
115
- if (written < iov[0].iov_len) {
116
- iov[0].iov_base = header + written;
117
- iov[0].iov_len -= (size_t)n;
118
- } else {
119
- /* Header fully sent, adjust body iov */
120
- size_t body_sent = written - (size_t)hlen;
121
- iov[0].iov_len = 0;
122
- iov[1].iov_base = (void*)(res->body + body_sent);
123
- iov[1].iov_len = res->body_len - body_sent;
207
+ while (written < total) {
208
+ ssize_t n = writev(fd, iov, 2);
209
+ if (n < 0) return -1;
210
+ written += (size_t)n;
211
+
212
+ /* Adjust iov for partial writes */
213
+ size_t to_consume = (size_t)n;
214
+ if (to_consume < iov[0].iov_len) {
215
+ iov[0].iov_base = (char*)iov[0].iov_base + to_consume;
216
+ iov[0].iov_len -= to_consume;
217
+ } else {
218
+ to_consume -= iov[0].iov_len;
219
+ iov[0].iov_len = 0;
220
+ iov[1].iov_base = (char*)iov[1].iov_base + to_consume;
221
+ iov[1].iov_len -= to_consume;
222
+ }
124
223
  }
125
224
  }
126
225
  } else {
127
226
  /* No body — just send header */
128
- ssize_t written = write(fd, header, (size_t)hlen);
129
- if (written < 0) return -1;
227
+ size_t total = (size_t)hlen;
228
+ size_t written = 0;
229
+ while (written < total) {
230
+ ssize_t n = write(fd, header + written, total - written);
231
+ if (n < 0) {
232
+ if (errno == EINTR) continue;
233
+ return -1;
234
+ }
235
+ written += (size_t)n;
236
+ }
130
237
  }
131
238
 
132
239
  return 0;
package/runtime/router.c CHANGED
@@ -19,8 +19,15 @@
19
19
 
20
20
  const char* cerver_req_param(const cerver_request_t* req, const char* key) {
21
21
  for (int i = 0; i < req->params_count; i++) {
22
- if (strcmp(req->params[i].key, key) == 0) {
23
- return req->params[i].value;
22
+ const char* pkey = req->params[i].key;
23
+ if (pkey && pkey[0] == key[0]) {
24
+ int j = 0;
25
+ while (key[j] && pkey[j] == key[j]) {
26
+ j++;
27
+ }
28
+ if (key[j] == '\0' && (pkey[j] == '\0' || pkey[j] == '/')) {
29
+ return req->params[i].value;
30
+ }
24
31
  }
25
32
  }
26
33
  return "";
@@ -101,7 +108,9 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
101
108
  const char* pp = pattern; /* pattern pointer */
102
109
  const char* rp = path; /* request path pointer */
103
110
 
104
- int saved_params = req->params_count;
111
+ int saved_params = req->params_count;
112
+ char* param_slashes[CERVER_MAX_PARAMS];
113
+ int param_slash_count = 0;
105
114
 
106
115
  /* Skip leading '/' */
107
116
  if (*pp == '/') pp++;
@@ -122,10 +131,12 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
122
131
  /* Dynamic segment — extract parameter */
123
132
  if (req->params_count < CERVER_MAX_PARAMS) {
124
133
  req->params[req->params_count].key = pp_seg + 1;
125
- /* Temporarily NUL-terminate the key at the slash */
126
- /* The key points into the route pattern (static/const) */
134
+ /* The key points into the route pattern (static/const, not NUL-terminated) */
127
135
  req->params[req->params_count].value = rp_seg;
128
136
  req->params_count++;
137
+ if (*rp == '/') {
138
+ param_slashes[param_slash_count++] = (char*)rp;
139
+ }
129
140
  }
130
141
  } else {
131
142
  /* Static segment — must match exactly */
@@ -146,6 +157,11 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
146
157
  return 0;
147
158
  }
148
159
 
160
+ /* Match succeeded, NUL-terminate extracted values in-place inside req->path */
161
+ for (int i = 0; i < param_slash_count; i++) {
162
+ *param_slashes[i] = '\0';
163
+ }
164
+
149
165
  return 1;
150
166
  }
151
167
 
@@ -153,6 +169,168 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
153
169
  /* Dispatch: find and return the handler for a request */
154
170
  /* ------------------------------------------------------------------ */
155
171
 
172
+ /* ------------------------------------------------------------------ */
173
+ /* Trie/Radix Route Router */
174
+ /* ------------------------------------------------------------------ */
175
+
176
+ static char* trie_strndup(const char* s, size_t n) {
177
+ char* p = malloc(n + 1);
178
+ if (p) {
179
+ memcpy(p, s, n);
180
+ p[n] = '\0';
181
+ }
182
+ return p;
183
+ }
184
+
185
+ typedef struct trie_node trie_node_t;
186
+
187
+ struct trie_node {
188
+ char* segment;
189
+ int is_param;
190
+ char* param_name;
191
+
192
+ struct {
193
+ const char* method;
194
+ cerver_handler_fn handler;
195
+ } handlers[16];
196
+ int handler_count;
197
+
198
+ trie_node_t** children;
199
+ int children_count;
200
+ int children_cap;
201
+ };
202
+
203
+ void* cerver_trie_create(void) {
204
+ trie_node_t* node = calloc(1, sizeof(trie_node_t));
205
+ return node;
206
+ }
207
+
208
+ static trie_node_t* trie_create_node(const char* segment, size_t len) {
209
+ trie_node_t* node = calloc(1, sizeof(trie_node_t));
210
+ if (node && segment) {
211
+ node->segment = trie_strndup(segment, len);
212
+ if (node->segment[0] == ':') {
213
+ node->is_param = 1;
214
+ node->param_name = trie_strndup(node->segment + 1, len - 1);
215
+ }
216
+ }
217
+ return node;
218
+ }
219
+
220
+ void cerver_trie_insert(void* trie, const char* pattern, const char* method,
221
+ cerver_handler_fn handler) {
222
+ if (!trie) return;
223
+ trie_node_t* curr = (trie_node_t*)trie;
224
+ const char* p = pattern;
225
+ while (*p == '/') p++;
226
+
227
+ while (*p) {
228
+ const char* seg_start = p;
229
+ while (*p && *p != '/') p++;
230
+ size_t len = (size_t)(p - seg_start);
231
+ if (len == 0) {
232
+ while (*p == '/') p++;
233
+ continue;
234
+ }
235
+
236
+ // Find if child exists
237
+ trie_node_t* child = NULL;
238
+ for (int i = 0; i < curr->children_count; i++) {
239
+ trie_node_t* c = curr->children[i];
240
+ if (strlen(c->segment) == len && memcmp(c->segment, seg_start, len) == 0) {
241
+ child = c;
242
+ break;
243
+ }
244
+ }
245
+
246
+ if (!child) {
247
+ child = trie_create_node(seg_start, len);
248
+ if (curr->children_count >= curr->children_cap) {
249
+ curr->children_cap = curr->children_cap == 0 ? 4 : curr->children_cap * 2;
250
+ curr->children = realloc(curr->children, curr->children_cap * sizeof(trie_node_t*));
251
+ }
252
+ curr->children[curr->children_count++] = child;
253
+ }
254
+
255
+ curr = child;
256
+ while (*p == '/') p++;
257
+ }
258
+
259
+ // Add handler to leaf
260
+ if (curr->handler_count < 16) {
261
+ curr->handlers[curr->handler_count].method = method;
262
+ curr->handlers[curr->handler_count].handler = handler;
263
+ curr->handler_count++;
264
+ }
265
+ }
266
+
267
+ void cerver_trie_free(void* trie) {
268
+ if (!trie) return;
269
+ trie_node_t* node = (trie_node_t*)trie;
270
+ for (int i = 0; i < node->children_count; i++) {
271
+ cerver_trie_free(node->children[i]);
272
+ }
273
+ free(node->children);
274
+ free(node->segment);
275
+ free(node->param_name);
276
+ free(node);
277
+ }
278
+
279
+ static int trie_match_recursive(trie_node_t* node, const char* path, cerver_request_t* req,
280
+ cerver_handler_fn* out_handler, int param_start_idx) {
281
+ // Skip leading slashes
282
+ while (*path == '/') path++;
283
+
284
+ if (*path == '\0') {
285
+ // Check if node has a handler for req->method
286
+ for (int i = 0; i < node->handler_count; i++) {
287
+ if (strcmp(node->handlers[i].method, req->method) == 0) {
288
+ *out_handler = node->handlers[i].handler;
289
+ req->params_count = param_start_idx;
290
+ return 1;
291
+ }
292
+ }
293
+ return 0;
294
+ }
295
+
296
+ // Extract next segment from path
297
+ const char* seg_start = path;
298
+ while (*path && *path != '/') path++;
299
+ size_t seg_len = (size_t)(path - seg_start);
300
+
301
+ // Try static children first
302
+ for (int i = 0; i < node->children_count; i++) {
303
+ trie_node_t* child = node->children[i];
304
+ if (!child->is_param) {
305
+ if (strlen(child->segment) == seg_len && memcmp(child->segment, seg_start, seg_len) == 0) {
306
+ if (trie_match_recursive(child, path, req, out_handler, param_start_idx)) {
307
+ return 1;
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ // Try parameter/dynamic children next
314
+ for (int i = 0; i < node->children_count; i++) {
315
+ trie_node_t* child = node->children[i];
316
+ if (child->is_param) {
317
+ if (param_start_idx < CERVER_MAX_PARAMS) {
318
+ req->params[param_start_idx].key = child->param_name;
319
+ req->params[param_start_idx].value = seg_start;
320
+ }
321
+ if (trie_match_recursive(child, path, req, out_handler, param_start_idx + 1)) {
322
+ return 1;
323
+ }
324
+ }
325
+ }
326
+
327
+ return 0;
328
+ }
329
+
330
+ /* ------------------------------------------------------------------ */
331
+ /* Dispatch: find and return the handler for a request */
332
+ /* ------------------------------------------------------------------ */
333
+
156
334
  cerver_handler_fn cerver_dispatch(cerver_server_t* srv, cerver_request_t* req) {
157
335
  /* Try the generated compile-time dispatch first */
158
336
  if (srv->dispatch_override) {
@@ -160,13 +338,19 @@ cerver_handler_fn cerver_dispatch(cerver_server_t* srv, cerver_request_t* req) {
160
338
  if (h) return h;
161
339
  }
162
340
 
163
- /* Fall back to generic route table scan */
164
- if (!srv->routes) return NULL;
341
+ /* Fall back to generic route table scan via Trie */
342
+ if (!srv->route_trie) return NULL;
165
343
 
166
- for (int i = 0; i < srv->route_count; i++) {
167
- if (cerver_route_match(&srv->routes[i], req)) {
168
- return srv->routes[i].handler;
344
+ cerver_handler_fn handler = NULL;
345
+ req->params_count = 0;
346
+ if (trie_match_recursive((trie_node_t*)srv->route_trie, req->path, req, &handler, 0)) {
347
+ // NUL-terminate extracted values in-place inside req->path
348
+ for (int i = 0; i < req->params_count; i++) {
349
+ char* val = (char*)req->params[i].value;
350
+ while (*val && *val != '/') val++;
351
+ if (*val == '/') *val = '\0';
169
352
  }
353
+ return handler;
170
354
  }
171
355
 
172
356
  return NULL;
package/runtime/server.c CHANGED
@@ -278,6 +278,8 @@ static void handle_connection(cerver_server_t* srv, int client_fd) {
278
278
  free((void*)res.body);
279
279
  else if (res._body_owned == 2 && res.body)
280
280
  munmap((void*)res.body, res.body_len);
281
+ else if (res._body_owned == 3 && res._file_fd >= 0)
282
+ close(res._file_fd);
281
283
 
282
284
  free(buf);
283
285
  if (write_err < 0) break;
@@ -348,7 +350,7 @@ static int create_listener(int port, int reuseport) {
348
350
  int opt = 1;
349
351
  setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
350
352
 
351
- #ifdef __linux__
353
+ #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
352
354
  if (reuseport) setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
353
355
  #else
354
356
  (void)reuseport;
@@ -621,6 +623,13 @@ int cerver_init(cerver_server_t* srv, int port, int threads) {
621
623
  int cerver_add_routes(cerver_server_t* srv, cerver_route_t* routes, int count) {
622
624
  srv->routes = routes;
623
625
  srv->route_count = count;
626
+
627
+ srv->route_trie = cerver_trie_create();
628
+ if (srv->route_trie) {
629
+ for (int i = 0; i < count; i++) {
630
+ cerver_trie_insert(srv->route_trie, routes[i].pattern, routes[i].method, routes[i].handler);
631
+ }
632
+ }
624
633
  return 0;
625
634
  }
626
635
 
@@ -702,7 +711,7 @@ int cerver_listen(cerver_server_t* srv) {
702
711
  return -1;
703
712
  }
704
713
 
705
- srv->sock_fd = create_listener(srv->port, 0);
714
+ srv->sock_fd = create_listener(srv->port, 1);
706
715
  if (srv->sock_fd < 0) {
707
716
  srv->running = 0;
708
717
  for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
@@ -730,9 +739,13 @@ int cerver_listen(cerver_server_t* srv) {
730
739
  w->id = i;
731
740
  w->srv = srv;
732
741
  w->event_fd = -1;
733
- #ifdef __linux__
734
- w->listen_fd = create_listener(srv->port, 1);
735
- if (w->listen_fd < 0) w->listen_fd = srv->sock_fd;
742
+ #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
743
+ if (i == 0) {
744
+ w->listen_fd = srv->sock_fd;
745
+ } else {
746
+ w->listen_fd = create_listener(srv->port, 1);
747
+ if (w->listen_fd < 0) w->listen_fd = srv->sock_fd;
748
+ }
736
749
  #else
737
750
  w->listen_fd = srv->sock_fd;
738
751
  #endif
@@ -758,6 +771,22 @@ int cerver_listen(cerver_server_t* srv) {
758
771
  printf("cerver: %d acceptor(s), %d connection workers, keep-alive max %d req/conn\n",
759
772
  acceptor_count, pool_size, CERVER_KEEPALIVE_MAX);
760
773
 
774
+ for (int i = 0; i < srv->route_count; i++) {
775
+ const char* method = srv->routes[i].method;
776
+ const char* color = "\x1B[35m";
777
+ if (strcmp(method, "GET") == 0) {
778
+ color = "\x1B[32m";
779
+ } else if (strcmp(method, "POST") == 0) {
780
+ color = "\x1B[33m";
781
+ } else if (strcmp(method, "PUT") == 0) {
782
+ color = "\x1B[36m";
783
+ } else if (strcmp(method, "DELETE") == 0) {
784
+ color = "\x1B[31m";
785
+ }
786
+ printf(" → Mapped {%s, %s%s\x1B[0m} route\n", srv->routes[i].pattern, color, method);
787
+ }
788
+ fflush(stdout);
789
+
761
790
  release_acceptors();
762
791
 
763
792
  pthread_attr_destroy(&attr);
@@ -791,7 +820,7 @@ void cerver_shutdown(cerver_server_t* srv) {
791
820
 
792
821
  if (srv->workers) {
793
822
  for (int i = 0; i < srv->worker_count; i++) {
794
- #ifdef __linux__
823
+ #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
795
824
  if (srv->workers[i].listen_fd != srv->sock_fd && srv->workers[i].listen_fd >= 0)
796
825
  close(srv->workers[i].listen_fd);
797
826
  #endif
@@ -801,6 +830,11 @@ void cerver_shutdown(cerver_server_t* srv) {
801
830
  srv->workers = NULL;
802
831
  }
803
832
 
833
+ if (srv->route_trie) {
834
+ cerver_trie_free(srv->route_trie);
835
+ srv->route_trie = NULL;
836
+ }
837
+
804
838
  if (srv->sock_fd >= 0) {
805
839
  close(srv->sock_fd);
806
840
  srv->sock_fd = -1;