@velox0/cerver 0.4.1 → 0.4.3
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.
- package/.github/workflows/ci.yml +35 -0
- package/.github/workflows/publish.yml +3 -0
- package/Makefile +26 -0
- package/README.md +22 -5
- package/bin/cerver.js +11 -0
- package/lib/codegen/dispatch_gen.js +3 -0
- package/lib/commands/dev.js +23 -9
- package/lib/commands/new.js +273 -79
- package/lib/ir/transform.js +14 -20
- package/package.json +3 -3
- package/runtime/cerver.h +55 -36
- package/runtime/http_parser.c +12 -11
- package/runtime/http_writer.c +152 -45
- package/runtime/mime.c +1 -0
- package/runtime/router.c +193 -10
- package/runtime/server.c +80 -61
- package/runtime/static.c +15 -55
- package/runtime/tests/minunit.c +76 -0
- package/runtime/tests/minunit.h +64 -0
- package/runtime/tests/runtime_tests.c +464 -0
- package/test/run.js +11 -0
package/runtime/router.c
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
#include <stdio.h>
|
|
12
12
|
#include <stdlib.h>
|
|
13
13
|
#include <string.h>
|
|
14
|
+
#include <strings.h>
|
|
14
15
|
|
|
15
16
|
/* ------------------------------------------------------------------ */
|
|
16
17
|
/* Request accessor helpers */
|
|
@@ -18,8 +19,15 @@
|
|
|
18
19
|
|
|
19
20
|
const char* cerver_req_param(const cerver_request_t* req, const char* key) {
|
|
20
21
|
for (int i = 0; i < req->params_count; i++) {
|
|
21
|
-
|
|
22
|
-
|
|
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
|
+
}
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
return "";
|
|
@@ -84,7 +92,7 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
|
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
const char* pattern = route->pattern;
|
|
87
|
-
const char* path
|
|
95
|
+
const char* path = req->path;
|
|
88
96
|
|
|
89
97
|
/* Fast path: exact match */
|
|
90
98
|
if (strcmp(pattern, path) == 0) {
|
|
@@ -101,6 +109,8 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
|
|
|
101
109
|
const char* rp = path; /* request path pointer */
|
|
102
110
|
|
|
103
111
|
int saved_params = req->params_count;
|
|
112
|
+
char* param_slashes[CERVER_MAX_PARAMS];
|
|
113
|
+
int param_slash_count = 0;
|
|
104
114
|
|
|
105
115
|
/* Skip leading '/' */
|
|
106
116
|
if (*pp == '/') pp++;
|
|
@@ -121,10 +131,12 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
|
|
|
121
131
|
/* Dynamic segment — extract parameter */
|
|
122
132
|
if (req->params_count < CERVER_MAX_PARAMS) {
|
|
123
133
|
req->params[req->params_count].key = pp_seg + 1;
|
|
124
|
-
/*
|
|
125
|
-
/* The key points into the route pattern (static/const) */
|
|
134
|
+
/* The key points into the route pattern (static/const, not NUL-terminated) */
|
|
126
135
|
req->params[req->params_count].value = rp_seg;
|
|
127
136
|
req->params_count++;
|
|
137
|
+
if (*rp == '/') {
|
|
138
|
+
param_slashes[param_slash_count++] = (char*)rp;
|
|
139
|
+
}
|
|
128
140
|
}
|
|
129
141
|
} else {
|
|
130
142
|
/* Static segment — must match exactly */
|
|
@@ -145,6 +157,11 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
|
|
|
145
157
|
return 0;
|
|
146
158
|
}
|
|
147
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
|
+
|
|
148
165
|
return 1;
|
|
149
166
|
}
|
|
150
167
|
|
|
@@ -152,6 +169,166 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
|
|
|
152
169
|
/* Dispatch: find and return the handler for a request */
|
|
153
170
|
/* ------------------------------------------------------------------ */
|
|
154
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, cerver_handler_fn handler) {
|
|
221
|
+
if (!trie) return;
|
|
222
|
+
trie_node_t* curr = (trie_node_t*)trie;
|
|
223
|
+
const char* p = pattern;
|
|
224
|
+
while (*p == '/') p++;
|
|
225
|
+
|
|
226
|
+
while (*p) {
|
|
227
|
+
const char* seg_start = p;
|
|
228
|
+
while (*p && *p != '/') p++;
|
|
229
|
+
size_t len = (size_t)(p - seg_start);
|
|
230
|
+
if (len == 0) {
|
|
231
|
+
while (*p == '/') p++;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Find if child exists
|
|
236
|
+
trie_node_t* child = NULL;
|
|
237
|
+
for (int i = 0; i < curr->children_count; i++) {
|
|
238
|
+
trie_node_t* c = curr->children[i];
|
|
239
|
+
if (strlen(c->segment) == len && memcmp(c->segment, seg_start, len) == 0) {
|
|
240
|
+
child = c;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!child) {
|
|
246
|
+
child = trie_create_node(seg_start, len);
|
|
247
|
+
if (curr->children_count >= curr->children_cap) {
|
|
248
|
+
curr->children_cap = curr->children_cap == 0 ? 4 : curr->children_cap * 2;
|
|
249
|
+
curr->children = realloc(curr->children, curr->children_cap * sizeof(trie_node_t*));
|
|
250
|
+
}
|
|
251
|
+
curr->children[curr->children_count++] = child;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
curr = child;
|
|
255
|
+
while (*p == '/') p++;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Add handler to leaf
|
|
259
|
+
if (curr->handler_count < 16) {
|
|
260
|
+
curr->handlers[curr->handler_count].method = method;
|
|
261
|
+
curr->handlers[curr->handler_count].handler = handler;
|
|
262
|
+
curr->handler_count++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
void cerver_trie_free(void* trie) {
|
|
267
|
+
if (!trie) return;
|
|
268
|
+
trie_node_t* node = (trie_node_t*)trie;
|
|
269
|
+
for (int i = 0; i < node->children_count; i++) {
|
|
270
|
+
cerver_trie_free(node->children[i]);
|
|
271
|
+
}
|
|
272
|
+
free(node->children);
|
|
273
|
+
free(node->segment);
|
|
274
|
+
free(node->param_name);
|
|
275
|
+
free(node);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
static int trie_match_recursive(trie_node_t* node, const char* path, cerver_request_t* req, cerver_handler_fn* out_handler, int param_start_idx) {
|
|
279
|
+
// Skip leading slashes
|
|
280
|
+
while (*path == '/') path++;
|
|
281
|
+
|
|
282
|
+
if (*path == '\0') {
|
|
283
|
+
// Check if node has a handler for req->method
|
|
284
|
+
for (int i = 0; i < node->handler_count; i++) {
|
|
285
|
+
if (strcmp(node->handlers[i].method, req->method) == 0) {
|
|
286
|
+
*out_handler = node->handlers[i].handler;
|
|
287
|
+
req->params_count = param_start_idx;
|
|
288
|
+
return 1;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Extract next segment from path
|
|
295
|
+
const char* seg_start = path;
|
|
296
|
+
while (*path && *path != '/') path++;
|
|
297
|
+
size_t seg_len = (size_t)(path - seg_start);
|
|
298
|
+
|
|
299
|
+
// Try static children first
|
|
300
|
+
for (int i = 0; i < node->children_count; i++) {
|
|
301
|
+
trie_node_t* child = node->children[i];
|
|
302
|
+
if (!child->is_param) {
|
|
303
|
+
if (strlen(child->segment) == seg_len && memcmp(child->segment, seg_start, seg_len) == 0) {
|
|
304
|
+
if (trie_match_recursive(child, path, req, out_handler, param_start_idx)) {
|
|
305
|
+
return 1;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Try parameter/dynamic children next
|
|
312
|
+
for (int i = 0; i < node->children_count; i++) {
|
|
313
|
+
trie_node_t* child = node->children[i];
|
|
314
|
+
if (child->is_param) {
|
|
315
|
+
if (param_start_idx < CERVER_MAX_PARAMS) {
|
|
316
|
+
req->params[param_start_idx].key = child->param_name;
|
|
317
|
+
req->params[param_start_idx].value = seg_start;
|
|
318
|
+
}
|
|
319
|
+
if (trie_match_recursive(child, path, req, out_handler, param_start_idx + 1)) {
|
|
320
|
+
return 1;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* ------------------------------------------------------------------ */
|
|
329
|
+
/* Dispatch: find and return the handler for a request */
|
|
330
|
+
/* ------------------------------------------------------------------ */
|
|
331
|
+
|
|
155
332
|
cerver_handler_fn cerver_dispatch(cerver_server_t* srv, cerver_request_t* req) {
|
|
156
333
|
/* Try the generated compile-time dispatch first */
|
|
157
334
|
if (srv->dispatch_override) {
|
|
@@ -159,13 +336,19 @@ cerver_handler_fn cerver_dispatch(cerver_server_t* srv, cerver_request_t* req) {
|
|
|
159
336
|
if (h) return h;
|
|
160
337
|
}
|
|
161
338
|
|
|
162
|
-
/* Fall back to generic route table scan */
|
|
163
|
-
if (!srv->
|
|
339
|
+
/* Fall back to generic route table scan via Trie */
|
|
340
|
+
if (!srv->route_trie) return NULL;
|
|
164
341
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
342
|
+
cerver_handler_fn handler = NULL;
|
|
343
|
+
req->params_count = 0;
|
|
344
|
+
if (trie_match_recursive((trie_node_t*)srv->route_trie, req->path, req, &handler, 0)) {
|
|
345
|
+
// NUL-terminate extracted values in-place inside req->path
|
|
346
|
+
for (int i = 0; i < req->params_count; i++) {
|
|
347
|
+
char* val = (char*)req->params[i].value;
|
|
348
|
+
while (*val && *val != '/') val++;
|
|
349
|
+
if (*val == '/') *val = '\0';
|
|
168
350
|
}
|
|
351
|
+
return handler;
|
|
169
352
|
}
|
|
170
353
|
|
|
171
354
|
return NULL;
|
package/runtime/server.c
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
#include <fcntl.h>
|
|
21
21
|
#include <time.h>
|
|
22
22
|
#include <sys/mman.h>
|
|
23
|
+
#include <sys/time.h>
|
|
23
24
|
#include <sys/socket.h>
|
|
24
25
|
#include <sys/types.h>
|
|
25
26
|
#include <netinet/in.h>
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
static void* cerver_memmem(const void* hay, size_t haylen, const void* needle, size_t nlen) {
|
|
56
57
|
if (nlen == 0) return (void*)hay;
|
|
57
58
|
if (nlen > haylen) return NULL;
|
|
58
|
-
const char* p
|
|
59
|
+
const char* p = (const char*)hay;
|
|
59
60
|
const char* end = p + haylen - nlen;
|
|
60
61
|
for (; p <= end; p++) {
|
|
61
62
|
if (memcmp(p, needle, nlen) == 0) return (void*)p;
|
|
@@ -91,12 +92,12 @@ static int get_cpu_count(void) {
|
|
|
91
92
|
/* ------------------------------------------------------------------ */
|
|
92
93
|
|
|
93
94
|
typedef struct {
|
|
94
|
-
int
|
|
95
|
-
int
|
|
96
|
-
int
|
|
97
|
-
int
|
|
95
|
+
int fds[CERVER_CONN_QUEUE_SIZE];
|
|
96
|
+
int head;
|
|
97
|
+
int tail;
|
|
98
|
+
int count;
|
|
98
99
|
pthread_mutex_t lock;
|
|
99
|
-
pthread_cond_t
|
|
100
|
+
pthread_cond_t not_empty;
|
|
100
101
|
} conn_queue_t;
|
|
101
102
|
|
|
102
103
|
static void cq_init(conn_queue_t* q) {
|
|
@@ -119,7 +120,7 @@ static int cq_push(conn_queue_t* q, int fd) {
|
|
|
119
120
|
return -1;
|
|
120
121
|
}
|
|
121
122
|
q->fds[q->tail] = fd;
|
|
122
|
-
q->tail
|
|
123
|
+
q->tail = (q->tail + 1) % CERVER_CONN_QUEUE_SIZE;
|
|
123
124
|
q->count++;
|
|
124
125
|
pthread_cond_signal(&q->not_empty);
|
|
125
126
|
pthread_mutex_unlock(&q->lock);
|
|
@@ -140,7 +141,7 @@ static int cq_pop(conn_queue_t* q, volatile int* running) {
|
|
|
140
141
|
pthread_mutex_unlock(&q->lock);
|
|
141
142
|
return -1;
|
|
142
143
|
}
|
|
143
|
-
int fd
|
|
144
|
+
int fd = q->fds[q->head];
|
|
144
145
|
q->head = (q->head + 1) % CERVER_CONN_QUEUE_SIZE;
|
|
145
146
|
q->count--;
|
|
146
147
|
pthread_mutex_unlock(&q->lock);
|
|
@@ -152,31 +153,31 @@ static conn_queue_t g_conn_queue;
|
|
|
152
153
|
|
|
153
154
|
/* Global worker readiness tracking */
|
|
154
155
|
typedef struct {
|
|
155
|
-
int
|
|
156
|
-
int
|
|
156
|
+
int workers_ready;
|
|
157
|
+
int workers_expected;
|
|
157
158
|
pthread_mutex_t lock;
|
|
158
|
-
pthread_cond_t
|
|
159
|
+
pthread_cond_t ready_cv;
|
|
159
160
|
} worker_readiness_t;
|
|
160
161
|
|
|
161
|
-
static worker_readiness_t g_worker_readiness = {.workers_ready
|
|
162
|
+
static worker_readiness_t g_worker_readiness = {.workers_ready = 0,
|
|
162
163
|
.workers_expected = 0,
|
|
163
|
-
.lock
|
|
164
|
-
.ready_cv
|
|
164
|
+
.lock = PTHREAD_MUTEX_INITIALIZER,
|
|
165
|
+
.ready_cv = PTHREAD_COND_INITIALIZER};
|
|
165
166
|
|
|
166
167
|
/* Global acceptor startup tracking */
|
|
167
168
|
typedef struct {
|
|
168
|
-
int
|
|
169
|
-
int
|
|
169
|
+
int acceptors_ready;
|
|
170
|
+
int start_accepting;
|
|
170
171
|
pthread_mutex_t lock;
|
|
171
|
-
pthread_cond_t
|
|
172
|
-
pthread_cond_t
|
|
172
|
+
pthread_cond_t ready_cv;
|
|
173
|
+
pthread_cond_t start_cv;
|
|
173
174
|
} acceptor_readiness_t;
|
|
174
175
|
|
|
175
176
|
static acceptor_readiness_t g_acceptor_readiness = {.acceptors_ready = 0,
|
|
176
177
|
.start_accepting = 0,
|
|
177
|
-
.lock
|
|
178
|
-
.ready_cv
|
|
179
|
-
.start_cv
|
|
178
|
+
.lock = PTHREAD_MUTEX_INITIALIZER,
|
|
179
|
+
.ready_cv = PTHREAD_COND_INITIALIZER,
|
|
180
|
+
.start_cv = PTHREAD_COND_INITIALIZER};
|
|
180
181
|
|
|
181
182
|
/* ------------------------------------------------------------------ */
|
|
182
183
|
/* Buffered read */
|
|
@@ -185,7 +186,7 @@ static acceptor_readiness_t g_acceptor_readiness = {.acceptors_ready = 0,
|
|
|
185
186
|
static char* read_full_request(int fd, size_t* out_len) {
|
|
186
187
|
size_t cap = CERVER_READ_BUF;
|
|
187
188
|
size_t len = 0;
|
|
188
|
-
char*
|
|
189
|
+
char* buf = malloc(cap + 1);
|
|
189
190
|
if (!buf) return NULL;
|
|
190
191
|
|
|
191
192
|
while (len < (size_t)CERVER_READ_BUF_MAX) {
|
|
@@ -227,16 +228,16 @@ static void handle_connection(cerver_server_t* srv, int client_fd) {
|
|
|
227
228
|
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
|
|
228
229
|
|
|
229
230
|
int request_count = 0;
|
|
230
|
-
int keepalive
|
|
231
|
+
int keepalive = 1;
|
|
231
232
|
|
|
232
233
|
while (keepalive && srv->running && request_count < CERVER_KEEPALIVE_MAX) {
|
|
233
234
|
struct timeval tv;
|
|
234
|
-
tv.tv_sec
|
|
235
|
+
tv.tv_sec = (request_count == 0) ? 5 : CERVER_KEEPALIVE_TIMEOUT;
|
|
235
236
|
tv.tv_usec = 0;
|
|
236
237
|
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
237
238
|
|
|
238
239
|
size_t req_len = 0;
|
|
239
|
-
char*
|
|
240
|
+
char* buf = read_full_request(client_fd, &req_len);
|
|
240
241
|
if (!buf || req_len == 0) {
|
|
241
242
|
if (buf) free(buf);
|
|
242
243
|
break;
|
|
@@ -277,6 +278,8 @@ static void handle_connection(cerver_server_t* srv, int client_fd) {
|
|
|
277
278
|
free((void*)res.body);
|
|
278
279
|
else if (res._body_owned == 2 && res.body)
|
|
279
280
|
munmap((void*)res.body, res.body_len);
|
|
281
|
+
else if (res._body_owned == 3 && res._file_fd >= 0)
|
|
282
|
+
close(res._file_fd);
|
|
280
283
|
|
|
281
284
|
free(buf);
|
|
282
285
|
if (write_err < 0) break;
|
|
@@ -347,7 +350,7 @@ static int create_listener(int port, int reuseport) {
|
|
|
347
350
|
int opt = 1;
|
|
348
351
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
|
349
352
|
|
|
350
|
-
#
|
|
353
|
+
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
|
351
354
|
if (reuseport) setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
|
|
352
355
|
#else
|
|
353
356
|
(void)reuseport;
|
|
@@ -355,9 +358,9 @@ static int create_listener(int port, int reuseport) {
|
|
|
355
358
|
|
|
356
359
|
struct sockaddr_in addr;
|
|
357
360
|
memset(&addr, 0, sizeof(addr));
|
|
358
|
-
addr.sin_family
|
|
361
|
+
addr.sin_family = AF_INET;
|
|
359
362
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
360
|
-
addr.sin_port
|
|
363
|
+
addr.sin_port = htons((uint16_t)port);
|
|
361
364
|
|
|
362
365
|
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
|
363
366
|
perror("cerver: bind");
|
|
@@ -380,7 +383,7 @@ static int create_listener(int port, int reuseport) {
|
|
|
380
383
|
|
|
381
384
|
static int accept_connection(int listen_fd) {
|
|
382
385
|
struct sockaddr_in ca;
|
|
383
|
-
socklen_t
|
|
386
|
+
socklen_t cl = sizeof(ca);
|
|
384
387
|
#ifdef __linux__
|
|
385
388
|
return accept4(listen_fd, (struct sockaddr*)&ca, &cl, SOCK_CLOEXEC);
|
|
386
389
|
#else
|
|
@@ -394,7 +397,7 @@ static int accept_connection(int listen_fd) {
|
|
|
394
397
|
|
|
395
398
|
#if CERVER_USE_KQUEUE
|
|
396
399
|
static void* acceptor_loop(void* arg) {
|
|
397
|
-
cerver_worker_t* w
|
|
400
|
+
cerver_worker_t* w = (cerver_worker_t*)arg;
|
|
398
401
|
cerver_server_t* srv = w->srv;
|
|
399
402
|
|
|
400
403
|
int kq = kqueue();
|
|
@@ -419,8 +422,8 @@ static void* acceptor_loop(void* arg) {
|
|
|
419
422
|
pthread_mutex_unlock(&g_acceptor_readiness.lock);
|
|
420
423
|
|
|
421
424
|
while (srv->running) {
|
|
422
|
-
struct timespec ts
|
|
423
|
-
int
|
|
425
|
+
struct timespec ts = {1, 0};
|
|
426
|
+
int nev = kevent(kq, NULL, 0, events, CERVER_MAX_EVENTS, &ts);
|
|
424
427
|
if (nev < 0) {
|
|
425
428
|
if (errno == EINTR) continue;
|
|
426
429
|
break;
|
|
@@ -454,7 +457,7 @@ static void* acceptor_loop(void* arg) {
|
|
|
454
457
|
|
|
455
458
|
#elif CERVER_USE_EPOLL
|
|
456
459
|
static void* acceptor_loop(void* arg) {
|
|
457
|
-
cerver_worker_t* w
|
|
460
|
+
cerver_worker_t* w = (cerver_worker_t*)arg;
|
|
458
461
|
cerver_server_t* srv = w->srv;
|
|
459
462
|
|
|
460
463
|
int ep = epoll_create1(EPOLL_CLOEXEC);
|
|
@@ -511,7 +514,7 @@ static void* acceptor_loop(void* arg) {
|
|
|
511
514
|
|
|
512
515
|
#else /* SELECT */
|
|
513
516
|
static void* acceptor_loop(void* arg) {
|
|
514
|
-
cerver_worker_t* w
|
|
517
|
+
cerver_worker_t* w = (cerver_worker_t*)arg;
|
|
515
518
|
cerver_server_t* srv = w->srv;
|
|
516
519
|
|
|
517
520
|
pthread_mutex_lock(&g_acceptor_readiness.lock);
|
|
@@ -526,8 +529,8 @@ static void* acceptor_loop(void* arg) {
|
|
|
526
529
|
fd_set rfds;
|
|
527
530
|
FD_ZERO(&rfds);
|
|
528
531
|
FD_SET(w->listen_fd, &rfds);
|
|
529
|
-
struct timeval tv
|
|
530
|
-
int
|
|
532
|
+
struct timeval tv = {1, 0};
|
|
533
|
+
int ret = select(w->listen_fd + 1, &rfds, NULL, NULL, &tv);
|
|
531
534
|
if (ret < 0) {
|
|
532
535
|
if (errno == EINTR) continue;
|
|
533
536
|
break;
|
|
@@ -577,7 +580,7 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
|
|
|
577
580
|
time_t mtime) {
|
|
578
581
|
time_t now = time(NULL);
|
|
579
582
|
pthread_mutex_lock(&cache->lock);
|
|
580
|
-
int
|
|
583
|
+
int best = 0;
|
|
581
584
|
time_t oldest = cache->entries[0].cached_at;
|
|
582
585
|
for (int i = 0; i < CERVER_STAT_CACHE_SIZE; i++) {
|
|
583
586
|
cerver_stat_entry_t* e = &cache->entries[i];
|
|
@@ -587,16 +590,16 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
|
|
|
587
590
|
}
|
|
588
591
|
if (e->cached_at < oldest) {
|
|
589
592
|
oldest = e->cached_at;
|
|
590
|
-
best
|
|
593
|
+
best = i;
|
|
591
594
|
}
|
|
592
595
|
}
|
|
593
596
|
cerver_stat_entry_t* slot = &cache->entries[best];
|
|
594
597
|
strncpy(slot->path, path, sizeof(slot->path) - 1);
|
|
595
598
|
slot->path[sizeof(slot->path) - 1] = '\0';
|
|
596
|
-
slot->file_size
|
|
597
|
-
slot->mtime
|
|
598
|
-
slot->cached_at
|
|
599
|
-
slot->valid
|
|
599
|
+
slot->file_size = file_size;
|
|
600
|
+
slot->mtime = mtime;
|
|
601
|
+
slot->cached_at = now;
|
|
602
|
+
slot->valid = 1;
|
|
600
603
|
pthread_mutex_unlock(&cache->lock);
|
|
601
604
|
}
|
|
602
605
|
|
|
@@ -606,25 +609,32 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
|
|
|
606
609
|
|
|
607
610
|
int cerver_init(cerver_server_t* srv, int port, int threads) {
|
|
608
611
|
memset(srv, 0, sizeof(*srv));
|
|
609
|
-
srv->port
|
|
610
|
-
srv->sock_fd
|
|
611
|
-
srv->running
|
|
612
|
-
srv->public_dir
|
|
612
|
+
srv->port = port;
|
|
613
|
+
srv->sock_fd = -1;
|
|
614
|
+
srv->running = 0;
|
|
615
|
+
srv->public_dir = NULL;
|
|
613
616
|
srv->dispatch_override = NULL;
|
|
614
|
-
srv->worker_count
|
|
615
|
-
srv->workers
|
|
617
|
+
srv->worker_count = (threads > 0) ? threads : get_cpu_count();
|
|
618
|
+
srv->workers = NULL;
|
|
616
619
|
cerver_stat_cache_init(&srv->stat_cache);
|
|
617
620
|
return 0;
|
|
618
621
|
}
|
|
619
622
|
|
|
620
623
|
int cerver_add_routes(cerver_server_t* srv, cerver_route_t* routes, int count) {
|
|
621
|
-
srv->routes
|
|
624
|
+
srv->routes = routes;
|
|
622
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
|
+
}
|
|
623
633
|
return 0;
|
|
624
634
|
}
|
|
625
635
|
|
|
626
636
|
int cerver_set_assets(cerver_server_t* srv, cerver_asset_t* assets, int count) {
|
|
627
|
-
srv->assets
|
|
637
|
+
srv->assets = assets;
|
|
628
638
|
srv->asset_count = count;
|
|
629
639
|
return 0;
|
|
630
640
|
}
|
|
@@ -646,7 +656,7 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
646
656
|
/* Determine pool and acceptor counts.
|
|
647
657
|
* Acceptors: min(worker_count, cpu_count) — one per core for accept.
|
|
648
658
|
* Pool workers: worker_count * 16 — enough to cover concurrent keep-alive. */
|
|
649
|
-
int cpu_count
|
|
659
|
+
int cpu_count = get_cpu_count();
|
|
650
660
|
int acceptor_count = cpu_count;
|
|
651
661
|
if (acceptor_count > srv->worker_count) acceptor_count = srv->worker_count;
|
|
652
662
|
if (acceptor_count < 1) acceptor_count = 1;
|
|
@@ -660,7 +670,7 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
660
670
|
|
|
661
671
|
/* Initialize worker readiness tracking */
|
|
662
672
|
pthread_mutex_lock(&g_worker_readiness.lock);
|
|
663
|
-
g_worker_readiness.workers_ready
|
|
673
|
+
g_worker_readiness.workers_ready = 0;
|
|
664
674
|
g_worker_readiness.workers_expected = pool_size;
|
|
665
675
|
pthread_mutex_unlock(&g_worker_readiness.lock);
|
|
666
676
|
|
|
@@ -701,7 +711,7 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
701
711
|
return -1;
|
|
702
712
|
}
|
|
703
713
|
|
|
704
|
-
srv->sock_fd = create_listener(srv->port,
|
|
714
|
+
srv->sock_fd = create_listener(srv->port, 1);
|
|
705
715
|
if (srv->sock_fd < 0) {
|
|
706
716
|
srv->running = 0;
|
|
707
717
|
for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
|
|
@@ -711,7 +721,7 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
711
721
|
}
|
|
712
722
|
|
|
713
723
|
/* Start acceptor threads */
|
|
714
|
-
srv->workers
|
|
724
|
+
srv->workers = calloc((size_t)acceptor_count, sizeof(cerver_worker_t));
|
|
715
725
|
srv->worker_count = acceptor_count;
|
|
716
726
|
if (!srv->workers) {
|
|
717
727
|
perror("cerver: calloc acceptors");
|
|
@@ -726,12 +736,16 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
726
736
|
|
|
727
737
|
for (int i = 0; i < acceptor_count; i++) {
|
|
728
738
|
cerver_worker_t* w = &srv->workers[i];
|
|
729
|
-
w->id
|
|
730
|
-
w->srv
|
|
731
|
-
w->event_fd
|
|
732
|
-
#
|
|
733
|
-
|
|
734
|
-
|
|
739
|
+
w->id = i;
|
|
740
|
+
w->srv = srv;
|
|
741
|
+
w->event_fd = -1;
|
|
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
|
+
}
|
|
735
749
|
#else
|
|
736
750
|
w->listen_fd = srv->sock_fd;
|
|
737
751
|
#endif
|
|
@@ -790,7 +804,7 @@ void cerver_shutdown(cerver_server_t* srv) {
|
|
|
790
804
|
|
|
791
805
|
if (srv->workers) {
|
|
792
806
|
for (int i = 0; i < srv->worker_count; i++) {
|
|
793
|
-
#
|
|
807
|
+
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
|
794
808
|
if (srv->workers[i].listen_fd != srv->sock_fd && srv->workers[i].listen_fd >= 0)
|
|
795
809
|
close(srv->workers[i].listen_fd);
|
|
796
810
|
#endif
|
|
@@ -800,6 +814,11 @@ void cerver_shutdown(cerver_server_t* srv) {
|
|
|
800
814
|
srv->workers = NULL;
|
|
801
815
|
}
|
|
802
816
|
|
|
817
|
+
if (srv->route_trie) {
|
|
818
|
+
cerver_trie_free(srv->route_trie);
|
|
819
|
+
srv->route_trie = NULL;
|
|
820
|
+
}
|
|
821
|
+
|
|
803
822
|
if (srv->sock_fd >= 0) {
|
|
804
823
|
close(srv->sock_fd);
|
|
805
824
|
srv->sock_fd = -1;
|