Page MenuHomePhorge

fetch.c
No OneTemporary

Size
10 KB
Referenced Files
None
Subscribers
None
#include <compat.h> // IWYU pragma: keep
#include <uv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlsuv/tlsuv.h>
#include <tlsuv/http.h>
#include <utarray.h>
#include "ant.h"
#include "errors.h"
#include "common.h"
#include "internal.h"
#include "runtime.h"
#include "modules/fetch.h"
#include "modules/json.h"
#include "modules/timer.h"
typedef struct {
char *data;
size_t size;
size_t capacity;
} fetch_buffer_t;
typedef struct fetch_request_s {
ant_t *js;
jsval_t promise;
tlsuv_http_t http_client;
tlsuv_http_req_t *http_req;
fetch_buffer_t response_buffer;
int status_code;
int completed;
int failed;
char *error_msg;
jsval_t headers_obj;
char *method;
char *body;
size_t body_len;
} fetch_request_t;
static UT_array *pending_requests = NULL;
static void free_fetch_request(fetch_request_t *req) {
if (!req) return;
if (req->response_buffer.data) free(req->response_buffer.data);
if (req->error_msg) free(req->error_msg);
if (req->method) free(req->method);
if (req->body) free(req->body);
free(req);
}
static void remove_pending_request(fetch_request_t *req) {
if (!req || !pending_requests) return;
fetch_request_t **p = NULL;
unsigned int i = 0;
while ((p = (fetch_request_t**)utarray_next(pending_requests, p))) {
if (*p == req) { utarray_erase(pending_requests, i, 1); break; }
i++;
}
}
static jsval_t fetch_fail_oom(ant_t *js, jsval_t promise, fetch_request_t *req, bool close_http, const char *msg, size_t len) {
jsval_t err = js_mkstr(js, msg, len);
js_reject_promise(js, promise, err);
if (close_http) tlsuv_http_close(&req->http_client, NULL);
remove_pending_request(req); free_fetch_request(req);
return js_mkundef();
}
static jsval_t response_text(ant_t *js, jsval_t *args, int nargs) {
jsval_t this = js_getthis(js);
jsval_t body = js_get_slot(js, this, SLOT_DATA);
jsval_t promise = js_mkpromise(js);
js_resolve_promise(js, promise, body);
return promise;
}
static jsval_t response_json(ant_t *js, jsval_t *args, int nargs) {
jsval_t this = js_getthis(js);
jsval_t body = js_get_slot(js, this, SLOT_DATA);
jsval_t parsed = js_json_parse(js, &body, 1);
jsval_t promise = js_mkpromise(js);
if (vtype(parsed) == T_ERR) {
js_reject_promise(js, promise, parsed);
} else js_resolve_promise(js, promise, parsed);
return promise;
}
static jsval_t create_response(ant_t *js, int status, const char *body, size_t body_len) {
jsval_t response_obj = js_mkobj(js);
jsval_t body_str = js_mkstr(js, body, body_len);
js_set(js, response_obj, "ok", js_bool(status >= 200 && status < 300));
js_set(js, response_obj, "status", js_mknum(status));
js_set_slot(js, response_obj, SLOT_DATA, body_str);
js_set(js, response_obj, "text", js_mkfun(response_text));
js_set(js, response_obj, "json", js_mkfun(response_json));
return response_obj;
}
static void complete_request(fetch_request_t *req) {
if (req->failed) {
jsval_t err = js_mkstr(req->js, req->error_msg ? req->error_msg : "Unknown error", req->error_msg ? strlen(req->error_msg) : 13);
js_reject_promise(req->js, req->promise, err);
} else {
const char *body = req->response_buffer.data ? req->response_buffer.data : "";
size_t body_len = req->response_buffer.data ? req->response_buffer.size : 0;
jsval_t response = create_response(req->js, req->status_code, body, body_len);
js_resolve_promise(req->js, req->promise, response);
}
remove_pending_request(req);
free_fetch_request(req);
}
static void on_http_close(tlsuv_http_t *client) {
fetch_request_t *req = (fetch_request_t *)client->data;
if (req && req->completed) complete_request(req);
}
static void body_cb(tlsuv_http_req_t *http_req, char *body, ssize_t len) {
fetch_request_t *req = (fetch_request_t *)http_req->data;
if (len == UV_EOF) {
req->completed = 1;
tlsuv_http_close(&req->http_client, on_http_close);
return;
}
if (len < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror((int)len));
req->completed = 1;
tlsuv_http_close(&req->http_client, on_http_close);
return;
}
if (req->response_buffer.size + len > req->response_buffer.capacity) {
size_t new_capacity = req->response_buffer.capacity * 2;
while (new_capacity < req->response_buffer.size + len) new_capacity *= 2;
char *new_data = realloc(req->response_buffer.data, new_capacity);
if (!new_data) {
req->failed = 1;
req->error_msg = strdup("Out of memory");
req->completed = 1;
tlsuv_http_close(&req->http_client, on_http_close);
return;
}
req->response_buffer.data = new_data;
req->response_buffer.capacity = new_capacity;
}
memcpy(req->response_buffer.data + req->response_buffer.size, body, len);
req->response_buffer.size += len;
}
static void resp_cb(tlsuv_http_resp_t *resp, void *data) {
(void)data;
fetch_request_t *req = (fetch_request_t *)resp->req->data;
if (resp->code < 0) {
req->failed = 1;
req->error_msg = strdup(uv_strerror(resp->code));
req->completed = 1;
tlsuv_http_close(&req->http_client, on_http_close);
return;
}
req->status_code = resp->code;
resp->body_cb = body_cb;
}
static jsval_t do_fetch_microtask(ant_t *js, jsval_t *args, int nargs) {
(void)args;
(void)nargs;
jsval_t current_func = js_getcurrentfunc(js);
jsval_t url_val = js_get(js, current_func, "url");
jsval_t options_val = js_get(js, current_func, "options");
jsval_t promise = js_get_slot(js, current_func, SLOT_DATA);
char *url_str = js_getstr(js, url_val, NULL);
if (!url_str) {
jsval_t err = js_mkstr(js, "Invalid URL", 11);
js_reject_promise(js, promise, err);
return js_mkundef();
}
fetch_request_t *req = calloc(1, sizeof(fetch_request_t));
if (!req) {
jsval_t err = js_mkstr(js, "Out of memory", 13);
js_reject_promise(js, promise, err);
return js_mkundef();
}
req->js = js;
req->promise = promise;
req->headers_obj = options_val;
req->response_buffer.capacity = 16384;
req->response_buffer.data = malloc(req->response_buffer.capacity);
req->response_buffer.size = 0;
if (!req->response_buffer.data) {
jsval_t err = js_mkstr(js, "Out of memory", 13);
js_reject_promise(js, promise, err);
free(req);
return js_mkundef();
}
utarray_push_back(pending_requests, &req);
const char *scheme_end = strstr(url_str, "://");
if (!scheme_end) return fetch_fail_oom(js, promise, req, false, "Invalid URL: no scheme", 22);
const char *host_start = scheme_end + 3;
const char *path_start = strchr(host_start, '/');
const char *at_in_host = NULL;
for (const char *p = host_start; p < (path_start ? path_start : host_start + strlen(host_start)); p++) {
if (*p == '@') at_in_host = p;
} if (at_in_host) host_start = at_in_host + 1;
size_t scheme_len = scheme_end - url_str;
size_t host_len = path_start ? (size_t)(path_start - host_start) : strlen(host_start);
const char *path = path_start ? path_start : "/";
if (host_len == 0) return fetch_fail_oom(js, promise, req, false, "Invalid URL: no host", 20);
char *host_url = calloc(1, scheme_len + 3 + host_len + 1);
if (!host_url) return fetch_fail_oom(js, promise, req, false, "Out of memory", 13);
snprintf(host_url, scheme_len + 3 + host_len + 1, "%.*s://%.*s", (int)scheme_len, url_str, (int)host_len, host_start);
int rc = tlsuv_http_init(uv_default_loop(), &req->http_client, host_url); free(host_url);
if (rc != 0) return fetch_fail_oom(js, promise, req, false, "Failed to initialize HTTP client", 33);
req->http_client.data = req;
req->method = NULL;
req->body = NULL;
req->body_len = 0;
if (is_special_object(options_val)) {
jsval_t method_val = js_get(js, options_val, "method");
if (vtype(method_val) == T_STR) {
char *str = js_getstr(js, method_val, NULL);
if (str) {
req->method = strdup(str);
if (!req->method) return fetch_fail_oom(js, promise, req, true, "Out of memory", 13);
}
}
jsval_t body_val = js_get(js, options_val, "body");
if (vtype(body_val) == T_STR) {
size_t len;
char *str = js_getstr(js, body_val, &len);
if (str && len > 0) {
req->body = malloc(len);
if (!req->body) return fetch_fail_oom(js, promise, req, true, "Out of memory", 13);
memcpy(req->body, str, len);
req->body_len = len;
}
}
}
if (!req->method) {
req->method = strdup("GET");
if (!req->method) return fetch_fail_oom(js, promise, req, true, "Out of memory", 13);
}
req->http_req = tlsuv_http_req(&req->http_client, req->method, path, resp_cb, req);
if (!req->http_req) return fetch_fail_oom(js, promise, req, true, "Failed to create HTTP request", 30);
req->http_req->data = req;
char user_agent[256];
snprintf(user_agent, sizeof(user_agent), "ant/%s", ANT_VERSION);
tlsuv_http_req_header(req->http_req, "User-Agent", user_agent);
if (is_special_object(options_val)) {
jsval_t headers_val = js_get(js, options_val, "headers");
if (is_special_object(headers_val)) {
ant_iter_t iter = js_prop_iter_begin(js, headers_val);
const char *key;
size_t key_len;
jsval_t value;
while (js_prop_iter_next(&iter, &key, &key_len, &value)) {
char *value_str = js_getstr(js, value, NULL);
if (value_str) {
char *key_str = strndup(key, key_len);
if (key_str) { tlsuv_http_req_header(req->http_req, key_str, value_str); free(key_str); }
}
}
js_prop_iter_end(&iter);
}
}
if (req->body && req->body_len > 0) {
tlsuv_http_req_data(req->http_req, req->body, req->body_len, body_cb);
}
return js_mkundef();
}
static jsval_t js_fetch(ant_t *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "fetch requires at least 1 argument");
if (vtype(args[0]) != T_STR) return js_mkerr(js, "fetch URL must be a string");
jsval_t url_val = args[0];
jsval_t options_val = nargs > 1 ? args[1] : js_mkundef();
jsval_t promise = js_mkpromise(js);
jsval_t wrapper_obj = js_mkobj(js);
js_set_slot(js, wrapper_obj, SLOT_DATA, promise);
js_set_slot(js, wrapper_obj, SLOT_CFUNC, js_mkfun(do_fetch_microtask));
js_set(js, wrapper_obj, "url", url_val);
js_set(js, wrapper_obj, "options", options_val);
queue_microtask(js, js_obj_to_func(wrapper_obj));
return promise;
}
void init_fetch_module() {
utarray_new(pending_requests, &ut_ptr_icd);
js_set(rt->js, rt->js->global, "fetch", js_mkfun(js_fetch));
}
int has_pending_fetches(void) {
return pending_requests && utarray_len(pending_requests) > 0;
}
void fetch_gc_update_roots(GC_OP_VAL_ARGS) {
if (!pending_requests) return;
unsigned int len = utarray_len(pending_requests);
for (unsigned int i = 0; i < len; i++) {
fetch_request_t **reqp = (fetch_request_t **)utarray_eltptr(pending_requests, i);
if (reqp && *reqp) {
op_val(ctx, &(*reqp)->promise);
op_val(ctx, &(*reqp)->headers_obj);
}
}
}

File Metadata

Mime Type
text/x-c
Expires
Thu, Mar 26, 4:40 PM (2 d)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
511728
Default Alt Text
fetch.c (10 KB)

Event Timeline