@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.
@@ -6,7 +6,7 @@ const IR = require("./types");
6
6
  * Transform a validated AST into an IR route descriptor.
7
7
  *
8
8
  * @param {object} ast - ESTree AST (validated)
9
- * @param {string} urlPath - The route path (e.g. "/art/:key")
9
+ * @param {string} urlPath - The route path (e.g. "/groups/:group_id")
10
10
  * @returns {IRRoute[]} — one IRRoute per exported handler (GET, POST)
11
11
  */
12
12
  function transformFile(ast, urlPath) {
@@ -135,11 +135,7 @@ function transformReturn(node, ctx) {
135
135
  }
136
136
 
137
137
  /* Plain expression return — treat as text */
138
- return IR.IRReturn(
139
- "text",
140
- 200,
141
- transformExpression(arg, ctx)
142
- );
138
+ return IR.IRReturn("text", 200, transformExpression(arg, ctx));
143
139
  }
144
140
 
145
141
  /**
@@ -148,9 +144,13 @@ function transformReturn(node, ctx) {
148
144
  function transformIf(node, ctx) {
149
145
  const condition = transformExpression(node.test, ctx);
150
146
 
151
- const thenBlock = node.consequent.type === "BlockStatement"
152
- ? transformBlock(node.consequent, ctx)
153
- : { variables: [], body: [transformStatement(node.consequent, ctx)].filter(Boolean) };
147
+ const thenBlock =
148
+ node.consequent.type === "BlockStatement"
149
+ ? transformBlock(node.consequent, ctx)
150
+ : {
151
+ variables: [],
152
+ body: [transformStatement(node.consequent, ctx)].filter(Boolean),
153
+ };
154
154
 
155
155
  let elseBody = null;
156
156
  if (node.alternate) {
@@ -208,28 +208,25 @@ function transformExpression(node, ctx) {
208
208
  return IR.IRComparison(
209
209
  node.operator,
210
210
  transformExpression(node.left, ctx),
211
- transformExpression(node.right, ctx)
211
+ transformExpression(node.right, ctx),
212
212
  );
213
213
 
214
214
  case "LogicalExpression":
215
215
  return IR.IRLogical(
216
216
  node.operator,
217
217
  transformExpression(node.left, ctx),
218
- transformExpression(node.right, ctx)
218
+ transformExpression(node.right, ctx),
219
219
  );
220
220
 
221
221
  case "UnaryExpression":
222
- return IR.IRUnary(
223
- node.operator,
224
- transformExpression(node.argument, ctx)
225
- );
222
+ return IR.IRUnary(node.operator, transformExpression(node.argument, ctx));
226
223
 
227
224
  case "ConditionalExpression":
228
225
  /* a ? b : c → IR.IRIf as expression — simplify to if/else for now */
229
226
  return IR.IRComparison(
230
227
  "?:",
231
228
  transformExpression(node.test, ctx),
232
- transformExpression(node.consequent, ctx)
229
+ transformExpression(node.consequent, ctx),
233
230
  );
234
231
 
235
232
  case "MemberExpression":
@@ -274,10 +271,7 @@ function transformMemberExpr(node, ctx) {
274
271
  }
275
272
 
276
273
  /* req.method, req.path */
277
- if (
278
- node.object.type === "Identifier" &&
279
- node.object.name === ctx.reqName
280
- ) {
274
+ if (node.object.type === "Identifier" && node.object.name === ctx.reqName) {
281
275
  const prop = node.property.name || node.property.value;
282
276
  if (prop === "method") return IR.IRIdentifier("req->method");
283
277
  if (prop === "path") return IR.IRIdentifier("req->path");
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@velox0/cerver",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Compile restricted JavaScript server logic into optimized native C binaries",
5
- "main": "lib/index.js",
5
+ "main": "bin/cerver.js",
6
6
  "bin": {
7
7
  "cerver": "bin/cerver.js"
8
8
  },
@@ -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
@@ -6,6 +6,16 @@
6
6
  * the route dispatch interface.
7
7
  */
8
8
 
9
+ /*
10
+ * Feature test macros for POSIX/GNU APIs used by the runtime.
11
+ * Must be defined before any system headers.
12
+ */
13
+ #if defined(__linux__)
14
+ #ifndef _GNU_SOURCE
15
+ #define _GNU_SOURCE
16
+ #endif
17
+ #endif
18
+
9
19
  #ifndef CERVER_H
10
20
  #define CERVER_H
11
21
 
@@ -69,22 +79,22 @@ typedef struct {
69
79
 
70
80
  /* Parsed query parameters */
71
81
  cerver_kv_t query[CERVER_MAX_QUERY];
72
- int query_count;
82
+ int query_count;
73
83
 
74
84
  /* Route parameters (from dynamic segments like :key) */
75
85
  cerver_kv_t params[CERVER_MAX_PARAMS];
76
- int params_count;
86
+ int params_count;
77
87
 
78
88
  /* Request headers */
79
89
  cerver_kv_t headers[CERVER_MAX_HEADERS];
80
- int header_count;
90
+ int header_count;
81
91
 
82
92
  /* Request body (for POST) */
83
93
  const char* body;
84
- size_t body_len;
94
+ size_t body_len;
85
95
 
86
96
  /* Internal: raw buffer ownership (NULL if in-place parsing used) */
87
- char* _raw_buf;
97
+ char* _raw_buf;
88
98
  size_t _raw_len;
89
99
  } cerver_request_t;
90
100
 
@@ -93,22 +103,25 @@ typedef struct {
93
103
  /* ------------------------------------------------------------------ */
94
104
 
95
105
  typedef struct {
96
- int status;
106
+ int status;
97
107
  const char* content_type;
98
108
 
99
109
  /* Response body — can be heap-allocated or static */
100
110
  const char* body;
101
- size_t body_len;
111
+ size_t body_len;
102
112
 
103
113
  /* Extra headers */
104
114
  cerver_kv_t headers[CERVER_MAX_HEADERS];
105
- int header_count;
115
+ int header_count;
106
116
 
107
117
  /* Internal flag: was body malloc'd? */
108
118
  int _body_owned;
109
119
 
110
120
  /* Keep-alive control: set to 1 to force close after response */
111
121
  int _force_close;
122
+
123
+ /* Internal file descriptor for sendfile serving */
124
+ int _file_fd;
112
125
  } cerver_response_t;
113
126
 
114
127
  /* Response helpers — called by generated handler code */
@@ -137,8 +150,8 @@ int cerver_req_wants_close(const cerver_request_t* req);
137
150
  typedef void (*cerver_handler_fn)(cerver_request_t* req, cerver_response_t* res);
138
151
 
139
152
  typedef struct {
140
- const char* method; /* "GET", "POST" */
141
- const char* pattern; /* "/", "/art/:key", "/api/projects" */
153
+ const char* method; /* "GET", "POST" */
154
+ const char* pattern; /* "/", "/groups/:group_id", "/api/items" */
142
155
  cerver_handler_fn handler;
143
156
  } cerver_route_t;
144
157
 
@@ -147,20 +160,20 @@ typedef struct {
147
160
  /* ------------------------------------------------------------------ */
148
161
 
149
162
  typedef struct {
150
- const char* path; /* e.g. "/index.html" */
151
- const char* mime_type; /* e.g. "text/html" */
163
+ const char* path; /* e.g. "/index.html" */
164
+ const char* mime_type; /* e.g. "text/html" */
152
165
  const unsigned char* data;
153
- size_t data_len;
166
+ size_t data_len;
154
167
 
155
168
  /* Pre-compressed variants (NULL if not available) */
156
169
  const unsigned char* data_gz;
157
- size_t data_gz_len;
170
+ size_t data_gz_len;
158
171
  const unsigned char* data_br;
159
- size_t data_br_len;
172
+ size_t data_br_len;
160
173
 
161
174
  /* Pre-computed response header (NULL if not generated) */
162
175
  const char* prebuilt_header;
163
- size_t prebuilt_header_len;
176
+ size_t prebuilt_header_len;
164
177
  } cerver_asset_t;
165
178
 
166
179
  /* ------------------------------------------------------------------ */
@@ -168,16 +181,16 @@ typedef struct {
168
181
  /* ------------------------------------------------------------------ */
169
182
 
170
183
  typedef struct {
171
- char path[CERVER_MAX_PATH];
184
+ char path[CERVER_MAX_PATH];
172
185
  size_t file_size;
173
186
  time_t mtime;
174
187
  time_t cached_at;
175
- int valid;
188
+ int valid;
176
189
  } cerver_stat_entry_t;
177
190
 
178
191
  typedef struct {
179
192
  cerver_stat_entry_t entries[CERVER_STAT_CACHE_SIZE];
180
- pthread_mutex_t lock;
193
+ pthread_mutex_t lock;
181
194
  } cerver_stat_cache_t;
182
195
 
183
196
  /* ------------------------------------------------------------------ */
@@ -193,11 +206,11 @@ typedef cerver_handler_fn (*cerver_dispatch_fn)(cerver_request_t* req);
193
206
  typedef struct cerver_server cerver_server_t;
194
207
 
195
208
  typedef struct {
196
- int id;
197
- int event_fd; /* kqueue or epoll fd */
198
- int listen_fd; /* per-worker on Linux, shared on macOS */
209
+ int id;
210
+ int event_fd; /* kqueue or epoll fd */
211
+ int listen_fd; /* per-worker on Linux, shared on macOS */
199
212
  cerver_server_t* srv;
200
- pthread_t thread;
213
+ pthread_t thread;
201
214
  } cerver_worker_t;
202
215
 
203
216
  /* ------------------------------------------------------------------ */
@@ -205,14 +218,14 @@ typedef struct {
205
218
  /* ------------------------------------------------------------------ */
206
219
 
207
220
  struct cerver_server {
208
- int port;
209
- int sock_fd;
221
+ int port;
222
+ int sock_fd;
210
223
  cerver_route_t* routes;
211
- int route_count;
224
+ int route_count;
212
225
  cerver_asset_t* assets;
213
- int asset_count;
214
- const char* public_dir; /* NULL if embedded mode */
215
- volatile int running;
226
+ int asset_count;
227
+ const char* public_dir; /* NULL if embedded mode */
228
+ volatile int running;
216
229
 
217
230
  /* Generated dispatch override (faster than generic router) */
218
231
  cerver_dispatch_fn dispatch_override;
@@ -221,17 +234,20 @@ struct cerver_server {
221
234
  cerver_stat_cache_t stat_cache;
222
235
 
223
236
  /* Worker pool */
224
- int worker_count;
237
+ int worker_count;
225
238
  cerver_worker_t* workers;
239
+
240
+ /* Route trie for radix/trie-based routing */
241
+ void* route_trie;
226
242
  };
227
243
 
228
244
  /* Server lifecycle */
229
- int cerver_init(cerver_server_t* srv, int port, int threads);
230
- int cerver_add_routes(cerver_server_t* srv, cerver_route_t* routes, int count);
231
- int cerver_set_assets(cerver_server_t* srv, cerver_asset_t* assets, int count);
245
+ int cerver_init(cerver_server_t* srv, int port, int threads);
246
+ int cerver_add_routes(cerver_server_t* srv, cerver_route_t* routes, int count);
247
+ int cerver_set_assets(cerver_server_t* srv, cerver_asset_t* assets, int count);
232
248
  void cerver_set_public_dir(cerver_server_t* srv, const char* dir);
233
249
  void cerver_set_dispatch(cerver_server_t* srv, cerver_dispatch_fn fn);
234
- int cerver_listen(cerver_server_t* srv);
250
+ int cerver_listen(cerver_server_t* srv);
235
251
  void cerver_shutdown(cerver_server_t* srv);
236
252
 
237
253
  /* ------------------------------------------------------------------ */
@@ -250,8 +266,11 @@ int cerver_write_response(int fd, const cerver_response_t* res, int keepalive);
250
266
  /* Router (internal) */
251
267
  /* ------------------------------------------------------------------ */
252
268
 
253
- int cerver_route_match(const cerver_route_t* route, cerver_request_t* req);
269
+ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req);
254
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, cerver_handler_fn handler);
273
+ void cerver_trie_free(void* trie);
255
274
 
256
275
  /* ------------------------------------------------------------------ */
257
276
  /* MIME (internal) */
@@ -270,7 +289,7 @@ int cerver_serve_static(cerver_server_t* srv, cerver_request_t* req, cerver_resp
270
289
  /* ------------------------------------------------------------------ */
271
290
 
272
291
  void cerver_stat_cache_init(cerver_stat_cache_t* cache);
273
- int cerver_stat_cache_lookup(cerver_stat_cache_t* cache, const char* path, size_t* file_size);
292
+ int cerver_stat_cache_lookup(cerver_stat_cache_t* cache, const char* path, size_t* file_size);
274
293
  void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_t file_size,
275
294
  time_t mtime);
276
295
 
@@ -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
  #include <ctype.h>
15
16
 
16
17
  /* ------------------------------------------------------------------ */
@@ -67,13 +68,13 @@ static void parse_query_string(char* qs, cerver_request_t* req) {
67
68
 
68
69
  char* eq = strchr(pair_start, '=');
69
70
  if (eq) {
70
- *eq = '\0';
71
- req->query[req->query_count].key = pair_start;
71
+ *eq = '\0';
72
+ req->query[req->query_count].key = pair_start;
72
73
  req->query[req->query_count].value = eq + 1;
73
74
  url_decode((char*)req->query[req->query_count].key);
74
75
  url_decode((char*)req->query[req->query_count].value);
75
76
  } else {
76
- req->query[req->query_count].key = pair_start;
77
+ req->query[req->query_count].key = pair_start;
77
78
  req->query[req->query_count].value = "";
78
79
  }
79
80
  req->query_count++;
@@ -94,7 +95,7 @@ int cerver_parse_request(const char* raw, size_t len, cerver_request_t* req) {
94
95
  * The caller must keep it alive for the request's lifetime.
95
96
  */
96
97
  char* buf = (char*)raw;
97
- buf[len] = '\0'; /* caller ensures buf has capacity for len+1 */
98
+ buf[len] = '\0'; /* caller ensures buf has capacity for len+1 */
98
99
 
99
100
  /* We no longer allocate _raw_buf — the read buffer IS the raw buffer */
100
101
  req->_raw_buf = NULL;
@@ -117,7 +118,7 @@ int cerver_parse_request(const char* raw, size_t len, cerver_request_t* req) {
117
118
 
118
119
  /* Path (and maybe query string) */
119
120
  char* path_start = sp1 + 1;
120
- char* sp2 = strchr(path_start, ' ');
121
+ char* sp2 = strchr(path_start, ' ');
121
122
  if (sp2) *sp2 = '\0';
122
123
 
123
124
  /* Split path and query string */
@@ -125,8 +126,8 @@ int cerver_parse_request(const char* raw, size_t len, cerver_request_t* req) {
125
126
  if (qmark) {
126
127
  *qmark = '\0';
127
128
  /* Point query_string directly into the buffer */
128
- char* qs_start = qmark + 1;
129
- size_t qs_len = strlen(qs_start);
129
+ char* qs_start = qmark + 1;
130
+ size_t qs_len = strlen(qs_start);
130
131
  if (qs_len >= sizeof(req->query_string)) qs_len = sizeof(req->query_string) - 1;
131
132
  memcpy(req->query_string, qs_start, qs_len);
132
133
  req->query_string[qs_len] = '\0';
@@ -148,7 +149,7 @@ int cerver_parse_request(const char* raw, size_t len, cerver_request_t* req) {
148
149
  }
149
150
 
150
151
  /* ---- Headers ---- */
151
- char* hdr_start = line_end + 2; /* skip \r\n */
152
+ char* hdr_start = line_end + 2; /* skip \r\n */
152
153
  size_t content_length = 0;
153
154
 
154
155
  while (hdr_start < buf + len) {
@@ -166,11 +167,11 @@ int cerver_parse_request(const char* raw, size_t len, cerver_request_t* req) {
166
167
  if (req->header_count < CERVER_MAX_HEADERS) {
167
168
  char* colon = strchr(hdr_start, ':');
168
169
  if (colon) {
169
- *colon = '\0';
170
+ *colon = '\0';
170
171
  char* val = colon + 1;
171
172
  while (*val == ' ') val++;
172
173
 
173
- req->headers[req->header_count].key = hdr_start;
174
+ req->headers[req->header_count].key = hdr_start;
174
175
  req->headers[req->header_count].value = val;
175
176
  req->header_count++;
176
177
 
@@ -186,7 +187,7 @@ int cerver_parse_request(const char* raw, size_t len, cerver_request_t* req) {
186
187
 
187
188
  /* ---- Body (for POST etc.) ---- */
188
189
  if (content_length > 0 && hdr_start < buf + len) {
189
- req->body = hdr_start;
190
+ req->body = hdr_start;
190
191
  req->body_len = content_length;
191
192
  /* Ensure we don't read past the buffer */
192
193
  size_t remaining = (size_t)(buf + len - hdr_start);
@@ -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 */
@@ -57,7 +115,7 @@ static const char* status_text(int code) {
57
115
  int cerver_write_response(int fd, const cerver_response_t* res, int keepalive) {
58
116
  /* Build the response header */
59
117
  char header[4096];
60
- int hlen = 0;
118
+ int hlen = 0;
61
119
 
62
120
  /* Status line */
63
121
  hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "HTTP/1.1 %d %s\r\n", res->status,
@@ -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;
@@ -137,41 +244,41 @@ int cerver_write_response(int fd, const cerver_response_t* res, int keepalive) {
137
244
  /* ------------------------------------------------------------------ */
138
245
 
139
246
  void cerver_res_text(cerver_response_t* res, int status, const char* text) {
140
- res->status = status;
247
+ res->status = status;
141
248
  res->content_type = "text/plain; charset=utf-8";
142
- res->body = text;
143
- res->body_len = strlen(text);
144
- res->_body_owned = 0;
249
+ res->body = text;
250
+ res->body_len = strlen(text);
251
+ res->_body_owned = 0;
145
252
  }
146
253
 
147
254
  void cerver_res_json(cerver_response_t* res, int status, const char* json) {
148
- res->status = status;
255
+ res->status = status;
149
256
  res->content_type = "application/json; charset=utf-8";
150
- res->body = json;
151
- res->body_len = strlen(json);
152
- res->_body_owned = 0;
257
+ res->body = json;
258
+ res->body_len = strlen(json);
259
+ res->_body_owned = 0;
153
260
  }
154
261
 
155
262
  void cerver_res_html(cerver_response_t* res, int status, const char* html) {
156
- res->status = status;
263
+ res->status = status;
157
264
  res->content_type = "text/html; charset=utf-8";
158
- res->body = html;
159
- res->body_len = strlen(html);
160
- res->_body_owned = 0;
265
+ res->body = html;
266
+ res->body_len = strlen(html);
267
+ res->_body_owned = 0;
161
268
  }
162
269
 
163
270
  void cerver_res_file(cerver_response_t* res, int status, const char* mime,
164
271
  const unsigned char* data, size_t len) {
165
- res->status = status;
272
+ res->status = status;
166
273
  res->content_type = mime;
167
- res->body = (const char*)data;
168
- res->body_len = len;
169
- res->_body_owned = 0;
274
+ res->body = (const char*)data;
275
+ res->body_len = len;
276
+ res->_body_owned = 0;
170
277
  }
171
278
 
172
279
  void cerver_res_header(cerver_response_t* res, const char* key, const char* val) {
173
280
  if (res->header_count < CERVER_MAX_HEADERS) {
174
- res->headers[res->header_count].key = key;
281
+ res->headers[res->header_count].key = key;
175
282
  res->headers[res->header_count].value = val;
176
283
  res->header_count++;
177
284
  }
package/runtime/mime.c CHANGED
@@ -5,6 +5,7 @@
5
5
  #include "cerver.h"
6
6
 
7
7
  #include <string.h>
8
+ #include <strings.h>
8
9
  #include <ctype.h>
9
10
 
10
11
  typedef struct {