@velox0/cerver 0.3.0 → 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.
@@ -18,18 +18,18 @@ jobs:
18
18
 
19
19
  steps:
20
20
  - name: Checkout
21
- uses: actions/checkout@v4
21
+ uses: actions/checkout@v6
22
22
 
23
23
  - name: Setup pnpm
24
- uses: pnpm/action-setup@v4
24
+ uses: pnpm/action-setup@v6
25
25
  with:
26
26
  version: 10
27
27
  run_install: false
28
28
 
29
29
  - name: Setup Node.js
30
- uses: actions/setup-node@v4
30
+ uses: actions/setup-node@v6
31
31
  with:
32
- node-version: 20
32
+ node-version: 24
33
33
  registry-url: https://registry.npmjs.org
34
34
  cache: pnpm
35
35
 
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
+ <div align="center">
2
+ <img src="templates/cerver.png" alt="Cerver Logo" width="120" />
3
+ </div>
4
+
1
5
  # Cerver
2
6
 
3
7
  A lightweight, compile-time web framework that transpiles restricted JavaScript server logic into highly optimized native C HTTP server binaries.
4
8
 
5
- Cerver takes a Next.js-style file-based routing structure (written in a strict subset of JavaScript), parses it, generates equivalent C code, embeds your static assets, and compiles it all into a single, standalone executable (~50KB) that runs with zero Node.js dependency.
9
+ Cerver takes a Next.js-style file-based routing structure (written in a strict subset of JavaScript), parses it, generates equivalent C code, embeds your static assets, and compiles it all into a single, standalone executable that runs with zero Node.js dependency.
6
10
 
7
11
  ## Features
8
12
 
@@ -179,7 +179,8 @@ async function generateEmbeddedAssets(assets, shouldMinify, compression) {
179
179
  ` { "${entry.servePath}", "${entry.mime}", ` +
180
180
  `${entry.name}, ${entry.name}_len, ` +
181
181
  `${entry.gzName}, ${entry.gzLen}, ` +
182
- `${entry.brName}, ${entry.brLen} },`
182
+ `${entry.brName}, ${entry.brLen}, ` +
183
+ `NULL, 0 },`
183
184
  );
184
185
  }
185
186
  lines.push("};");
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+
3
+ const { cString, handlerName } = require("./emit");
4
+
5
+ /**
6
+ * Generate a compile-time dispatch function that avoids the generic router.
7
+ *
8
+ * For static routes: method check → length check → memcmp (known lengths).
9
+ * For dynamic routes: segment-count check → per-segment inline comparisons.
10
+ *
11
+ * Falls through to generic router for unmatched paths.
12
+ *
13
+ * @param {IRRoute[]} routes - All IR routes
14
+ * @returns {string} - C source for cerver_generated_dispatch()
15
+ */
16
+ function generateDispatch(routes) {
17
+ const lines = [];
18
+
19
+ /* Separate static and dynamic routes */
20
+ const staticRoutes = routes.filter((r) => !r.urlPath.includes(":"));
21
+ const dynamicRoutes = routes.filter((r) => r.urlPath.includes(":"));
22
+
23
+ /* Forward declarations for handler functions */
24
+ lines.push("/* ---- Generated Fast Dispatch ---- */");
25
+ lines.push("");
26
+
27
+ /* Group static routes by method */
28
+ const byMethod = {};
29
+ for (const route of staticRoutes) {
30
+ if (!byMethod[route.method]) byMethod[route.method] = [];
31
+ byMethod[route.method].push(route);
32
+ }
33
+
34
+ /* Group dynamic routes by method */
35
+ const dynByMethod = {};
36
+ for (const route of dynamicRoutes) {
37
+ if (!dynByMethod[route.method]) dynByMethod[route.method] = [];
38
+ dynByMethod[route.method].push(route);
39
+ }
40
+
41
+ /* Generate the dispatch function */
42
+ lines.push(
43
+ "static cerver_handler_fn cerver_generated_dispatch(cerver_request_t *req) {"
44
+ );
45
+ lines.push(" const char *path = req->path;");
46
+ lines.push(" size_t path_len = 0;");
47
+ lines.push(" { const char *p = path; while (*p) { path_len++; p++; } }");
48
+ lines.push("");
49
+
50
+ const allMethods = new Set([
51
+ ...Object.keys(byMethod),
52
+ ...Object.keys(dynByMethod),
53
+ ]);
54
+
55
+ let firstMethod = true;
56
+ for (const method of allMethods) {
57
+ const methodStatic = byMethod[method] || [];
58
+ const methodDynamic = dynByMethod[method] || [];
59
+
60
+ /* Method check */
61
+ const methodLen = method.length;
62
+ lines.push(
63
+ ` ${firstMethod ? "" : "} else "}if (req->method[0] == '${method[0]}' && memcmp(req->method, ${cString(method)}, ${methodLen + 1}) == 0) {`
64
+ );
65
+ firstMethod = false;
66
+
67
+ /* ---- Static routes: group by path length for fast rejection ---- */
68
+ if (methodStatic.length > 0) {
69
+ /* Group by length */
70
+ const byLength = {};
71
+ for (const route of methodStatic) {
72
+ const len = route.urlPath.length;
73
+ if (!byLength[len]) byLength[len] = [];
74
+ byLength[len].push(route);
75
+ }
76
+
77
+ lines.push(" /* Static routes */");
78
+
79
+ const lengths = Object.keys(byLength)
80
+ .map(Number)
81
+ .sort((a, b) => a - b);
82
+
83
+ for (const len of lengths) {
84
+ const routesAtLen = byLength[len];
85
+
86
+ lines.push(` if (path_len == ${len}) {`);
87
+
88
+ for (const route of routesAtLen) {
89
+ const name = handlerName(route.method, route.urlPath);
90
+ if (route.urlPath === "/") {
91
+ lines.push(
92
+ ` if (path[0] == '/' && path[1] == '\\0') return ${name};`
93
+ );
94
+ } else {
95
+ lines.push(
96
+ ` if (memcmp(path, ${cString(route.urlPath)}, ${len}) == 0) return ${name};`
97
+ );
98
+ }
99
+ }
100
+
101
+ lines.push(" }");
102
+ }
103
+ }
104
+
105
+ /* ---- Dynamic routes: inline segment matching ---- */
106
+ if (methodDynamic.length > 0) {
107
+ lines.push("");
108
+ lines.push(" /* Dynamic routes */");
109
+
110
+ for (const route of methodDynamic) {
111
+ const segments = route.urlPath.split("/").filter(Boolean);
112
+ const name = handlerName(route.method, route.urlPath);
113
+
114
+ lines.push(" {");
115
+ lines.push(` /* ${route.urlPath} */`);
116
+ lines.push(` const char *p = path;`);
117
+ lines.push(` if (*p == '/') p++;`);
118
+ lines.push(` int match = 1;`);
119
+
120
+ for (let i = 0; i < segments.length; i++) {
121
+ const seg = segments[i];
122
+ const isLast = i === segments.length - 1;
123
+
124
+ if (seg.startsWith(":")) {
125
+ const paramName = seg.slice(1);
126
+ /* Dynamic segment: find the segment boundaries */
127
+ lines.push(` const char *seg${i}_start = p;`);
128
+ lines.push(` while (*p && *p != '/') p++;`);
129
+ lines.push(` size_t seg${i}_len = (size_t)(p - seg${i}_start);`);
130
+ lines.push(` if (seg${i}_len == 0) match = 0;`);
131
+
132
+ if (!isLast) {
133
+ lines.push(` if (match && *p == '/') p++; else if (!${isLast}) match = 0;`);
134
+ }
135
+ } else {
136
+ /* Static segment: compare directly */
137
+ const segLen = seg.length;
138
+ lines.push(
139
+ ` if (match && memcmp(p, ${cString(seg)}, ${segLen}) == 0 && (p[${segLen}] == '/' || p[${segLen}] == '\\0')) {`
140
+ );
141
+ lines.push(` p += ${segLen};`);
142
+ lines.push(` if (*p == '/') p++;`);
143
+ lines.push(` } else { match = 0; }`);
144
+ }
145
+ }
146
+
147
+ /* Ensure path is fully consumed */
148
+ lines.push(` if (match && *p != '\\0') match = 0;`);
149
+
150
+ /* Extract params on match */
151
+ lines.push(` if (match) {`);
152
+
153
+ /* Re-iterate to set params */
154
+ let paramIdx = 0;
155
+ for (let i = 0; i < segments.length; i++) {
156
+ const seg = segments[i];
157
+ if (seg.startsWith(":")) {
158
+ const paramName = seg.slice(1);
159
+ lines.push(
160
+ ` req->params[req->params_count].key = ${cString(paramName)};`
161
+ );
162
+ lines.push(
163
+ ` req->params[req->params_count].value = seg${i}_start;`
164
+ );
165
+ lines.push(` req->params_count++;`);
166
+ paramIdx++;
167
+ }
168
+ }
169
+
170
+ lines.push(` return ${name};`);
171
+ lines.push(` }`);
172
+ lines.push(` }`);
173
+ }
174
+ }
175
+ }
176
+
177
+ if (!firstMethod) {
178
+ lines.push(" }");
179
+ }
180
+
181
+ lines.push("");
182
+ lines.push(" return NULL; /* No match — fall through to generic router */");
183
+ lines.push("}");
184
+ lines.push("");
185
+
186
+ return lines.join("\n");
187
+ }
188
+
189
+ module.exports = { generateDispatch };
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { handlerName, emitStatement } = require("./emit");
4
4
  const { generateRouteTable } = require("./route_table");
5
+ const { generateDispatch } = require("./dispatch_gen");
5
6
 
6
7
  /**
7
8
  * Generate the complete C server source file from IR routes.
@@ -70,6 +71,11 @@ function generateServer(routes, config, assetsCode) {
70
71
  lines.push("");
71
72
  }
72
73
 
74
+ /* ---- Generated fast dispatch ---- */
75
+ lines.push("/* ---- Generated Fast Dispatch (compile-time optimized) ---- */");
76
+ lines.push("");
77
+ lines.push(generateDispatch(routes));
78
+
73
79
  /* ---- Main function ---- */
74
80
  lines.push("/* ---- Entry Point ---- */");
75
81
  lines.push("");
@@ -81,7 +87,7 @@ function generateServer(routes, config, assetsCode) {
81
87
  lines.push("");
82
88
  lines.push(" /* Allow PORT env override */");
83
89
  lines.push(' const char *env_port = getenv("CERVER_PORT");');
84
- lines.push(" if (!env_port) env_port = getenv(\"PORT\");");
90
+ lines.push(' if (!env_port) env_port = getenv("PORT");');
85
91
  lines.push(" if (env_port) port = atoi(env_port);");
86
92
  lines.push("");
87
93
  lines.push(" cerver_server_t srv;");
@@ -92,6 +98,11 @@ function generateServer(routes, config, assetsCode) {
92
98
  );
93
99
  lines.push("");
94
100
 
101
+ /* Wire up generated dispatch */
102
+ lines.push(" /* Use generated compile-time dispatch for fast routing */");
103
+ lines.push(" cerver_set_dispatch(&srv, cerver_generated_dispatch);");
104
+ lines.push("");
105
+
95
106
  if (assetsCode) {
96
107
  lines.push(
97
108
  " cerver_set_assets(&srv, cerver_embedded_assets, cerver_embedded_asset_count);"
@@ -53,15 +53,103 @@ function newProject(name) {
53
53
  <meta charset="utf-8">
54
54
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
55
55
  <title>${name}</title>
56
+ <link rel="icon" href="/favicon.ico" type="image/x-icon">
57
+ <style>
58
+ :root {
59
+ --bg-color: #0f172a;
60
+ --text-color: #f8fafc;
61
+ --accent-color: #38bdf8;
62
+ --card-bg: rgba(30, 41, 59, 0.7);
63
+ }
64
+ body {
65
+ margin: 0;
66
+ padding: 0;
67
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
68
+ background-color: var(--bg-color);
69
+ color: var(--text-color);
70
+ display: flex;
71
+ flex-direction: column;
72
+ align-items: center;
73
+ justify-content: center;
74
+ min-height: 100vh;
75
+ background: radial-gradient(circle at top right, #1e293b, #0f172a);
76
+ }
77
+ .container {
78
+ background: var(--card-bg);
79
+ backdrop-filter: blur(12px);
80
+ -webkit-backdrop-filter: blur(12px);
81
+ border: 1px solid rgba(255, 255, 255, 0.1);
82
+ padding: 3rem;
83
+ border-radius: 16px;
84
+ text-align: center;
85
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
86
+ max-width: 500px;
87
+ width: 90%;
88
+ animation: fadeUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
89
+ opacity: 0;
90
+ transform: translateY(20px);
91
+ }
92
+ @keyframes fadeUp {
93
+ to { opacity: 1; transform: translateY(0); }
94
+ }
95
+ .logo {
96
+ width: 120px;
97
+ height: 120px;
98
+ margin-bottom: 1.5rem;
99
+ border-radius: 24%;
100
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
101
+ transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
102
+ }
103
+ .logo:hover {
104
+ transform: scale(1.08) rotate(-3deg);
105
+ }
106
+ h1 {
107
+ margin: 0 0 1rem 0;
108
+ font-size: 2.5rem;
109
+ font-weight: 700;
110
+ letter-spacing: -0.025em;
111
+ }
112
+ p {
113
+ margin: 0;
114
+ color: #94a3b8;
115
+ font-size: 1.125rem;
116
+ line-height: 1.6;
117
+ }
118
+ .badge {
119
+ display: inline-block;
120
+ margin-top: 2rem;
121
+ padding: 0.5rem 1rem;
122
+ background: rgba(56, 189, 248, 0.1);
123
+ color: var(--accent-color);
124
+ border-radius: 9999px;
125
+ font-size: 0.875rem;
126
+ font-weight: 600;
127
+ border: 1px solid rgba(56, 189, 248, 0.2);
128
+ }
129
+ </style>
56
130
  </head>
57
131
  <body>
58
- <h1>${name}</h1>
59
- <p>Served by cerver.</p>
132
+ <div class="container">
133
+ <img src="/cerver.png" alt="cerver logo" class="logo">
134
+ <h1>${name}</h1>
135
+ <p>Your ultra-fast, native web application is running.</p>
136
+ <div class="badge">Powered by cerver</div>
137
+ </div>
60
138
  </body>
61
139
  </html>
62
140
  `
63
141
  );
64
142
 
143
+ // Copy standard static assets
144
+ fs.copyFileSync(
145
+ path.join(templatesDir, "cerver.png"),
146
+ path.join(projectDir, "public", "cerver.png")
147
+ );
148
+ fs.copyFileSync(
149
+ path.join(templatesDir, "favicon.ico"),
150
+ path.join(projectDir, "public", "favicon.ico")
151
+ );
152
+
65
153
  // package.json
66
154
  fs.writeFileSync(
67
155
  path.join(projectDir, "package.json"),
@@ -83,6 +171,8 @@ function newProject(name) {
83
171
  console.log(" Created:");
84
172
  console.log(" app/routes/index.js");
85
173
  console.log(" public/index.html");
174
+ console.log(" public/cerver.png");
175
+ console.log(" public/favicon.ico");
86
176
  console.log(" cerver.config.js");
87
177
  console.log(" package.json");
88
178
  console.log("");
@@ -22,6 +22,20 @@ function detectCompiler() {
22
22
  );
23
23
  }
24
24
 
25
+ /**
26
+ * Check if a compiler supports a given flag.
27
+ */
28
+ function supportsFlag(cc, flag) {
29
+ try {
30
+ execSync(`echo 'int main(){}' | ${cc} ${flag} -x c -o /dev/null - 2>/dev/null`, {
31
+ stdio: "ignore",
32
+ });
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
25
39
  /**
26
40
  * Compile the generated C source into a binary.
27
41
  *
@@ -46,9 +60,9 @@ function compile(distDir, runtimeDir, opts) {
46
60
  .filter((f) => f.endsWith(".c"))
47
61
  .map((f) => path.join(runtimeDir, f));
48
62
 
49
- /* Build the compiler command */
63
+ /* Build the compiler command with aggressive optimization */
50
64
  const args = [
51
- "-O2",
65
+ "-O3",
52
66
  "-Wall",
53
67
  "-Wextra",
54
68
  "-Wno-unused-parameter",
@@ -60,10 +74,23 @@ function compile(distDir, runtimeDir, opts) {
60
74
  "-lpthread",
61
75
  ];
62
76
 
77
+ /* Add LTO if supported */
78
+ if (supportsFlag(cc, "-flto")) {
79
+ args.splice(1, 0, "-flto");
80
+ }
81
+
82
+ /* Add march=native if supported (for SIMD/hardware-specific opts) */
83
+ if (supportsFlag(cc, "-march=native")) {
84
+ args.push("-march=native");
85
+ }
86
+
63
87
  if (opts && opts.static) {
64
88
  args.push("-static");
65
89
  }
66
90
 
91
+ /* On macOS, we need to define _GNU_SOURCE for some functions */
92
+ args.push("-D_GNU_SOURCE");
93
+
67
94
  console.log(` compiling with ${cc}...`);
68
95
  console.log(` ${cc} ${args.join(" ")}`);
69
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velox0/cerver",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Compile restricted JavaScript server logic into optimized native C binaries",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
package/runtime/cerver.h CHANGED
@@ -26,8 +26,21 @@
26
26
  #define CERVER_READ_BUF_MAX (1 << 20) /* 1 MB hard limit */
27
27
  #define CERVER_MAX_ROUTES 256
28
28
 
29
+ /* Keep-alive settings */
30
+ #define CERVER_KEEPALIVE_MAX 10000 /* max requests per connection */
31
+ #define CERVER_KEEPALIVE_TIMEOUT 5 /* seconds idle between requests */
32
+
33
+ /* Event loop tuning */
34
+ #define CERVER_MAX_EVENTS 256
35
+ #define CERVER_LISTEN_BACKLOG 4096
36
+
37
+ /* Worker architecture */
29
38
  #define CERVER_THREAD_POOL_DEFAULT 4
30
- #define CERVER_TASK_QUEUE_SIZE 256
39
+ #define CERVER_TASK_QUEUE_SIZE 1024
40
+
41
+ /* Stat cache for filesystem serving */
42
+ #define CERVER_STAT_CACHE_SIZE 256
43
+ #define CERVER_STAT_CACHE_TTL 60 /* seconds */
31
44
 
32
45
  /* ------------------------------------------------------------------ */
33
46
  /* Key-value pair (used for headers, query params, route params) */
@@ -68,7 +81,7 @@ typedef struct {
68
81
  const char *body;
69
82
  size_t body_len;
70
83
 
71
- /* Internal: raw buffer ownership */
84
+ /* Internal: raw buffer ownership (NULL if in-place parsing used) */
72
85
  char *_raw_buf;
73
86
  size_t _raw_len;
74
87
  } cerver_request_t;
@@ -91,6 +104,9 @@ typedef struct {
91
104
 
92
105
  /* Internal flag: was body malloc'd? */
93
106
  int _body_owned;
107
+
108
+ /* Keep-alive control: set to 1 to force close after response */
109
+ int _force_close;
94
110
  } cerver_response_t;
95
111
 
96
112
  /* Response helpers — called by generated handler code */
@@ -109,6 +125,9 @@ const char *cerver_req_param(const cerver_request_t *req, const char *key);
109
125
  const char *cerver_req_query(const cerver_request_t *req, const char *key);
110
126
  const char *cerver_req_header(const cerver_request_t *req, const char *key);
111
127
 
128
+ /* Check if client wants to close after this request */
129
+ int cerver_req_wants_close(const cerver_request_t *req);
130
+
112
131
  /* ------------------------------------------------------------------ */
113
132
  /* Route definition */
114
133
  /* ------------------------------------------------------------------ */
@@ -136,13 +155,54 @@ typedef struct {
136
155
  size_t data_gz_len;
137
156
  const unsigned char *data_br;
138
157
  size_t data_br_len;
158
+
159
+ /* Pre-computed response header (NULL if not generated) */
160
+ const char *prebuilt_header;
161
+ size_t prebuilt_header_len;
139
162
  } cerver_asset_t;
140
163
 
141
164
  /* ------------------------------------------------------------------ */
142
- /* Server */
165
+ /* Stat cache for filesystem serving */
166
+ /* ------------------------------------------------------------------ */
167
+
168
+ typedef struct {
169
+ char path[CERVER_MAX_PATH];
170
+ size_t file_size;
171
+ time_t mtime;
172
+ time_t cached_at;
173
+ int valid;
174
+ } cerver_stat_entry_t;
175
+
176
+ typedef struct {
177
+ cerver_stat_entry_t entries[CERVER_STAT_CACHE_SIZE];
178
+ pthread_mutex_t lock;
179
+ } cerver_stat_cache_t;
180
+
181
+ /* ------------------------------------------------------------------ */
182
+ /* Generated dispatch (compile-time route optimization) */
183
+ /* ------------------------------------------------------------------ */
184
+
185
+ typedef cerver_handler_fn (*cerver_dispatch_fn)(cerver_request_t *req);
186
+
143
187
  /* ------------------------------------------------------------------ */
188
+ /* Worker state (per-core event loop) */
189
+ /* ------------------------------------------------------------------ */
190
+
191
+ typedef struct cerver_server cerver_server_t;
144
192
 
145
193
  typedef struct {
194
+ int id;
195
+ int event_fd; /* kqueue or epoll fd */
196
+ int listen_fd; /* per-worker on Linux, shared on macOS */
197
+ cerver_server_t *srv;
198
+ pthread_t thread;
199
+ } cerver_worker_t;
200
+
201
+ /* ------------------------------------------------------------------ */
202
+ /* Server */
203
+ /* ------------------------------------------------------------------ */
204
+
205
+ struct cerver_server {
146
206
  int port;
147
207
  int sock_fd;
148
208
  cerver_route_t *routes;
@@ -152,22 +212,23 @@ typedef struct {
152
212
  const char *public_dir; /* NULL if embedded mode */
153
213
  volatile int running;
154
214
 
155
- /* Thread pool */
156
- int thread_count;
157
- pthread_t *threads;
158
- int task_queue[CERVER_TASK_QUEUE_SIZE];
159
- int tq_head;
160
- int tq_tail;
161
- int tq_count;
162
- pthread_mutex_t tq_mutex;
163
- pthread_cond_t tq_cond;
164
- } cerver_server_t;
215
+ /* Generated dispatch override (faster than generic router) */
216
+ cerver_dispatch_fn dispatch_override;
217
+
218
+ /* Stat cache for filesystem serving */
219
+ cerver_stat_cache_t stat_cache;
220
+
221
+ /* Worker pool */
222
+ int worker_count;
223
+ cerver_worker_t *workers;
224
+ };
165
225
 
166
226
  /* Server lifecycle */
167
227
  int cerver_init(cerver_server_t *srv, int port, int threads);
168
228
  int cerver_add_routes(cerver_server_t *srv, cerver_route_t *routes, int count);
169
229
  int cerver_set_assets(cerver_server_t *srv, cerver_asset_t *assets, int count);
170
230
  void cerver_set_public_dir(cerver_server_t *srv, const char *dir);
231
+ void cerver_set_dispatch(cerver_server_t *srv, cerver_dispatch_fn fn);
171
232
  int cerver_listen(cerver_server_t *srv);
172
233
  void cerver_shutdown(cerver_server_t *srv);
173
234
 
@@ -181,7 +242,7 @@ int cerver_parse_request(const char *raw, size_t len, cerver_request_t *req);
181
242
  /* HTTP writer (internal) */
182
243
  /* ------------------------------------------------------------------ */
183
244
 
184
- int cerver_write_response(int fd, const cerver_response_t *res);
245
+ int cerver_write_response(int fd, const cerver_response_t *res, int keepalive);
185
246
 
186
247
  /* ------------------------------------------------------------------ */
187
248
  /* Router (internal) */
@@ -203,4 +264,14 @@ const char *cerver_mime_from_path(const char *path);
203
264
  int cerver_serve_static(cerver_server_t *srv, cerver_request_t *req,
204
265
  cerver_response_t *res);
205
266
 
267
+ /* ------------------------------------------------------------------ */
268
+ /* Stat cache (internal) */
269
+ /* ------------------------------------------------------------------ */
270
+
271
+ void cerver_stat_cache_init(cerver_stat_cache_t *cache);
272
+ int cerver_stat_cache_lookup(cerver_stat_cache_t *cache, const char *path,
273
+ size_t *file_size);
274
+ void cerver_stat_cache_store(cerver_stat_cache_t *cache, const char *path,
275
+ size_t file_size, time_t mtime);
276
+
206
277
  #endif /* CERVER_H */