@velox0/cerver 0.4.0 → 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 +6 -4
- 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 +139 -129
- package/runtime/http_parser.c +153 -152
- package/runtime/http_writer.c +136 -126
- package/runtime/mime.c +49 -49
- package/runtime/router.c +99 -98
- package/runtime/server.c +594 -436
- package/runtime/static.c +174 -183
- package/runtime/tests/minunit.c +76 -0
- package/runtime/tests/minunit.h +64 -0
- package/runtime/tests/runtime_tests.c +414 -0
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
|
/* ------------------------------------------------------------------ */
|
|
@@ -18,182 +19,182 @@
|
|
|
18
19
|
/* ------------------------------------------------------------------ */
|
|
19
20
|
|
|
20
21
|
static int hex_val(char c) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if (c >= '0' && c <= '9') return c - '0';
|
|
23
|
+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
24
|
+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
25
|
+
return -1;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
static void url_decode(char
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
if (*src == '+') {
|
|
42
|
-
*dst++ = ' ';
|
|
43
|
-
src++;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
*dst++ = *src++;
|
|
28
|
+
static void url_decode(char* str) {
|
|
29
|
+
char* src = str;
|
|
30
|
+
char* dst = str;
|
|
31
|
+
|
|
32
|
+
while (*src) {
|
|
33
|
+
if (*src == '%' && src[1] && src[2]) {
|
|
34
|
+
int hi = hex_val(src[1]);
|
|
35
|
+
int lo = hex_val(src[2]);
|
|
36
|
+
if (hi >= 0 && lo >= 0) {
|
|
37
|
+
*dst++ = (char)((hi << 4) | lo);
|
|
38
|
+
src += 3;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
47
41
|
}
|
|
48
|
-
*
|
|
42
|
+
if (*src == '+') {
|
|
43
|
+
*dst++ = ' ';
|
|
44
|
+
src++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
*dst++ = *src++;
|
|
48
|
+
}
|
|
49
|
+
*dst = '\0';
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/* ------------------------------------------------------------------ */
|
|
52
53
|
/* Parse query string IN-PLACE: "a=1&b=2" → key-value pairs */
|
|
53
54
|
/* ------------------------------------------------------------------ */
|
|
54
55
|
|
|
55
|
-
static void parse_query_string(char
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
req->query_count++;
|
|
56
|
+
static void parse_query_string(char* qs, cerver_request_t* req) {
|
|
57
|
+
if (!qs || !*qs) return;
|
|
58
|
+
|
|
59
|
+
/* Parse directly on the buffer — no strdup needed */
|
|
60
|
+
char* p = qs;
|
|
61
|
+
|
|
62
|
+
while (*p && req->query_count < CERVER_MAX_QUERY) {
|
|
63
|
+
char* pair_start = p;
|
|
64
|
+
|
|
65
|
+
/* Find end of pair (& or NUL) */
|
|
66
|
+
while (*p && *p != '&') p++;
|
|
67
|
+
if (*p == '&') *p++ = '\0';
|
|
68
|
+
|
|
69
|
+
char* eq = strchr(pair_start, '=');
|
|
70
|
+
if (eq) {
|
|
71
|
+
*eq = '\0';
|
|
72
|
+
req->query[req->query_count].key = pair_start;
|
|
73
|
+
req->query[req->query_count].value = eq + 1;
|
|
74
|
+
url_decode((char*)req->query[req->query_count].key);
|
|
75
|
+
url_decode((char*)req->query[req->query_count].value);
|
|
76
|
+
} else {
|
|
77
|
+
req->query[req->query_count].key = pair_start;
|
|
78
|
+
req->query[req->query_count].value = "";
|
|
80
79
|
}
|
|
80
|
+
req->query_count++;
|
|
81
|
+
}
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
/* ------------------------------------------------------------------ */
|
|
84
85
|
/* Parse the HTTP request IN-PLACE */
|
|
85
86
|
/* ------------------------------------------------------------------ */
|
|
86
87
|
|
|
87
|
-
int cerver_parse_request(const char
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
88
|
+
int cerver_parse_request(const char* raw, size_t len, cerver_request_t* req) {
|
|
89
|
+
if (!raw || len == 0) return -1;
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* We parse in-place: the caller gives us a mutable buffer (cast away
|
|
93
|
+
* const — the caller's read_full_request already owns a mutable buffer).
|
|
94
|
+
* All internal pointers (headers, query, body) reference this buffer.
|
|
95
|
+
* The caller must keep it alive for the request's lifetime.
|
|
96
|
+
*/
|
|
97
|
+
char* buf = (char*)raw;
|
|
98
|
+
buf[len] = '\0'; /* caller ensures buf has capacity for len+1 */
|
|
99
|
+
|
|
100
|
+
/* We no longer allocate _raw_buf — the read buffer IS the raw buffer */
|
|
101
|
+
req->_raw_buf = NULL;
|
|
102
|
+
req->_raw_len = len;
|
|
103
|
+
|
|
104
|
+
/* ---- Request line: METHOD PATH HTTP/1.x ---- */
|
|
105
|
+
char* line_end = strstr(buf, "\r\n");
|
|
106
|
+
if (!line_end) return -1;
|
|
107
|
+
*line_end = '\0';
|
|
108
|
+
|
|
109
|
+
/* Method */
|
|
110
|
+
char* sp1 = strchr(buf, ' ');
|
|
111
|
+
if (!sp1) return -1;
|
|
112
|
+
*sp1 = '\0';
|
|
113
|
+
|
|
114
|
+
size_t method_len = (size_t)(sp1 - buf);
|
|
115
|
+
if (method_len >= sizeof(req->method)) method_len = sizeof(req->method) - 1;
|
|
116
|
+
memcpy(req->method, buf, method_len);
|
|
117
|
+
req->method[method_len] = '\0';
|
|
118
|
+
|
|
119
|
+
/* Path (and maybe query string) */
|
|
120
|
+
char* path_start = sp1 + 1;
|
|
121
|
+
char* sp2 = strchr(path_start, ' ');
|
|
122
|
+
if (sp2) *sp2 = '\0';
|
|
123
|
+
|
|
124
|
+
/* Split path and query string */
|
|
125
|
+
char* qmark = strchr(path_start, '?');
|
|
126
|
+
if (qmark) {
|
|
127
|
+
*qmark = '\0';
|
|
128
|
+
/* Point query_string directly into the buffer */
|
|
129
|
+
char* qs_start = qmark + 1;
|
|
130
|
+
size_t qs_len = strlen(qs_start);
|
|
131
|
+
if (qs_len >= sizeof(req->query_string)) qs_len = sizeof(req->query_string) - 1;
|
|
132
|
+
memcpy(req->query_string, qs_start, qs_len);
|
|
133
|
+
req->query_string[qs_len] = '\0';
|
|
134
|
+
|
|
135
|
+
/* Parse query params in-place from query_string
|
|
136
|
+
* (we copied to req->query_string so params point into req memory) */
|
|
137
|
+
parse_query_string(req->query_string, req);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Decode and store path */
|
|
141
|
+
url_decode(path_start);
|
|
142
|
+
strncpy(req->path, path_start, sizeof(req->path) - 1);
|
|
143
|
+
req->path[sizeof(req->path) - 1] = '\0';
|
|
144
|
+
|
|
145
|
+
/* Normalize trailing slash: "/foo/" → "/foo" (but keep "/" as is) */
|
|
146
|
+
size_t plen = strlen(req->path);
|
|
147
|
+
if (plen > 1 && req->path[plen - 1] == '/') {
|
|
148
|
+
req->path[plen - 1] = '\0';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ---- Headers ---- */
|
|
152
|
+
char* hdr_start = line_end + 2; /* skip \r\n */
|
|
153
|
+
size_t content_length = 0;
|
|
154
|
+
|
|
155
|
+
while (hdr_start < buf + len) {
|
|
156
|
+
char* hdr_end = strstr(hdr_start, "\r\n");
|
|
157
|
+
if (!hdr_end) break;
|
|
158
|
+
|
|
159
|
+
/* Empty line = end of headers */
|
|
160
|
+
if (hdr_end == hdr_start) {
|
|
161
|
+
hdr_start = hdr_end + 2;
|
|
162
|
+
break;
|
|
137
163
|
}
|
|
138
164
|
|
|
139
|
-
|
|
140
|
-
url_decode(path_start);
|
|
141
|
-
strncpy(req->path, path_start, sizeof(req->path) - 1);
|
|
142
|
-
req->path[sizeof(req->path) - 1] = '\0';
|
|
165
|
+
*hdr_end = '\0';
|
|
143
166
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
/* ---- Headers ---- */
|
|
151
|
-
char *hdr_start = line_end + 2; /* skip \r\n */
|
|
152
|
-
size_t content_length = 0;
|
|
167
|
+
if (req->header_count < CERVER_MAX_HEADERS) {
|
|
168
|
+
char* colon = strchr(hdr_start, ':');
|
|
169
|
+
if (colon) {
|
|
170
|
+
*colon = '\0';
|
|
171
|
+
char* val = colon + 1;
|
|
172
|
+
while (*val == ' ') val++;
|
|
153
173
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
174
|
+
req->headers[req->header_count].key = hdr_start;
|
|
175
|
+
req->headers[req->header_count].value = val;
|
|
176
|
+
req->header_count++;
|
|
157
177
|
|
|
158
|
-
/*
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
break;
|
|
178
|
+
/* Track content-length */
|
|
179
|
+
if (strcasecmp(hdr_start, "Content-Length") == 0) {
|
|
180
|
+
content_length = (size_t)atol(val);
|
|
162
181
|
}
|
|
163
|
-
|
|
164
|
-
*hdr_end = '\0';
|
|
165
|
-
|
|
166
|
-
if (req->header_count < CERVER_MAX_HEADERS) {
|
|
167
|
-
char *colon = strchr(hdr_start, ':');
|
|
168
|
-
if (colon) {
|
|
169
|
-
*colon = '\0';
|
|
170
|
-
char *val = colon + 1;
|
|
171
|
-
while (*val == ' ') val++;
|
|
172
|
-
|
|
173
|
-
req->headers[req->header_count].key = hdr_start;
|
|
174
|
-
req->headers[req->header_count].value = val;
|
|
175
|
-
req->header_count++;
|
|
176
|
-
|
|
177
|
-
/* Track content-length */
|
|
178
|
-
if (strcasecmp(hdr_start, "Content-Length") == 0) {
|
|
179
|
-
content_length = (size_t)atol(val);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
hdr_start = hdr_end + 2;
|
|
182
|
+
}
|
|
185
183
|
}
|
|
186
184
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
185
|
+
hdr_start = hdr_end + 2;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* ---- Body (for POST etc.) ---- */
|
|
189
|
+
if (content_length > 0 && hdr_start < buf + len) {
|
|
190
|
+
req->body = hdr_start;
|
|
191
|
+
req->body_len = content_length;
|
|
192
|
+
/* Ensure we don't read past the buffer */
|
|
193
|
+
size_t remaining = (size_t)(buf + len - hdr_start);
|
|
194
|
+
if (req->body_len > remaining) {
|
|
195
|
+
req->body_len = remaining;
|
|
196
196
|
}
|
|
197
|
+
}
|
|
197
198
|
|
|
198
|
-
|
|
199
|
+
return 0;
|
|
199
200
|
}
|
package/runtime/http_writer.c
CHANGED
|
@@ -17,152 +17,162 @@
|
|
|
17
17
|
/* Status text lookup */
|
|
18
18
|
/* ------------------------------------------------------------------ */
|
|
19
19
|
|
|
20
|
-
static const char
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
static const char* status_text(int code) {
|
|
21
|
+
switch (code) {
|
|
22
|
+
case 200:
|
|
23
|
+
return "OK";
|
|
24
|
+
case 201:
|
|
25
|
+
return "Created";
|
|
26
|
+
case 204:
|
|
27
|
+
return "No Content";
|
|
28
|
+
case 301:
|
|
29
|
+
return "Moved Permanently";
|
|
30
|
+
case 302:
|
|
31
|
+
return "Found";
|
|
32
|
+
case 304:
|
|
33
|
+
return "Not Modified";
|
|
34
|
+
case 400:
|
|
35
|
+
return "Bad Request";
|
|
36
|
+
case 401:
|
|
37
|
+
return "Unauthorized";
|
|
38
|
+
case 403:
|
|
39
|
+
return "Forbidden";
|
|
40
|
+
case 404:
|
|
41
|
+
return "Not Found";
|
|
42
|
+
case 405:
|
|
43
|
+
return "Method Not Allowed";
|
|
44
|
+
case 500:
|
|
45
|
+
return "Internal Server Error";
|
|
46
|
+
case 503:
|
|
47
|
+
return "Service Unavailable";
|
|
48
|
+
default:
|
|
49
|
+
return "Unknown";
|
|
50
|
+
}
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
/* ------------------------------------------------------------------ */
|
|
40
54
|
/* Write the full response to fd using writev */
|
|
41
55
|
/* ------------------------------------------------------------------ */
|
|
42
56
|
|
|
43
|
-
int cerver_write_response(int fd, const cerver_response_t
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
int cerver_write_response(int fd, const cerver_response_t* res, int keepalive) {
|
|
58
|
+
/* Build the response header */
|
|
59
|
+
char header[4096];
|
|
60
|
+
int hlen = 0;
|
|
61
|
+
|
|
62
|
+
/* Status line */
|
|
63
|
+
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "HTTP/1.1 %d %s\r\n", res->status,
|
|
64
|
+
status_text(res->status));
|
|
65
|
+
|
|
66
|
+
/* Content-Type */
|
|
67
|
+
if (res->content_type) {
|
|
68
|
+
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "Content-Type: %s\r\n",
|
|
69
|
+
res->content_type);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Content-Length */
|
|
73
|
+
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "Content-Length: %zu\r\n",
|
|
74
|
+
res->body_len);
|
|
75
|
+
|
|
76
|
+
/* Extra headers */
|
|
77
|
+
for (int i = 0; i < res->header_count; i++) {
|
|
78
|
+
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "%s: %s\r\n",
|
|
79
|
+
res->headers[i].key, res->headers[i].value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Connection header — honor keep-alive state */
|
|
83
|
+
if (keepalive && !res->_force_close) {
|
|
84
|
+
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "Connection: keep-alive\r\n");
|
|
85
|
+
} else {
|
|
86
|
+
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "Connection: close\r\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Server header */
|
|
90
|
+
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "Server: cerver\r\n");
|
|
91
|
+
|
|
92
|
+
/* End of headers */
|
|
93
|
+
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "\r\n");
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* Use writev() to send header + body in a single syscall.
|
|
97
|
+
* This avoids Nagle interaction and reduces context switches.
|
|
98
|
+
*/
|
|
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;
|
|
108
|
+
|
|
109
|
+
while (written < total) {
|
|
110
|
+
ssize_t n = writev(fd, iov, 2);
|
|
111
|
+
if (n < 0) return -1;
|
|
112
|
+
written += (size_t)n;
|
|
113
|
+
|
|
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;
|
|
124
|
+
}
|
|
67
125
|
}
|
|
126
|
+
} else {
|
|
127
|
+
/* No body — just send header */
|
|
128
|
+
ssize_t written = write(fd, header, (size_t)hlen);
|
|
129
|
+
if (written < 0) return -1;
|
|
130
|
+
}
|
|
68
131
|
|
|
69
|
-
|
|
70
|
-
if (keepalive && !res->_force_close) {
|
|
71
|
-
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen,
|
|
72
|
-
"Connection: keep-alive\r\n");
|
|
73
|
-
} else {
|
|
74
|
-
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen,
|
|
75
|
-
"Connection: close\r\n");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/* Server header */
|
|
79
|
-
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen,
|
|
80
|
-
"Server: cerver\r\n");
|
|
81
|
-
|
|
82
|
-
/* End of headers */
|
|
83
|
-
hlen += snprintf(header + hlen, sizeof(header) - (size_t)hlen, "\r\n");
|
|
84
|
-
|
|
85
|
-
/*
|
|
86
|
-
* Use writev() to send header + body in a single syscall.
|
|
87
|
-
* This avoids Nagle interaction and reduces context switches.
|
|
88
|
-
*/
|
|
89
|
-
if (res->body && res->body_len > 0) {
|
|
90
|
-
struct iovec iov[2];
|
|
91
|
-
iov[0].iov_base = header;
|
|
92
|
-
iov[0].iov_len = (size_t)hlen;
|
|
93
|
-
iov[1].iov_base = (void *)res->body;
|
|
94
|
-
iov[1].iov_len = res->body_len;
|
|
95
|
-
|
|
96
|
-
size_t total = iov[0].iov_len + iov[1].iov_len;
|
|
97
|
-
size_t written = 0;
|
|
98
|
-
|
|
99
|
-
while (written < total) {
|
|
100
|
-
ssize_t n = writev(fd, iov, 2);
|
|
101
|
-
if (n < 0) return -1;
|
|
102
|
-
written += (size_t)n;
|
|
103
|
-
|
|
104
|
-
/* Adjust iov for partial writes */
|
|
105
|
-
if (written < iov[0].iov_len) {
|
|
106
|
-
iov[0].iov_base = header + written;
|
|
107
|
-
iov[0].iov_len -= (size_t)n;
|
|
108
|
-
} else {
|
|
109
|
-
/* Header fully sent, adjust body iov */
|
|
110
|
-
size_t body_sent = written - (size_t)hlen;
|
|
111
|
-
iov[0].iov_len = 0;
|
|
112
|
-
iov[1].iov_base = (void *)(res->body + body_sent);
|
|
113
|
-
iov[1].iov_len = res->body_len - body_sent;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
/* No body — just send header */
|
|
118
|
-
ssize_t written = write(fd, header, (size_t)hlen);
|
|
119
|
-
if (written < 0) return -1;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return 0;
|
|
132
|
+
return 0;
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
/* ------------------------------------------------------------------ */
|
|
126
136
|
/* Response helper functions */
|
|
127
137
|
/* ------------------------------------------------------------------ */
|
|
128
138
|
|
|
129
|
-
void cerver_res_text(cerver_response_t
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
void cerver_res_text(cerver_response_t* res, int status, const char* text) {
|
|
140
|
+
res->status = status;
|
|
141
|
+
res->content_type = "text/plain; charset=utf-8";
|
|
142
|
+
res->body = text;
|
|
143
|
+
res->body_len = strlen(text);
|
|
144
|
+
res->_body_owned = 0;
|
|
135
145
|
}
|
|
136
146
|
|
|
137
|
-
void cerver_res_json(cerver_response_t
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
void cerver_res_json(cerver_response_t* res, int status, const char* json) {
|
|
148
|
+
res->status = status;
|
|
149
|
+
res->content_type = "application/json; charset=utf-8";
|
|
150
|
+
res->body = json;
|
|
151
|
+
res->body_len = strlen(json);
|
|
152
|
+
res->_body_owned = 0;
|
|
143
153
|
}
|
|
144
154
|
|
|
145
|
-
void cerver_res_html(cerver_response_t
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
void cerver_res_html(cerver_response_t* res, int status, const char* html) {
|
|
156
|
+
res->status = status;
|
|
157
|
+
res->content_type = "text/html; charset=utf-8";
|
|
158
|
+
res->body = html;
|
|
159
|
+
res->body_len = strlen(html);
|
|
160
|
+
res->_body_owned = 0;
|
|
151
161
|
}
|
|
152
162
|
|
|
153
|
-
void cerver_res_file(cerver_response_t
|
|
154
|
-
const unsigned char
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
void cerver_res_file(cerver_response_t* res, int status, const char* mime,
|
|
164
|
+
const unsigned char* data, size_t len) {
|
|
165
|
+
res->status = status;
|
|
166
|
+
res->content_type = mime;
|
|
167
|
+
res->body = (const char*)data;
|
|
168
|
+
res->body_len = len;
|
|
169
|
+
res->_body_owned = 0;
|
|
160
170
|
}
|
|
161
171
|
|
|
162
|
-
void cerver_res_header(cerver_response_t
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
void cerver_res_header(cerver_response_t* res, const char* key, const char* val) {
|
|
173
|
+
if (res->header_count < CERVER_MAX_HEADERS) {
|
|
174
|
+
res->headers[res->header_count].key = key;
|
|
175
|
+
res->headers[res->header_count].value = val;
|
|
176
|
+
res->header_count++;
|
|
177
|
+
}
|
|
168
178
|
}
|