@velox0/cerver 0.4.1 → 0.4.2
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/bin/cerver.js +11 -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 +2 -2
- package/runtime/cerver.h +46 -36
- package/runtime/http_parser.c +12 -11
- package/runtime/http_writer.c +24 -24
- package/runtime/mime.c +1 -0
- package/runtime/router.c +2 -1
- package/runtime/server.c +56 -55
- package/runtime/static.c +11 -11
- package/runtime/tests/minunit.c +76 -0
- package/runtime/tests/minunit.h +64 -0
- package/runtime/tests/runtime_tests.c +414 -0
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,16 +103,16 @@ 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;
|
|
@@ -137,8 +147,8 @@ int cerver_req_wants_close(const cerver_request_t* req);
|
|
|
137
147
|
typedef void (*cerver_handler_fn)(cerver_request_t* req, cerver_response_t* res);
|
|
138
148
|
|
|
139
149
|
typedef struct {
|
|
140
|
-
const char*
|
|
141
|
-
const char*
|
|
150
|
+
const char* method; /* "GET", "POST" */
|
|
151
|
+
const char* pattern; /* "/", "/groups/:group_id", "/api/items" */
|
|
142
152
|
cerver_handler_fn handler;
|
|
143
153
|
} cerver_route_t;
|
|
144
154
|
|
|
@@ -147,20 +157,20 @@ typedef struct {
|
|
|
147
157
|
/* ------------------------------------------------------------------ */
|
|
148
158
|
|
|
149
159
|
typedef struct {
|
|
150
|
-
const char*
|
|
151
|
-
const char*
|
|
160
|
+
const char* path; /* e.g. "/index.html" */
|
|
161
|
+
const char* mime_type; /* e.g. "text/html" */
|
|
152
162
|
const unsigned char* data;
|
|
153
|
-
size_t
|
|
163
|
+
size_t data_len;
|
|
154
164
|
|
|
155
165
|
/* Pre-compressed variants (NULL if not available) */
|
|
156
166
|
const unsigned char* data_gz;
|
|
157
|
-
size_t
|
|
167
|
+
size_t data_gz_len;
|
|
158
168
|
const unsigned char* data_br;
|
|
159
|
-
size_t
|
|
169
|
+
size_t data_br_len;
|
|
160
170
|
|
|
161
171
|
/* Pre-computed response header (NULL if not generated) */
|
|
162
172
|
const char* prebuilt_header;
|
|
163
|
-
size_t
|
|
173
|
+
size_t prebuilt_header_len;
|
|
164
174
|
} cerver_asset_t;
|
|
165
175
|
|
|
166
176
|
/* ------------------------------------------------------------------ */
|
|
@@ -168,16 +178,16 @@ typedef struct {
|
|
|
168
178
|
/* ------------------------------------------------------------------ */
|
|
169
179
|
|
|
170
180
|
typedef struct {
|
|
171
|
-
char
|
|
181
|
+
char path[CERVER_MAX_PATH];
|
|
172
182
|
size_t file_size;
|
|
173
183
|
time_t mtime;
|
|
174
184
|
time_t cached_at;
|
|
175
|
-
int
|
|
185
|
+
int valid;
|
|
176
186
|
} cerver_stat_entry_t;
|
|
177
187
|
|
|
178
188
|
typedef struct {
|
|
179
189
|
cerver_stat_entry_t entries[CERVER_STAT_CACHE_SIZE];
|
|
180
|
-
pthread_mutex_t
|
|
190
|
+
pthread_mutex_t lock;
|
|
181
191
|
} cerver_stat_cache_t;
|
|
182
192
|
|
|
183
193
|
/* ------------------------------------------------------------------ */
|
|
@@ -193,11 +203,11 @@ typedef cerver_handler_fn (*cerver_dispatch_fn)(cerver_request_t* req);
|
|
|
193
203
|
typedef struct cerver_server cerver_server_t;
|
|
194
204
|
|
|
195
205
|
typedef struct {
|
|
196
|
-
int
|
|
197
|
-
int
|
|
198
|
-
int
|
|
206
|
+
int id;
|
|
207
|
+
int event_fd; /* kqueue or epoll fd */
|
|
208
|
+
int listen_fd; /* per-worker on Linux, shared on macOS */
|
|
199
209
|
cerver_server_t* srv;
|
|
200
|
-
pthread_t
|
|
210
|
+
pthread_t thread;
|
|
201
211
|
} cerver_worker_t;
|
|
202
212
|
|
|
203
213
|
/* ------------------------------------------------------------------ */
|
|
@@ -205,14 +215,14 @@ typedef struct {
|
|
|
205
215
|
/* ------------------------------------------------------------------ */
|
|
206
216
|
|
|
207
217
|
struct cerver_server {
|
|
208
|
-
int
|
|
209
|
-
int
|
|
218
|
+
int port;
|
|
219
|
+
int sock_fd;
|
|
210
220
|
cerver_route_t* routes;
|
|
211
|
-
int
|
|
221
|
+
int route_count;
|
|
212
222
|
cerver_asset_t* assets;
|
|
213
|
-
int
|
|
214
|
-
const char*
|
|
215
|
-
volatile int
|
|
223
|
+
int asset_count;
|
|
224
|
+
const char* public_dir; /* NULL if embedded mode */
|
|
225
|
+
volatile int running;
|
|
216
226
|
|
|
217
227
|
/* Generated dispatch override (faster than generic router) */
|
|
218
228
|
cerver_dispatch_fn dispatch_override;
|
|
@@ -221,17 +231,17 @@ struct cerver_server {
|
|
|
221
231
|
cerver_stat_cache_t stat_cache;
|
|
222
232
|
|
|
223
233
|
/* Worker pool */
|
|
224
|
-
int
|
|
234
|
+
int worker_count;
|
|
225
235
|
cerver_worker_t* workers;
|
|
226
236
|
};
|
|
227
237
|
|
|
228
238
|
/* Server lifecycle */
|
|
229
|
-
int
|
|
230
|
-
int
|
|
231
|
-
int
|
|
239
|
+
int cerver_init(cerver_server_t* srv, int port, int threads);
|
|
240
|
+
int cerver_add_routes(cerver_server_t* srv, cerver_route_t* routes, int count);
|
|
241
|
+
int cerver_set_assets(cerver_server_t* srv, cerver_asset_t* assets, int count);
|
|
232
242
|
void cerver_set_public_dir(cerver_server_t* srv, const char* dir);
|
|
233
243
|
void cerver_set_dispatch(cerver_server_t* srv, cerver_dispatch_fn fn);
|
|
234
|
-
int
|
|
244
|
+
int cerver_listen(cerver_server_t* srv);
|
|
235
245
|
void cerver_shutdown(cerver_server_t* srv);
|
|
236
246
|
|
|
237
247
|
/* ------------------------------------------------------------------ */
|
|
@@ -250,7 +260,7 @@ int cerver_write_response(int fd, const cerver_response_t* res, int keepalive);
|
|
|
250
260
|
/* Router (internal) */
|
|
251
261
|
/* ------------------------------------------------------------------ */
|
|
252
262
|
|
|
253
|
-
int
|
|
263
|
+
int cerver_route_match(const cerver_route_t* route, cerver_request_t* req);
|
|
254
264
|
cerver_handler_fn cerver_dispatch(cerver_server_t* srv, cerver_request_t* req);
|
|
255
265
|
|
|
256
266
|
/* ------------------------------------------------------------------ */
|
|
@@ -270,7 +280,7 @@ int cerver_serve_static(cerver_server_t* srv, cerver_request_t* req, cerver_resp
|
|
|
270
280
|
/* ------------------------------------------------------------------ */
|
|
271
281
|
|
|
272
282
|
void cerver_stat_cache_init(cerver_stat_cache_t* cache);
|
|
273
|
-
int
|
|
283
|
+
int cerver_stat_cache_lookup(cerver_stat_cache_t* cache, const char* path, size_t* file_size);
|
|
274
284
|
void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_t file_size,
|
|
275
285
|
time_t mtime);
|
|
276
286
|
|
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
|
@@ -57,7 +57,7 @@ static const char* status_text(int code) {
|
|
|
57
57
|
int cerver_write_response(int fd, const cerver_response_t* res, int keepalive) {
|
|
58
58
|
/* Build the response header */
|
|
59
59
|
char header[4096];
|
|
60
|
-
int
|
|
60
|
+
int hlen = 0;
|
|
61
61
|
|
|
62
62
|
/* Status line */
|
|
63
63
|
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "HTTP/1.1 %d %s\r\n", res->status,
|
|
@@ -99,11 +99,11 @@ int cerver_write_response(int fd, const cerver_response_t* res, int keepalive) {
|
|
|
99
99
|
if (res->body && res->body_len > 0) {
|
|
100
100
|
struct iovec iov[2];
|
|
101
101
|
iov[0].iov_base = header;
|
|
102
|
-
iov[0].iov_len
|
|
102
|
+
iov[0].iov_len = (size_t)hlen;
|
|
103
103
|
iov[1].iov_base = (void*)res->body;
|
|
104
|
-
iov[1].iov_len
|
|
104
|
+
iov[1].iov_len = res->body_len;
|
|
105
105
|
|
|
106
|
-
size_t total
|
|
106
|
+
size_t total = iov[0].iov_len + iov[1].iov_len;
|
|
107
107
|
size_t written = 0;
|
|
108
108
|
|
|
109
109
|
while (written < total) {
|
|
@@ -118,9 +118,9 @@ int cerver_write_response(int fd, const cerver_response_t* res, int keepalive) {
|
|
|
118
118
|
} else {
|
|
119
119
|
/* Header fully sent, adjust body iov */
|
|
120
120
|
size_t body_sent = written - (size_t)hlen;
|
|
121
|
-
iov[0].iov_len
|
|
122
|
-
iov[1].iov_base
|
|
123
|
-
iov[1].iov_len
|
|
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;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
} else {
|
|
@@ -137,41 +137,41 @@ int cerver_write_response(int fd, const cerver_response_t* res, int keepalive) {
|
|
|
137
137
|
/* ------------------------------------------------------------------ */
|
|
138
138
|
|
|
139
139
|
void cerver_res_text(cerver_response_t* res, int status, const char* text) {
|
|
140
|
-
res->status
|
|
140
|
+
res->status = status;
|
|
141
141
|
res->content_type = "text/plain; charset=utf-8";
|
|
142
|
-
res->body
|
|
143
|
-
res->body_len
|
|
144
|
-
res->_body_owned
|
|
142
|
+
res->body = text;
|
|
143
|
+
res->body_len = strlen(text);
|
|
144
|
+
res->_body_owned = 0;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
void cerver_res_json(cerver_response_t* res, int status, const char* json) {
|
|
148
|
-
res->status
|
|
148
|
+
res->status = status;
|
|
149
149
|
res->content_type = "application/json; charset=utf-8";
|
|
150
|
-
res->body
|
|
151
|
-
res->body_len
|
|
152
|
-
res->_body_owned
|
|
150
|
+
res->body = json;
|
|
151
|
+
res->body_len = strlen(json);
|
|
152
|
+
res->_body_owned = 0;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
void cerver_res_html(cerver_response_t* res, int status, const char* html) {
|
|
156
|
-
res->status
|
|
156
|
+
res->status = status;
|
|
157
157
|
res->content_type = "text/html; charset=utf-8";
|
|
158
|
-
res->body
|
|
159
|
-
res->body_len
|
|
160
|
-
res->_body_owned
|
|
158
|
+
res->body = html;
|
|
159
|
+
res->body_len = strlen(html);
|
|
160
|
+
res->_body_owned = 0;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
void cerver_res_file(cerver_response_t* res, int status, const char* mime,
|
|
164
164
|
const unsigned char* data, size_t len) {
|
|
165
|
-
res->status
|
|
165
|
+
res->status = status;
|
|
166
166
|
res->content_type = mime;
|
|
167
|
-
res->body
|
|
168
|
-
res->body_len
|
|
169
|
-
res->_body_owned
|
|
167
|
+
res->body = (const char*)data;
|
|
168
|
+
res->body_len = len;
|
|
169
|
+
res->_body_owned = 0;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
void cerver_res_header(cerver_response_t* res, const char* key, const char* val) {
|
|
173
173
|
if (res->header_count < CERVER_MAX_HEADERS) {
|
|
174
|
-
res->headers[res->header_count].key
|
|
174
|
+
res->headers[res->header_count].key = key;
|
|
175
175
|
res->headers[res->header_count].value = val;
|
|
176
176
|
res->header_count++;
|
|
177
177
|
}
|
package/runtime/mime.c
CHANGED
package/runtime/router.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
|
|
|
15
16
|
/* ------------------------------------------------------------------ */
|
|
16
17
|
/* Request accessor helpers */
|
|
@@ -84,7 +85,7 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
const char* pattern = route->pattern;
|
|
87
|
-
const char* path
|
|
88
|
+
const char* path = req->path;
|
|
88
89
|
|
|
89
90
|
/* Fast path: exact match */
|
|
90
91
|
if (strcmp(pattern, path) == 0) {
|