@velox0/cerver 0.5.2 → 0.5.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/Makefile +3 -2
- package/lib/codegen/emit.js +88 -7
- package/lib/codegen/generator.js +1 -0
- package/lib/commands/build.js +28 -1
- package/lib/compiler/compile.js +7 -0
- package/lib/ir/transform.js +38 -0
- package/lib/ir/types.js +14 -0
- package/package.json +1 -1
- package/runtime/cerver.h +20 -0
- package/runtime/fetch.c +163 -0
package/Makefile
CHANGED
|
@@ -7,7 +7,8 @@ RUNTIME_SRCS = \
|
|
|
7
7
|
runtime/router.c \
|
|
8
8
|
runtime/static.c \
|
|
9
9
|
runtime/mime.c \
|
|
10
|
-
runtime/server.c
|
|
10
|
+
runtime/server.c \
|
|
11
|
+
runtime/fetch.c
|
|
11
12
|
|
|
12
13
|
TEST_SRCS = runtime/tests/runtime_tests.c \
|
|
13
14
|
runtime/tests/minunit.c
|
|
@@ -20,7 +21,7 @@ test-runtime: $(TEST_BIN)
|
|
|
20
21
|
|
|
21
22
|
$(TEST_BIN): $(RUNTIME_SRCS) $(TEST_SRCS) runtime/cerver.h
|
|
22
23
|
mkdir -p build
|
|
23
|
-
$(CC) $(CFLAGS) -Iruntime -o $(TEST_BIN) $(RUNTIME_SRCS) $(TEST_SRCS) -pthread
|
|
24
|
+
$(CC) $(CFLAGS) -Iruntime -o $(TEST_BIN) $(RUNTIME_SRCS) $(TEST_SRCS) -pthread -lcurl
|
|
24
25
|
|
|
25
26
|
clean:
|
|
26
27
|
rm -rf build
|
package/lib/codegen/emit.js
CHANGED
|
@@ -143,6 +143,31 @@ function emitExpression(expr) {
|
|
|
143
143
|
return '""';
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
case "Fetch": {
|
|
147
|
+
/*
|
|
148
|
+
* Fetch is a special expression — it returns a call to cerver_fetch().
|
|
149
|
+
* The generated code will be: cerver_fetch(url, method, body, headers)
|
|
150
|
+
* where headers is either NULL or a stack-allocated array.
|
|
151
|
+
*
|
|
152
|
+
* Because the actual headers array setup requires multiple statements,
|
|
153
|
+
* simple inline expressions won't work for headers. We handle headers
|
|
154
|
+
* at the statement level in emitStatement instead.
|
|
155
|
+
* For inline expression contexts (like variable init), we emit without
|
|
156
|
+
* custom headers when headers are present — the statement emitter
|
|
157
|
+
* will handle the full form.
|
|
158
|
+
*/
|
|
159
|
+
const urlCode = emitExpression(expr.url);
|
|
160
|
+
const methodCode = expr.method ? emitExpression(expr.method) : "NULL";
|
|
161
|
+
const bodyCode = expr.body ? emitExpression(expr.body) : "NULL";
|
|
162
|
+
|
|
163
|
+
if (expr.headers && expr.headers.length > 0) {
|
|
164
|
+
/* Flag that this needs statement-level emission */
|
|
165
|
+
return `__fetch_with_headers__`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return `cerver_fetch(${urlCode}, ${methodCode}, ${bodyCode}, NULL)`;
|
|
169
|
+
}
|
|
170
|
+
|
|
146
171
|
default:
|
|
147
172
|
return '""';
|
|
148
173
|
}
|
|
@@ -167,6 +192,45 @@ function isStringExpr(expr) {
|
|
|
167
192
|
* Emit a statement into C code lines.
|
|
168
193
|
* Returns an array of C source lines.
|
|
169
194
|
*/
|
|
195
|
+
|
|
196
|
+
/* Counter for unique fetch variable names */
|
|
197
|
+
let fetchVarCounter = 0;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Emit the headers array and cerver_fetch call for a Fetch expression.
|
|
201
|
+
* Returns { lines: string[], varName: string } where varName holds the result.
|
|
202
|
+
*/
|
|
203
|
+
function emitFetchBlock(fetchExpr, pad, varName) {
|
|
204
|
+
const lines = [];
|
|
205
|
+
const urlCode = emitExpression(fetchExpr.url);
|
|
206
|
+
const methodCode = fetchExpr.method ? emitExpression(fetchExpr.method) : "NULL";
|
|
207
|
+
const bodyCode = fetchExpr.body ? emitExpression(fetchExpr.body) : "NULL";
|
|
208
|
+
|
|
209
|
+
if (fetchExpr.headers && fetchExpr.headers.length > 0) {
|
|
210
|
+
const count = fetchExpr.headers.length;
|
|
211
|
+
/* Build the headers array on the stack */
|
|
212
|
+
for (let i = 0; i < count; i++) {
|
|
213
|
+
const hKey = emitExpression(fetchExpr.headers[i].key);
|
|
214
|
+
const hVal = emitExpression(fetchExpr.headers[i].value);
|
|
215
|
+
/* Format: "Key: Value" */
|
|
216
|
+
lines.push(`${pad}char ${varName}_h${i}[512];`);
|
|
217
|
+
lines.push(`${pad}snprintf(${varName}_h${i}, sizeof(${varName}_h${i}), "%s: %s", ${hKey}, ${hVal});`);
|
|
218
|
+
}
|
|
219
|
+
/* NULL-terminated array */
|
|
220
|
+
lines.push(`${pad}const char *${varName}_hdrs[] = {`);
|
|
221
|
+
for (let i = 0; i < count; i++) {
|
|
222
|
+
lines.push(`${pad} ${varName}_h${i},`);
|
|
223
|
+
}
|
|
224
|
+
lines.push(`${pad} NULL`);
|
|
225
|
+
lines.push(`${pad}};`);
|
|
226
|
+
lines.push(`${pad}char *${varName} = cerver_fetch(${urlCode}, ${methodCode}, ${bodyCode}, ${varName}_hdrs);`);
|
|
227
|
+
} else {
|
|
228
|
+
lines.push(`${pad}char *${varName} = cerver_fetch(${urlCode}, ${methodCode}, ${bodyCode}, NULL);`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return { lines, varName };
|
|
232
|
+
}
|
|
233
|
+
|
|
170
234
|
function emitStatement(stmt, level) {
|
|
171
235
|
if (!stmt) return [];
|
|
172
236
|
const pad = indent(level);
|
|
@@ -175,9 +239,20 @@ function emitStatement(stmt, level) {
|
|
|
175
239
|
switch (stmt.type) {
|
|
176
240
|
case "Return": {
|
|
177
241
|
const fnName = `cerver_res_${stmt.responseType}`;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
242
|
+
|
|
243
|
+
/* Check if the return value involves a fetch() call */
|
|
244
|
+
if (stmt.value && stmt.value.type === "Fetch") {
|
|
245
|
+
const tempName = `_fetch_res_${fetchVarCounter++}`;
|
|
246
|
+
const fetchBlock = emitFetchBlock(stmt.value, pad, tempName);
|
|
247
|
+
lines.push(...fetchBlock.lines);
|
|
248
|
+
lines.push(`${pad}${fnName}(res, ${stmt.status}, ${tempName});`);
|
|
249
|
+
lines.push(`${pad}free(${tempName});`);
|
|
250
|
+
lines.push(`${pad}return;`);
|
|
251
|
+
} else {
|
|
252
|
+
const valueCode = emitExpression(stmt.value);
|
|
253
|
+
lines.push(`${pad}${fnName}(res, ${stmt.status}, ${valueCode});`);
|
|
254
|
+
lines.push(`${pad}return;`);
|
|
255
|
+
}
|
|
181
256
|
break;
|
|
182
257
|
}
|
|
183
258
|
|
|
@@ -217,11 +292,17 @@ function emitStatement(stmt, level) {
|
|
|
217
292
|
}
|
|
218
293
|
|
|
219
294
|
case "Variable": {
|
|
220
|
-
|
|
221
|
-
if (stmt.
|
|
222
|
-
|
|
295
|
+
/* Check if the variable is initialized with a fetch() call */
|
|
296
|
+
if (stmt.initExpr && stmt.initExpr.type === "Fetch") {
|
|
297
|
+
const fetchBlock = emitFetchBlock(stmt.initExpr, pad, stmt.name);
|
|
298
|
+
lines.push(...fetchBlock.lines);
|
|
223
299
|
} else {
|
|
224
|
-
|
|
300
|
+
const val = emitExpression(stmt.initExpr);
|
|
301
|
+
if (stmt.valueType === "number") {
|
|
302
|
+
lines.push(`${pad}int ${stmt.name} = ${val};`);
|
|
303
|
+
} else {
|
|
304
|
+
lines.push(`${pad}const char *${stmt.name} = ${val};`);
|
|
305
|
+
}
|
|
225
306
|
}
|
|
226
307
|
break;
|
|
227
308
|
}
|
package/lib/codegen/generator.js
CHANGED
|
@@ -98,6 +98,7 @@ function generateRoutesC(routes) {
|
|
|
98
98
|
lines.push('#include "cerver.h"');
|
|
99
99
|
lines.push("#include <string.h>");
|
|
100
100
|
lines.push("#include <stdlib.h>");
|
|
101
|
+
lines.push("#include <stdio.h>");
|
|
101
102
|
lines.push("");
|
|
102
103
|
|
|
103
104
|
/* ---- Route table (forward decls + table) ---- */
|
package/lib/commands/build.js
CHANGED
|
@@ -13,6 +13,23 @@ const { discoverAssets } = require("../assets/discover");
|
|
|
13
13
|
const { generateEmbeddedAssets } = require("../assets/embed");
|
|
14
14
|
const { compile: compileC } = require("../compiler/compile");
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Recursively check if an IR node tree contains any Fetch expressions.
|
|
18
|
+
*/
|
|
19
|
+
function irUsesFetch(node) {
|
|
20
|
+
if (!node || typeof node !== "object") return false;
|
|
21
|
+
if (node.type === "Fetch") return true;
|
|
22
|
+
for (const key of Object.keys(node)) {
|
|
23
|
+
const val = node[key];
|
|
24
|
+
if (Array.isArray(val)) {
|
|
25
|
+
if (val.some((item) => irUsesFetch(item))) return true;
|
|
26
|
+
} else if (val && typeof val === "object" && val.type) {
|
|
27
|
+
if (irUsesFetch(val)) return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
16
33
|
/**
|
|
17
34
|
* Full build pipeline: parse → validate → IR → codegen → compile
|
|
18
35
|
*/
|
|
@@ -136,7 +153,17 @@ async function build(opts) {
|
|
|
136
153
|
|
|
137
154
|
/* ---- 7. Compile ---- */
|
|
138
155
|
console.log(" → compiling...");
|
|
139
|
-
|
|
156
|
+
|
|
157
|
+
/* Detect if any route uses fetch() — only link libcurl when needed */
|
|
158
|
+
const usesFetch = allRoutes.some((route) => irUsesFetch(route.handler));
|
|
159
|
+
if (usesFetch) {
|
|
160
|
+
console.log(" fetch() detected — linking libcurl");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const binaryPath = compileC(distDir, runtimeDir, {
|
|
164
|
+
static: opts.static,
|
|
165
|
+
usesFetch,
|
|
166
|
+
});
|
|
140
167
|
|
|
141
168
|
/* ---- Done ---- */
|
|
142
169
|
const elapsed = Date.now() - startTime;
|
package/lib/compiler/compile.js
CHANGED
|
@@ -63,6 +63,8 @@ function compile(distDir, runtimeDir, opts) {
|
|
|
63
63
|
const runtimeSources = fs
|
|
64
64
|
.readdirSync(runtimeDir)
|
|
65
65
|
.filter((f) => f.endsWith(".c"))
|
|
66
|
+
/* Exclude fetch.c when not used — avoids libcurl dependency */
|
|
67
|
+
.filter((f) => f !== "fetch.c" || (opts && opts.usesFetch))
|
|
66
68
|
.map((f) => path.join(runtimeDir, f));
|
|
67
69
|
|
|
68
70
|
/* Build the compiler command with aggressive optimization */
|
|
@@ -79,6 +81,11 @@ function compile(distDir, runtimeDir, opts) {
|
|
|
79
81
|
"-lpthread",
|
|
80
82
|
];
|
|
81
83
|
|
|
84
|
+
/* Link libcurl only when fetch() is used */
|
|
85
|
+
if (opts && opts.usesFetch) {
|
|
86
|
+
args.push("-lcurl");
|
|
87
|
+
}
|
|
88
|
+
|
|
82
89
|
/* Add LTO if supported */
|
|
83
90
|
if (supportsFlag(cc, "-flto")) {
|
|
84
91
|
args.splice(1, 0, "-flto");
|
package/lib/ir/transform.js
CHANGED
|
@@ -295,6 +295,44 @@ function transformCallExpr(node, ctx) {
|
|
|
295
295
|
return IR.IRCall("res", method, args);
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
/* fetch(url) or fetch(url, { method, body, headers }) */
|
|
299
|
+
if (
|
|
300
|
+
node.callee.type === "Identifier" &&
|
|
301
|
+
node.callee.name === "fetch"
|
|
302
|
+
) {
|
|
303
|
+
const urlExpr = node.arguments[0]
|
|
304
|
+
? transformExpression(node.arguments[0], ctx)
|
|
305
|
+
: IR.IRStringLiteral("");
|
|
306
|
+
|
|
307
|
+
let methodExpr = null;
|
|
308
|
+
let bodyExpr = null;
|
|
309
|
+
let headersArr = null;
|
|
310
|
+
|
|
311
|
+
/* Parse options object if present: fetch(url, { method, body, headers }) */
|
|
312
|
+
if (node.arguments[1] && node.arguments[1].type === "ObjectExpression") {
|
|
313
|
+
const opts = node.arguments[1];
|
|
314
|
+
for (const prop of opts.properties) {
|
|
315
|
+
const key = prop.key.name || prop.key.value;
|
|
316
|
+
if (key === "method") {
|
|
317
|
+
methodExpr = transformExpression(prop.value, ctx);
|
|
318
|
+
} else if (key === "body") {
|
|
319
|
+
bodyExpr = transformExpression(prop.value, ctx);
|
|
320
|
+
} else if (key === "headers" && prop.value.type === "ObjectExpression") {
|
|
321
|
+
headersArr = [];
|
|
322
|
+
for (const hProp of prop.value.properties) {
|
|
323
|
+
const hKey = hProp.key.name || hProp.key.value;
|
|
324
|
+
headersArr.push({
|
|
325
|
+
key: IR.IRStringLiteral(hKey),
|
|
326
|
+
value: transformExpression(hProp.value, ctx),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return IR.IRFetch(urlExpr, methodExpr, bodyExpr, headersArr);
|
|
334
|
+
}
|
|
335
|
+
|
|
298
336
|
/* String methods like str.toLowerCase(), includes() etc. — return as-is */
|
|
299
337
|
if (node.callee.type === "MemberExpression") {
|
|
300
338
|
const obj = transformExpression(node.callee.object, ctx);
|
package/lib/ir/types.js
CHANGED
|
@@ -184,6 +184,19 @@ function IRCall(object, method, args) {
|
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* An outbound HTTP fetch call.
|
|
189
|
+
*/
|
|
190
|
+
function IRFetch(url, method, body, headers) {
|
|
191
|
+
return {
|
|
192
|
+
type: "Fetch",
|
|
193
|
+
url, /* IRExpression — the URL */
|
|
194
|
+
method, /* IRExpression | null — "GET", "POST", etc. */
|
|
195
|
+
body, /* IRExpression | null — request body */
|
|
196
|
+
headers, /* Array<{key: IRExpression, value: IRExpression}> | null */
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
187
200
|
module.exports = {
|
|
188
201
|
IRRoute,
|
|
189
202
|
IRHandler,
|
|
@@ -201,4 +214,5 @@ module.exports = {
|
|
|
201
214
|
IRIdentifier,
|
|
202
215
|
IRConcat,
|
|
203
216
|
IRCall,
|
|
217
|
+
IRFetch,
|
|
204
218
|
};
|
package/package.json
CHANGED
package/runtime/cerver.h
CHANGED
|
@@ -273,6 +273,26 @@ void cerver_trie_insert(void* trie, const char* pattern, const char
|
|
|
273
273
|
cerver_handler_fn handler);
|
|
274
274
|
void cerver_trie_free(void* trie);
|
|
275
275
|
|
|
276
|
+
/* ------------------------------------------------------------------ */
|
|
277
|
+
/* Fetch — outbound HTTP client (libcurl) */
|
|
278
|
+
/* ------------------------------------------------------------------ */
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Perform a synchronous HTTP request.
|
|
282
|
+
*
|
|
283
|
+
* @param url Request URL (required).
|
|
284
|
+
* @param method HTTP method: "GET", "POST", "PUT", "DELETE", "PATCH"
|
|
285
|
+
* (NULL defaults to "GET").
|
|
286
|
+
* @param body Request body string (NULL for none).
|
|
287
|
+
* @param headers NULL-terminated array of "Key: Value" header strings,
|
|
288
|
+
* or NULL for no custom headers.
|
|
289
|
+
*
|
|
290
|
+
* @return Heap-allocated response body (caller must free()), or
|
|
291
|
+
* empty heap-allocated string on error.
|
|
292
|
+
*/
|
|
293
|
+
char* cerver_fetch(const char* url, const char* method,
|
|
294
|
+
const char* body, const char** headers);
|
|
295
|
+
|
|
276
296
|
/* ------------------------------------------------------------------ */
|
|
277
297
|
/* MIME (internal) */
|
|
278
298
|
/* ------------------------------------------------------------------ */
|
package/runtime/fetch.c
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* fetch.c — HTTP client for outbound API calls using libcurl.
|
|
3
|
+
*
|
|
4
|
+
* Provides cerver_fetch() which performs synchronous HTTP requests
|
|
5
|
+
* from within generated handler code. Supports GET/POST/PUT/DELETE,
|
|
6
|
+
* custom headers, and request bodies.
|
|
7
|
+
*
|
|
8
|
+
* The returned string is heap-allocated and must be freed by the caller.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include "cerver.h"
|
|
12
|
+
|
|
13
|
+
#include <curl/curl.h>
|
|
14
|
+
#include <stdlib.h>
|
|
15
|
+
#include <string.h>
|
|
16
|
+
#include <stdio.h>
|
|
17
|
+
|
|
18
|
+
/* ------------------------------------------------------------------ */
|
|
19
|
+
/* Internal write callback for curl */
|
|
20
|
+
/* ------------------------------------------------------------------ */
|
|
21
|
+
|
|
22
|
+
typedef struct {
|
|
23
|
+
char* data;
|
|
24
|
+
size_t len;
|
|
25
|
+
size_t cap;
|
|
26
|
+
} cerver_fetch_buf_t;
|
|
27
|
+
|
|
28
|
+
static size_t fetch_write_cb(void* contents, size_t size, size_t nmemb,
|
|
29
|
+
void* userp) {
|
|
30
|
+
size_t realsize = size * nmemb;
|
|
31
|
+
cerver_fetch_buf_t* buf = (cerver_fetch_buf_t*)userp;
|
|
32
|
+
|
|
33
|
+
/* Grow buffer if needed */
|
|
34
|
+
while (buf->len + realsize + 1 > buf->cap) {
|
|
35
|
+
size_t newcap = buf->cap * 2;
|
|
36
|
+
if (newcap < 4096) newcap = 4096;
|
|
37
|
+
char* tmp = realloc(buf->data, newcap);
|
|
38
|
+
if (!tmp) return 0; /* signal error to curl */
|
|
39
|
+
buf->data = tmp;
|
|
40
|
+
buf->cap = newcap;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
memcpy(buf->data + buf->len, contents, realsize);
|
|
44
|
+
buf->len += realsize;
|
|
45
|
+
buf->data[buf->len] = '\0';
|
|
46
|
+
|
|
47
|
+
return realsize;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ------------------------------------------------------------------ */
|
|
51
|
+
/* Global curl init (thread-safe, called once) */
|
|
52
|
+
/* ------------------------------------------------------------------ */
|
|
53
|
+
|
|
54
|
+
static pthread_once_t curl_init_once = PTHREAD_ONCE_INIT;
|
|
55
|
+
|
|
56
|
+
static void curl_global_init_once(void) {
|
|
57
|
+
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ------------------------------------------------------------------ */
|
|
61
|
+
/* Public API */
|
|
62
|
+
/* ------------------------------------------------------------------ */
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* cerver_fetch — Perform a synchronous HTTP request.
|
|
66
|
+
*
|
|
67
|
+
* @param url The URL to request (required).
|
|
68
|
+
* @param method HTTP method: "GET", "POST", "PUT", "DELETE" (NULL = "GET").
|
|
69
|
+
* @param body Request body for POST/PUT (NULL for none).
|
|
70
|
+
* @param headers Array of "Key: Value" header strings (NULL-terminated, or NULL for none).
|
|
71
|
+
*
|
|
72
|
+
* @return Heap-allocated response body string (caller must free), or
|
|
73
|
+
* empty string "" (heap-allocated) on error.
|
|
74
|
+
*/
|
|
75
|
+
char* cerver_fetch(const char* url, const char* method,
|
|
76
|
+
const char* body, const char** headers) {
|
|
77
|
+
if (!url) {
|
|
78
|
+
char* empty = malloc(1);
|
|
79
|
+
if (empty) empty[0] = '\0';
|
|
80
|
+
return empty;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Ensure global curl init */
|
|
84
|
+
pthread_once(&curl_init_once, curl_global_init_once);
|
|
85
|
+
|
|
86
|
+
CURL* curl = curl_easy_init();
|
|
87
|
+
if (!curl) {
|
|
88
|
+
char* empty = malloc(1);
|
|
89
|
+
if (empty) empty[0] = '\0';
|
|
90
|
+
return empty;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Response buffer */
|
|
94
|
+
cerver_fetch_buf_t buf;
|
|
95
|
+
buf.data = malloc(4096);
|
|
96
|
+
buf.len = 0;
|
|
97
|
+
buf.cap = 4096;
|
|
98
|
+
if (!buf.data) {
|
|
99
|
+
curl_easy_cleanup(curl);
|
|
100
|
+
char* empty = malloc(1);
|
|
101
|
+
if (empty) empty[0] = '\0';
|
|
102
|
+
return empty;
|
|
103
|
+
}
|
|
104
|
+
buf.data[0] = '\0';
|
|
105
|
+
|
|
106
|
+
/* Configure request */
|
|
107
|
+
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
108
|
+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_write_cb);
|
|
109
|
+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf);
|
|
110
|
+
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
111
|
+
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
|
112
|
+
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
|
113
|
+
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); /* thread-safe */
|
|
114
|
+
curl_easy_setopt(curl, CURLOPT_USERAGENT, "cerver/1.0");
|
|
115
|
+
|
|
116
|
+
/* Set HTTP method */
|
|
117
|
+
if (method) {
|
|
118
|
+
if (strcmp(method, "POST") == 0) {
|
|
119
|
+
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
|
120
|
+
} else if (strcmp(method, "PUT") == 0) {
|
|
121
|
+
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
|
122
|
+
} else if (strcmp(method, "DELETE") == 0) {
|
|
123
|
+
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
124
|
+
} else if (strcmp(method, "PATCH") == 0) {
|
|
125
|
+
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
|
|
126
|
+
}
|
|
127
|
+
/* GET is the default — no action needed */
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Set request body */
|
|
131
|
+
if (body) {
|
|
132
|
+
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
|
|
133
|
+
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(body));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Set custom headers */
|
|
137
|
+
struct curl_slist* header_list = NULL;
|
|
138
|
+
if (headers) {
|
|
139
|
+
for (int i = 0; headers[i] != NULL; i++) {
|
|
140
|
+
header_list = curl_slist_append(header_list, headers[i]);
|
|
141
|
+
}
|
|
142
|
+
if (header_list) {
|
|
143
|
+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Perform the request */
|
|
148
|
+
CURLcode res = curl_easy_perform(curl);
|
|
149
|
+
|
|
150
|
+
if (res != CURLE_OK) {
|
|
151
|
+
fprintf(stderr, "cerver: fetch error: %s (url: %s)\n",
|
|
152
|
+
curl_easy_strerror(res), url);
|
|
153
|
+
/* Return empty string on error */
|
|
154
|
+
buf.data[0] = '\0';
|
|
155
|
+
buf.len = 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Cleanup */
|
|
159
|
+
if (header_list) curl_slist_free_all(header_list);
|
|
160
|
+
curl_easy_cleanup(curl);
|
|
161
|
+
|
|
162
|
+
return buf.data;
|
|
163
|
+
}
|