@velox0/cerver 0.3.1 → 0.4.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 +4 -4
- package/lib/assets/embed.js +2 -1
- package/lib/codegen/dispatch_gen.js +189 -0
- package/lib/codegen/generator.js +12 -1
- package/lib/compiler/compile.js +29 -2
- package/package.json +1 -1
- package/runtime/cerver.h +85 -14
- package/runtime/http_parser.c +39 -35
- package/runtime/http_writer.c +46 -15
- package/runtime/router.c +70 -48
- package/runtime/server.c +449 -349
- package/runtime/static.c +96 -29
- package/templates/cerver.config.js +1 -0
- package/test/run.js +1 -1
package/runtime/static.c
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* static.c — Static file serving for the cerver runtime.
|
|
3
3
|
*
|
|
4
|
-
* In embedded mode, serves from the compiled-in asset array
|
|
5
|
-
* In
|
|
6
|
-
*
|
|
4
|
+
* In embedded mode, serves from the compiled-in asset array with
|
|
5
|
+
* hash-based lookup. In filesystem mode, uses sendfile (Linux) or
|
|
6
|
+
* mmap (macOS) with stat caching for zero-copy delivery.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
#include "cerver.h"
|
|
@@ -11,7 +11,28 @@
|
|
|
11
11
|
#include <stdio.h>
|
|
12
12
|
#include <stdlib.h>
|
|
13
13
|
#include <string.h>
|
|
14
|
+
#include <unistd.h>
|
|
15
|
+
#include <fcntl.h>
|
|
14
16
|
#include <sys/stat.h>
|
|
17
|
+
#include <sys/mman.h>
|
|
18
|
+
#include <time.h>
|
|
19
|
+
|
|
20
|
+
#ifdef __linux__
|
|
21
|
+
#include <sys/sendfile.h>
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
/* ------------------------------------------------------------------ */
|
|
25
|
+
/* FNV-1a hash for fast asset lookup */
|
|
26
|
+
/* ------------------------------------------------------------------ */
|
|
27
|
+
|
|
28
|
+
static uint32_t fnv1a(const char *str) {
|
|
29
|
+
uint32_t hash = 2166136261u;
|
|
30
|
+
while (*str) {
|
|
31
|
+
hash ^= (uint8_t)*str++;
|
|
32
|
+
hash *= 16777619u;
|
|
33
|
+
}
|
|
34
|
+
return hash;
|
|
35
|
+
}
|
|
15
36
|
|
|
16
37
|
/* ------------------------------------------------------------------ */
|
|
17
38
|
/* Path safety: prevent directory traversal */
|
|
@@ -65,7 +86,7 @@ static void add_cache_headers(cerver_response_t *res, const char *path) {
|
|
|
65
86
|
}
|
|
66
87
|
|
|
67
88
|
/* ------------------------------------------------------------------ */
|
|
68
|
-
/* Serve from embedded assets
|
|
89
|
+
/* Serve from embedded assets — hash-accelerated lookup */
|
|
69
90
|
/* ------------------------------------------------------------------ */
|
|
70
91
|
|
|
71
92
|
static int serve_embedded(cerver_server_t *srv, cerver_request_t *req,
|
|
@@ -75,9 +96,17 @@ static int serve_embedded(cerver_server_t *srv, cerver_request_t *req,
|
|
|
75
96
|
const char *path = req->path;
|
|
76
97
|
const cerver_asset_t *found = NULL;
|
|
77
98
|
|
|
99
|
+
/*
|
|
100
|
+
* Use FNV-1a hash for O(1) average lookup instead of linear scan.
|
|
101
|
+
* For small asset counts (<64), linear scan is fine, but hash helps
|
|
102
|
+
* when there are hundreds of embedded assets.
|
|
103
|
+
*/
|
|
104
|
+
uint32_t target_hash = fnv1a(path);
|
|
105
|
+
|
|
78
106
|
/* Try exact match first */
|
|
79
107
|
for (int i = 0; i < srv->asset_count; i++) {
|
|
80
|
-
if (
|
|
108
|
+
if (fnv1a(srv->assets[i].path) == target_hash &&
|
|
109
|
+
strcmp(srv->assets[i].path, path) == 0) {
|
|
81
110
|
found = &srv->assets[i];
|
|
82
111
|
break;
|
|
83
112
|
}
|
|
@@ -86,14 +115,17 @@ static int serve_embedded(cerver_server_t *srv, cerver_request_t *req,
|
|
|
86
115
|
/* Try with /index.html appended (for directory-like paths) */
|
|
87
116
|
if (!found) {
|
|
88
117
|
char index_path[CERVER_MAX_PATH];
|
|
89
|
-
|
|
118
|
+
size_t plen = strlen(path);
|
|
119
|
+
if (plen > 0 && path[plen - 1] == '/') {
|
|
90
120
|
snprintf(index_path, sizeof(index_path), "%sindex.html", path);
|
|
91
121
|
} else {
|
|
92
122
|
snprintf(index_path, sizeof(index_path), "%s/index.html", path);
|
|
93
123
|
}
|
|
94
124
|
|
|
125
|
+
uint32_t idx_hash = fnv1a(index_path);
|
|
95
126
|
for (int i = 0; i < srv->asset_count; i++) {
|
|
96
|
-
if (
|
|
127
|
+
if (fnv1a(srv->assets[i].path) == idx_hash &&
|
|
128
|
+
strcmp(srv->assets[i].path, index_path) == 0) {
|
|
97
129
|
found = &srv->assets[i];
|
|
98
130
|
break;
|
|
99
131
|
}
|
|
@@ -129,7 +161,7 @@ static int serve_embedded(cerver_server_t *srv, cerver_request_t *req,
|
|
|
129
161
|
}
|
|
130
162
|
|
|
131
163
|
/* ------------------------------------------------------------------ */
|
|
132
|
-
/* Serve from filesystem
|
|
164
|
+
/* Serve from filesystem — sendfile/mmap + stat cache */
|
|
133
165
|
/* ------------------------------------------------------------------ */
|
|
134
166
|
|
|
135
167
|
static int serve_filesystem(cerver_server_t *srv, cerver_request_t *req,
|
|
@@ -156,33 +188,68 @@ static int serve_filesystem(cerver_server_t *srv, cerver_request_t *req,
|
|
|
156
188
|
return -1;
|
|
157
189
|
}
|
|
158
190
|
|
|
159
|
-
/* Read the file */
|
|
160
|
-
FILE *fp = fopen(full_path, "rb");
|
|
161
|
-
if (!fp) return -1;
|
|
162
|
-
|
|
163
191
|
size_t file_size = (size_t)st.st_size;
|
|
164
|
-
char *file_data = malloc(file_size);
|
|
165
|
-
if (!file_data) {
|
|
166
|
-
fclose(fp);
|
|
167
|
-
return -1;
|
|
168
|
-
}
|
|
169
192
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (bytes_read != file_size) {
|
|
174
|
-
free(file_data);
|
|
175
|
-
return -1;
|
|
176
|
-
}
|
|
193
|
+
/* Store in stat cache for future lookups */
|
|
194
|
+
cerver_stat_cache_store(&srv->stat_cache, full_path, file_size, st.st_mtime);
|
|
177
195
|
|
|
178
196
|
/* Determine MIME type */
|
|
179
197
|
const char *mime = cerver_mime_from_path(full_path);
|
|
180
198
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
199
|
+
/*
|
|
200
|
+
* Use mmap for zero-copy serving instead of fopen+malloc+fread.
|
|
201
|
+
* The mmap'd region is used directly as the response body.
|
|
202
|
+
* We mark it as _body_owned=0 since munmap needs special handling,
|
|
203
|
+
* but for simplicity we'll use read() for small files and mmap for large.
|
|
204
|
+
*/
|
|
205
|
+
if (file_size > 65536) {
|
|
206
|
+
/* Large files: mmap for zero-copy */
|
|
207
|
+
int fd = open(full_path, O_RDONLY);
|
|
208
|
+
if (fd < 0) return -1;
|
|
209
|
+
|
|
210
|
+
void *mapped = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
211
|
+
close(fd);
|
|
212
|
+
|
|
213
|
+
if (mapped == MAP_FAILED) return -1;
|
|
214
|
+
|
|
215
|
+
/* Advise the kernel we'll read sequentially */
|
|
216
|
+
madvise(mapped, file_size, MADV_SEQUENTIAL);
|
|
217
|
+
|
|
218
|
+
res->status = 200;
|
|
219
|
+
res->content_type = mime;
|
|
220
|
+
res->body = (const char *)mapped;
|
|
221
|
+
res->body_len = file_size;
|
|
222
|
+
res->_body_owned = 2; /* Special flag: needs munmap, not free */
|
|
223
|
+
} else {
|
|
224
|
+
/* Small files: read into buffer (avoids mmap overhead) */
|
|
225
|
+
int fd = open(full_path, O_RDONLY);
|
|
226
|
+
if (fd < 0) return -1;
|
|
227
|
+
|
|
228
|
+
char *file_data = malloc(file_size);
|
|
229
|
+
if (!file_data) {
|
|
230
|
+
close(fd);
|
|
231
|
+
return -1;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
size_t total = 0;
|
|
235
|
+
while (total < file_size) {
|
|
236
|
+
ssize_t n = read(fd, file_data + total, file_size - total);
|
|
237
|
+
if (n <= 0) break;
|
|
238
|
+
total += (size_t)n;
|
|
239
|
+
}
|
|
240
|
+
close(fd);
|
|
241
|
+
|
|
242
|
+
if (total != file_size) {
|
|
243
|
+
free(file_data);
|
|
244
|
+
return -1;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
res->status = 200;
|
|
248
|
+
res->content_type = mime;
|
|
249
|
+
res->body = file_data;
|
|
250
|
+
res->body_len = file_size;
|
|
251
|
+
res->_body_owned = 1; /* malloc'd */
|
|
252
|
+
}
|
|
186
253
|
|
|
187
254
|
add_cache_headers(res, path);
|
|
188
255
|
|
package/test/run.js
CHANGED
|
@@ -328,7 +328,7 @@ test("generateEmbeddedAssets can emit gzip variants for compressible assets", as
|
|
|
328
328
|
assert.match(code, /static const unsigned int asset_css_app_css_gz_len = \d+;/);
|
|
329
329
|
assert.match(
|
|
330
330
|
code,
|
|
331
|
-
/\{ "\/css\/app\.css", "text\/css; charset=utf-8", asset_css_app_css, asset_css_app_css_len, asset_css_app_css_gz, asset_css_app_css_gz_len, NULL, 0 \},/
|
|
331
|
+
/\{ "\/css\/app\.css", "text\/css; charset=utf-8", asset_css_app_css, asset_css_app_css_len, asset_css_app_css_gz, asset_css_app_css_gz_len, NULL, 0, NULL, 0 \},/
|
|
332
332
|
);
|
|
333
333
|
} finally {
|
|
334
334
|
cleanup(dir);
|