@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/lib/ir/transform.js
CHANGED
|
@@ -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. "/
|
|
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 =
|
|
152
|
-
|
|
153
|
-
|
|
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.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Compile restricted JavaScript server logic into optimized native C binaries",
|
|
5
|
-
"main": "
|
|
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
|
|
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
|
|
86
|
+
int params_count;
|
|
77
87
|
|
|
78
88
|
/* Request headers */
|
|
79
89
|
cerver_kv_t headers[CERVER_MAX_HEADERS];
|
|
80
|
-
int
|
|
90
|
+
int header_count;
|
|
81
91
|
|
|
82
92
|
/* Request body (for POST) */
|
|
83
93
|
const char* body;
|
|
84
|
-
size_t
|
|
94
|
+
size_t body_len;
|
|
85
95
|
|
|
86
96
|
/* Internal: raw buffer ownership (NULL if in-place parsing used) */
|
|
87
|
-
char*
|
|
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
|
|
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
|
|
111
|
+
size_t body_len;
|
|
102
112
|
|
|
103
113
|
/* Extra headers */
|
|
104
114
|
cerver_kv_t headers[CERVER_MAX_HEADERS];
|
|
105
|
-
int
|
|
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*
|
|
141
|
-
const char*
|
|
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*
|
|
151
|
-
const char*
|
|
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
|
|
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
|
|
170
|
+
size_t data_gz_len;
|
|
158
171
|
const unsigned char* data_br;
|
|
159
|
-
size_t
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
197
|
-
int
|
|
198
|
-
int
|
|
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
|
|
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
|
|
209
|
-
int
|
|
221
|
+
int port;
|
|
222
|
+
int sock_fd;
|
|
210
223
|
cerver_route_t* routes;
|
|
211
|
-
int
|
|
224
|
+
int route_count;
|
|
212
225
|
cerver_asset_t* assets;
|
|
213
|
-
int
|
|
214
|
-
const char*
|
|
215
|
-
volatile int
|
|
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
|
|
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
|
|
230
|
-
int
|
|
231
|
-
int
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/runtime/http_parser.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
|
#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
|
|
71
|
-
req->query[req->query_count].key
|
|
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
|
|
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]
|
|
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
|
|
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*
|
|
129
|
-
size_t qs_len
|
|
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*
|
|
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
|
|
170
|
+
*colon = '\0';
|
|
170
171
|
char* val = colon + 1;
|
|
171
172
|
while (*val == ' ') val++;
|
|
172
173
|
|
|
173
|
-
req->headers[req->header_count].key
|
|
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
|
|
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);
|
package/runtime/http_writer.c
CHANGED
|
@@ -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
|
|
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
|
|
97
|
-
*
|
|
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->
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
/*
|
|
120
|
-
size_t
|
|
121
|
-
iov[0].iov_len
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
|
247
|
+
res->status = status;
|
|
141
248
|
res->content_type = "text/plain; charset=utf-8";
|
|
142
|
-
res->body
|
|
143
|
-
res->body_len
|
|
144
|
-
res->_body_owned
|
|
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
|
|
255
|
+
res->status = status;
|
|
149
256
|
res->content_type = "application/json; charset=utf-8";
|
|
150
|
-
res->body
|
|
151
|
-
res->body_len
|
|
152
|
-
res->_body_owned
|
|
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
|
|
263
|
+
res->status = status;
|
|
157
264
|
res->content_type = "text/html; charset=utf-8";
|
|
158
|
-
res->body
|
|
159
|
-
res->body_len
|
|
160
|
-
res->_body_owned
|
|
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
|
|
272
|
+
res->status = status;
|
|
166
273
|
res->content_type = mime;
|
|
167
|
-
res->body
|
|
168
|
-
res->body_len
|
|
169
|
-
res->_body_owned
|
|
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
|
|
281
|
+
res->headers[res->header_count].key = key;
|
|
175
282
|
res->headers[res->header_count].value = val;
|
|
176
283
|
res->header_count++;
|
|
177
284
|
}
|