@velox0/cerver 0.1.0 → 0.3.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.
- package/.github/workflows/publish.yml +48 -0
- package/README.md +28 -17
- package/bin/cerver.js +10 -0
- package/lib/assets/compress.js +55 -0
- package/lib/assets/embed.js +73 -4
- package/lib/codegen/generator.js +1 -1
- package/lib/commands/build.js +3 -2
- package/lib/commands/dev.js +146 -0
- package/lib/compiler/compile.js +1 -0
- package/lib/config.js +5 -1
- package/package.json +6 -1
- package/runtime/cerver.h +22 -1
- package/runtime/router.c +1 -1
- package/runtime/server.c +238 -27
- package/runtime/static.c +78 -15
- package/test/run.js +355 -0
package/runtime/server.c
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* server.c — Socket setup and event loop for the cerver runtime.
|
|
2
|
+
* server.c — Socket setup, thread pool, and event loop for the cerver runtime.
|
|
3
3
|
*
|
|
4
4
|
* Uses kqueue on macOS, epoll on Linux, with a select() fallback.
|
|
5
|
+
* Requests are dispatched to a fixed-size thread pool via a ring-buffer
|
|
6
|
+
* task queue protected by a mutex + condition variable.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
#include "cerver.h"
|
|
@@ -17,6 +19,7 @@
|
|
|
17
19
|
#include <sys/types.h>
|
|
18
20
|
#include <netinet/in.h>
|
|
19
21
|
#include <arpa/inet.h>
|
|
22
|
+
#include <pthread.h>
|
|
20
23
|
|
|
21
24
|
/* Platform-specific event API */
|
|
22
25
|
#if defined(__APPLE__) || defined(__FreeBSD__)
|
|
@@ -49,53 +52,122 @@ static int set_nonblocking(int fd) {
|
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
/* ------------------------------------------------------------------ */
|
|
52
|
-
/*
|
|
55
|
+
/* memmem fallback for systems that lack it */
|
|
53
56
|
/* ------------------------------------------------------------------ */
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
#if !defined(__APPLE__) && !defined(__linux__) && !defined(_GNU_SOURCE)
|
|
59
|
+
static void *cerver_memmem(const void *hay, size_t haylen,
|
|
60
|
+
const void *needle, size_t nlen) {
|
|
61
|
+
if (nlen == 0) return (void *)hay;
|
|
62
|
+
if (nlen > haylen) return NULL;
|
|
63
|
+
const char *p = (const char *)hay;
|
|
64
|
+
const char *end = p + haylen - nlen;
|
|
65
|
+
for (; p <= end; p++) {
|
|
66
|
+
if (memcmp(p, needle, nlen) == 0) return (void *)p;
|
|
67
|
+
}
|
|
68
|
+
return NULL;
|
|
59
69
|
}
|
|
70
|
+
#define memmem cerver_memmem
|
|
71
|
+
#endif
|
|
72
|
+
|
|
73
|
+
/* ------------------------------------------------------------------ */
|
|
74
|
+
/* Buffered read — accumulates until \r\n\r\n or limit reached */
|
|
75
|
+
/* ------------------------------------------------------------------ */
|
|
76
|
+
|
|
77
|
+
static char *read_full_request(int fd, size_t *out_len) {
|
|
78
|
+
size_t cap = CERVER_READ_BUF;
|
|
79
|
+
size_t len = 0;
|
|
80
|
+
char *buf = malloc(cap + 1); /* +1 for null terminator */
|
|
81
|
+
if (!buf) return NULL;
|
|
82
|
+
|
|
83
|
+
while (len < (size_t)CERVER_READ_BUF_MAX) {
|
|
84
|
+
ssize_t n = read(fd, buf + len, cap - len);
|
|
85
|
+
if (n <= 0) break;
|
|
86
|
+
len += (size_t)n;
|
|
87
|
+
|
|
88
|
+
/* Check for end of headers */
|
|
89
|
+
if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4)) break;
|
|
90
|
+
|
|
91
|
+
/* Grow buffer if full */
|
|
92
|
+
if (len == cap) {
|
|
93
|
+
size_t newcap = cap * 2;
|
|
94
|
+
if (newcap > (size_t)CERVER_READ_BUF_MAX)
|
|
95
|
+
newcap = (size_t)CERVER_READ_BUF_MAX;
|
|
96
|
+
char *tmp = realloc(buf, newcap + 1);
|
|
97
|
+
if (!tmp) { free(buf); return NULL; }
|
|
98
|
+
buf = tmp;
|
|
99
|
+
cap = newcap;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (len == 0) {
|
|
104
|
+
free(buf);
|
|
105
|
+
return NULL;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
buf[len] = '\0';
|
|
109
|
+
*out_len = len;
|
|
110
|
+
return buf;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ------------------------------------------------------------------ */
|
|
114
|
+
/* Handle a single connection: read, parse, dispatch, respond */
|
|
115
|
+
/* ------------------------------------------------------------------ */
|
|
60
116
|
|
|
61
117
|
static void handle_connection(cerver_server_t *srv, int client_fd) {
|
|
62
|
-
/*
|
|
63
|
-
|
|
64
|
-
|
|
118
|
+
/* Ensure the client socket is in blocking mode for reads.
|
|
119
|
+
Some platforms inherit O_NONBLOCK from the listening socket. */
|
|
120
|
+
int flags = fcntl(client_fd, F_GETFL, 0);
|
|
121
|
+
if (flags >= 0 && (flags & O_NONBLOCK)) {
|
|
122
|
+
fcntl(client_fd, F_SETFL, flags & ~O_NONBLOCK);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Set a read timeout so workers don't block on stale connections */
|
|
126
|
+
struct timeval tv = { 5, 0 }; /* 5 seconds */
|
|
127
|
+
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
65
128
|
|
|
66
|
-
|
|
67
|
-
|
|
129
|
+
/* Read the full request with buffering */
|
|
130
|
+
size_t req_len = 0;
|
|
131
|
+
char *buf = read_full_request(client_fd, &req_len);
|
|
68
132
|
|
|
69
|
-
if (
|
|
133
|
+
if (!buf || req_len == 0) {
|
|
134
|
+
if (buf) free(buf);
|
|
70
135
|
close(client_fd);
|
|
71
136
|
return;
|
|
72
137
|
}
|
|
73
|
-
buf[n] = '\0';
|
|
74
138
|
|
|
75
139
|
/* Parse HTTP request */
|
|
76
140
|
cerver_request_t req;
|
|
77
141
|
memset(&req, 0, sizeof(req));
|
|
78
142
|
|
|
79
|
-
if (cerver_parse_request(buf,
|
|
143
|
+
if (cerver_parse_request(buf, req_len, &req) < 0) {
|
|
80
144
|
/* Bad request — send 400 */
|
|
81
145
|
const char *resp = "HTTP/1.1 400 Bad Request\r\nContent-Length: 11\r\nConnection: close\r\n\r\nBad Request";
|
|
82
146
|
write(client_fd, resp, strlen(resp));
|
|
147
|
+
free(buf);
|
|
83
148
|
close(client_fd);
|
|
84
149
|
return;
|
|
85
150
|
}
|
|
86
151
|
|
|
152
|
+
/*
|
|
153
|
+
* The parser malloc'd its own mutable copy (req._raw_buf) and all
|
|
154
|
+
* internal pointers (path, headers, etc.) reference that copy.
|
|
155
|
+
* We can now free our original read buffer.
|
|
156
|
+
*/
|
|
157
|
+
free(buf);
|
|
158
|
+
|
|
87
159
|
/* Prepare response */
|
|
88
160
|
cerver_response_t res;
|
|
89
161
|
memset(&res, 0, sizeof(res));
|
|
90
162
|
|
|
91
|
-
/* Try
|
|
92
|
-
|
|
163
|
+
/* Try static assets first — files take priority over route handlers */
|
|
164
|
+
if (cerver_serve_static(srv, &req, &res) < 0) {
|
|
165
|
+
/* No static file found — try route handlers */
|
|
166
|
+
cerver_handler_fn handler = cerver_dispatch(srv, &req);
|
|
93
167
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/* Try static assets */
|
|
98
|
-
if (cerver_serve_static(srv, &req, &res) < 0) {
|
|
168
|
+
if (handler) {
|
|
169
|
+
handler(&req, &res);
|
|
170
|
+
} else {
|
|
99
171
|
/* 404 */
|
|
100
172
|
cerver_res_text(&res, 404, "Not Found");
|
|
101
173
|
}
|
|
@@ -115,16 +187,91 @@ static void handle_connection(cerver_server_t *srv, int client_fd) {
|
|
|
115
187
|
close(client_fd);
|
|
116
188
|
}
|
|
117
189
|
|
|
190
|
+
/* ------------------------------------------------------------------ */
|
|
191
|
+
/* Thread pool — task queue (ring buffer) */
|
|
192
|
+
/* ------------------------------------------------------------------ */
|
|
193
|
+
|
|
194
|
+
static int enqueue_task(cerver_server_t *srv, int client_fd) {
|
|
195
|
+
pthread_mutex_lock(&srv->tq_mutex);
|
|
196
|
+
|
|
197
|
+
if (srv->tq_count >= CERVER_TASK_QUEUE_SIZE) {
|
|
198
|
+
/* Queue full — drop connection */
|
|
199
|
+
pthread_mutex_unlock(&srv->tq_mutex);
|
|
200
|
+
const char *resp = "HTTP/1.1 503 Service Unavailable\r\n"
|
|
201
|
+
"Content-Length: 19\r\nConnection: close\r\n\r\n"
|
|
202
|
+
"Service Unavailable";
|
|
203
|
+
write(client_fd, resp, strlen(resp));
|
|
204
|
+
close(client_fd);
|
|
205
|
+
return -1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
srv->task_queue[srv->tq_tail] = client_fd;
|
|
209
|
+
srv->tq_tail = (srv->tq_tail + 1) % CERVER_TASK_QUEUE_SIZE;
|
|
210
|
+
srv->tq_count++;
|
|
211
|
+
|
|
212
|
+
pthread_cond_signal(&srv->tq_cond);
|
|
213
|
+
pthread_mutex_unlock(&srv->tq_mutex);
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static int dequeue_task(cerver_server_t *srv) {
|
|
218
|
+
pthread_mutex_lock(&srv->tq_mutex);
|
|
219
|
+
|
|
220
|
+
while (srv->tq_count == 0 && srv->running) {
|
|
221
|
+
pthread_cond_wait(&srv->tq_cond, &srv->tq_mutex);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!srv->running && srv->tq_count == 0) {
|
|
225
|
+
pthread_mutex_unlock(&srv->tq_mutex);
|
|
226
|
+
return -1; /* shutdown signal */
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
int fd = srv->task_queue[srv->tq_head];
|
|
230
|
+
srv->tq_head = (srv->tq_head + 1) % CERVER_TASK_QUEUE_SIZE;
|
|
231
|
+
srv->tq_count--;
|
|
232
|
+
|
|
233
|
+
pthread_mutex_unlock(&srv->tq_mutex);
|
|
234
|
+
return fd;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* ------------------------------------------------------------------ */
|
|
238
|
+
/* Worker thread entry point */
|
|
239
|
+
/* ------------------------------------------------------------------ */
|
|
240
|
+
|
|
241
|
+
static void *worker_thread(void *arg) {
|
|
242
|
+
cerver_server_t *srv = (cerver_server_t *)arg;
|
|
243
|
+
|
|
244
|
+
while (srv->running) {
|
|
245
|
+
int client_fd = dequeue_task(srv);
|
|
246
|
+
if (client_fd < 0) break; /* shutdown */
|
|
247
|
+
|
|
248
|
+
handle_connection(srv, client_fd);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return NULL;
|
|
252
|
+
}
|
|
253
|
+
|
|
118
254
|
/* ------------------------------------------------------------------ */
|
|
119
255
|
/* Server init */
|
|
120
256
|
/* ------------------------------------------------------------------ */
|
|
121
257
|
|
|
122
|
-
int cerver_init(cerver_server_t *srv, int port) {
|
|
258
|
+
int cerver_init(cerver_server_t *srv, int port, int threads) {
|
|
123
259
|
memset(srv, 0, sizeof(*srv));
|
|
124
260
|
srv->port = port;
|
|
125
261
|
srv->sock_fd = -1;
|
|
126
262
|
srv->running = 0;
|
|
127
263
|
srv->public_dir = NULL;
|
|
264
|
+
|
|
265
|
+
/* Thread pool config */
|
|
266
|
+
srv->thread_count = (threads > 0) ? threads : CERVER_THREAD_POOL_DEFAULT;
|
|
267
|
+
srv->threads = NULL;
|
|
268
|
+
srv->tq_head = 0;
|
|
269
|
+
srv->tq_tail = 0;
|
|
270
|
+
srv->tq_count = 0;
|
|
271
|
+
|
|
272
|
+
pthread_mutex_init(&srv->tq_mutex, NULL);
|
|
273
|
+
pthread_cond_init(&srv->tq_cond, NULL);
|
|
274
|
+
|
|
128
275
|
return 0;
|
|
129
276
|
}
|
|
130
277
|
|
|
@@ -144,6 +291,43 @@ void cerver_set_public_dir(cerver_server_t *srv, const char *dir) {
|
|
|
144
291
|
srv->public_dir = dir;
|
|
145
292
|
}
|
|
146
293
|
|
|
294
|
+
/* ------------------------------------------------------------------ */
|
|
295
|
+
/* Start thread pool */
|
|
296
|
+
/* ------------------------------------------------------------------ */
|
|
297
|
+
|
|
298
|
+
static int start_thread_pool(cerver_server_t *srv) {
|
|
299
|
+
srv->threads = malloc(sizeof(pthread_t) * (size_t)srv->thread_count);
|
|
300
|
+
if (!srv->threads) {
|
|
301
|
+
perror("cerver: malloc threads");
|
|
302
|
+
return -1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* Use 2 MB stack per worker (handles deep call chains with large buffers) */
|
|
306
|
+
pthread_attr_t attr;
|
|
307
|
+
pthread_attr_init(&attr);
|
|
308
|
+
pthread_attr_setstacksize(&attr, 2 * 1024 * 1024);
|
|
309
|
+
|
|
310
|
+
for (int i = 0; i < srv->thread_count; i++) {
|
|
311
|
+
if (pthread_create(&srv->threads[i], &attr, worker_thread, srv) != 0) {
|
|
312
|
+
perror("cerver: pthread_create");
|
|
313
|
+
/* Clean up already-created threads */
|
|
314
|
+
srv->running = 0;
|
|
315
|
+
pthread_cond_broadcast(&srv->tq_cond);
|
|
316
|
+
for (int j = 0; j < i; j++) {
|
|
317
|
+
pthread_join(srv->threads[j], NULL);
|
|
318
|
+
}
|
|
319
|
+
free(srv->threads);
|
|
320
|
+
srv->threads = NULL;
|
|
321
|
+
return -1;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
pthread_attr_destroy(&attr);
|
|
326
|
+
|
|
327
|
+
printf("cerver: started %d worker thread(s)\n", srv->thread_count);
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
147
331
|
/* ------------------------------------------------------------------ */
|
|
148
332
|
/* Server listen — event loop */
|
|
149
333
|
/* ------------------------------------------------------------------ */
|
|
@@ -190,7 +374,13 @@ int cerver_listen(cerver_server_t *srv) {
|
|
|
190
374
|
|
|
191
375
|
srv->running = 1;
|
|
192
376
|
|
|
193
|
-
printf("cerver: listening on http://
|
|
377
|
+
printf("cerver: listening on http://localhost:%d\n", srv->port);
|
|
378
|
+
|
|
379
|
+
/* Start the thread pool */
|
|
380
|
+
if (start_thread_pool(srv) < 0) {
|
|
381
|
+
close(srv->sock_fd);
|
|
382
|
+
return -1;
|
|
383
|
+
}
|
|
194
384
|
|
|
195
385
|
/* ================================================================== */
|
|
196
386
|
/* kqueue event loop (macOS / FreeBSD) */
|
|
@@ -236,7 +426,7 @@ int cerver_listen(cerver_server_t *srv) {
|
|
|
236
426
|
perror("cerver: accept");
|
|
237
427
|
break;
|
|
238
428
|
}
|
|
239
|
-
|
|
429
|
+
enqueue_task(srv, client_fd);
|
|
240
430
|
}
|
|
241
431
|
}
|
|
242
432
|
}
|
|
@@ -285,7 +475,7 @@ int cerver_listen(cerver_server_t *srv) {
|
|
|
285
475
|
perror("cerver: accept");
|
|
286
476
|
break;
|
|
287
477
|
}
|
|
288
|
-
|
|
478
|
+
enqueue_task(srv, client_fd);
|
|
289
479
|
}
|
|
290
480
|
}
|
|
291
481
|
}
|
|
@@ -319,7 +509,7 @@ int cerver_listen(cerver_server_t *srv) {
|
|
|
319
509
|
(struct sockaddr *)&client_addr,
|
|
320
510
|
&client_len);
|
|
321
511
|
if (client_fd >= 0) {
|
|
322
|
-
|
|
512
|
+
enqueue_task(srv, client_fd);
|
|
323
513
|
}
|
|
324
514
|
}
|
|
325
515
|
}
|
|
@@ -335,10 +525,31 @@ int cerver_listen(cerver_server_t *srv) {
|
|
|
335
525
|
/* ------------------------------------------------------------------ */
|
|
336
526
|
|
|
337
527
|
void cerver_shutdown(cerver_server_t *srv) {
|
|
528
|
+
srv->running = 0;
|
|
529
|
+
|
|
530
|
+
/* Wake all worker threads so they can exit */
|
|
531
|
+
pthread_mutex_lock(&srv->tq_mutex);
|
|
532
|
+
pthread_cond_broadcast(&srv->tq_cond);
|
|
533
|
+
pthread_mutex_unlock(&srv->tq_mutex);
|
|
534
|
+
|
|
535
|
+
/* Join all worker threads */
|
|
536
|
+
if (srv->threads) {
|
|
537
|
+
for (int i = 0; i < srv->thread_count; i++) {
|
|
538
|
+
pthread_join(srv->threads[i], NULL);
|
|
539
|
+
}
|
|
540
|
+
free(srv->threads);
|
|
541
|
+
srv->threads = NULL;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/* Close listener socket */
|
|
338
545
|
if (srv->sock_fd >= 0) {
|
|
339
546
|
close(srv->sock_fd);
|
|
340
547
|
srv->sock_fd = -1;
|
|
341
548
|
}
|
|
342
|
-
|
|
549
|
+
|
|
550
|
+
/* Destroy synchronization primitives */
|
|
551
|
+
pthread_mutex_destroy(&srv->tq_mutex);
|
|
552
|
+
pthread_cond_destroy(&srv->tq_cond);
|
|
553
|
+
|
|
343
554
|
printf("\ncerver: server stopped\n");
|
|
344
555
|
}
|
package/runtime/static.c
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* In embedded mode, serves from the compiled-in asset array.
|
|
5
5
|
* In external mode, serves from the filesystem (public/ directory).
|
|
6
|
+
* Supports pre-compressed gzip/brotli variants and cache headers.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
#include "cerver.h"
|
|
@@ -29,6 +30,40 @@ static int path_is_safe(const char *path) {
|
|
|
29
30
|
return 1;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/* ------------------------------------------------------------------ */
|
|
34
|
+
/* Accept-Encoding parsing */
|
|
35
|
+
/* ------------------------------------------------------------------ */
|
|
36
|
+
|
|
37
|
+
typedef struct {
|
|
38
|
+
int accepts_gzip;
|
|
39
|
+
int accepts_br;
|
|
40
|
+
} encoding_prefs_t;
|
|
41
|
+
|
|
42
|
+
static encoding_prefs_t parse_accept_encoding(const cerver_request_t *req) {
|
|
43
|
+
encoding_prefs_t prefs = { 0, 0 };
|
|
44
|
+
const char *ae = cerver_req_header(req, "Accept-Encoding");
|
|
45
|
+
if (!ae) return prefs;
|
|
46
|
+
|
|
47
|
+
if (strstr(ae, "br")) prefs.accepts_br = 1;
|
|
48
|
+
if (strstr(ae, "gzip")) prefs.accepts_gzip = 1;
|
|
49
|
+
|
|
50
|
+
return prefs;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ------------------------------------------------------------------ */
|
|
54
|
+
/* Cache header helper */
|
|
55
|
+
/* ------------------------------------------------------------------ */
|
|
56
|
+
|
|
57
|
+
static void add_cache_headers(cerver_response_t *res, const char *path) {
|
|
58
|
+
/* Hashed/versioned assets (in /static/) get long cache */
|
|
59
|
+
if (strstr(path, "/static/") || strstr(path, "/assets/")) {
|
|
60
|
+
cerver_res_header(res, "Cache-Control", "public, max-age=31536000, immutable");
|
|
61
|
+
} else {
|
|
62
|
+
/* HTML and other top-level files get short cache with revalidation */
|
|
63
|
+
cerver_res_header(res, "Cache-Control", "public, max-age=3600, must-revalidate");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
32
67
|
/* ------------------------------------------------------------------ */
|
|
33
68
|
/* Serve from embedded assets */
|
|
34
69
|
/* ------------------------------------------------------------------ */
|
|
@@ -38,33 +73,59 @@ static int serve_embedded(cerver_server_t *srv, cerver_request_t *req,
|
|
|
38
73
|
if (!srv->assets || srv->asset_count == 0) return -1;
|
|
39
74
|
|
|
40
75
|
const char *path = req->path;
|
|
76
|
+
const cerver_asset_t *found = NULL;
|
|
41
77
|
|
|
42
78
|
/* Try exact match first */
|
|
43
79
|
for (int i = 0; i < srv->asset_count; i++) {
|
|
44
80
|
if (strcmp(srv->assets[i].path, path) == 0) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return 0;
|
|
81
|
+
found = &srv->assets[i];
|
|
82
|
+
break;
|
|
48
83
|
}
|
|
49
84
|
}
|
|
50
85
|
|
|
51
86
|
/* Try with /index.html appended (for directory-like paths) */
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
87
|
+
if (!found) {
|
|
88
|
+
char index_path[CERVER_MAX_PATH];
|
|
89
|
+
if (path[strlen(path) - 1] == '/') {
|
|
90
|
+
snprintf(index_path, sizeof(index_path), "%sindex.html", path);
|
|
91
|
+
} else {
|
|
92
|
+
snprintf(index_path, sizeof(index_path), "%s/index.html", path);
|
|
93
|
+
}
|
|
58
94
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
95
|
+
for (int i = 0; i < srv->asset_count; i++) {
|
|
96
|
+
if (strcmp(srv->assets[i].path, index_path) == 0) {
|
|
97
|
+
found = &srv->assets[i];
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
64
100
|
}
|
|
65
101
|
}
|
|
66
102
|
|
|
67
|
-
return -1;
|
|
103
|
+
if (!found) return -1;
|
|
104
|
+
|
|
105
|
+
/* Check for pre-compressed variants */
|
|
106
|
+
encoding_prefs_t enc = parse_accept_encoding(req);
|
|
107
|
+
|
|
108
|
+
if (enc.accepts_br && found->data_br && found->data_br_len > 0) {
|
|
109
|
+
/* Serve brotli */
|
|
110
|
+
cerver_res_file(res, 200, found->mime_type,
|
|
111
|
+
found->data_br, found->data_br_len);
|
|
112
|
+
cerver_res_header(res, "Content-Encoding", "br");
|
|
113
|
+
cerver_res_header(res, "Vary", "Accept-Encoding");
|
|
114
|
+
} else if (enc.accepts_gzip && found->data_gz && found->data_gz_len > 0) {
|
|
115
|
+
/* Serve gzip */
|
|
116
|
+
cerver_res_file(res, 200, found->mime_type,
|
|
117
|
+
found->data_gz, found->data_gz_len);
|
|
118
|
+
cerver_res_header(res, "Content-Encoding", "gzip");
|
|
119
|
+
cerver_res_header(res, "Vary", "Accept-Encoding");
|
|
120
|
+
} else {
|
|
121
|
+
/* Serve uncompressed */
|
|
122
|
+
cerver_res_file(res, 200, found->mime_type,
|
|
123
|
+
found->data, found->data_len);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
add_cache_headers(res, found->path);
|
|
127
|
+
|
|
128
|
+
return 0;
|
|
68
129
|
}
|
|
69
130
|
|
|
70
131
|
/* ------------------------------------------------------------------ */
|
|
@@ -123,6 +184,8 @@ static int serve_filesystem(cerver_server_t *srv, cerver_request_t *req,
|
|
|
123
184
|
res->body_len = file_size;
|
|
124
185
|
res->_body_owned = 1; /* We malloc'd this */
|
|
125
186
|
|
|
187
|
+
add_cache_headers(res, path);
|
|
188
|
+
|
|
126
189
|
return 0;
|
|
127
190
|
}
|
|
128
191
|
|