Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022#include <compat.h> // IWYU pragma: keep
#include <stdbool.h>#include <stdint.h>#include <stdlib.h>#include <string.h>#include <strings.h>#include <uv.h>
#include "ant.h"#include "list.h"#include "cfunc.h"#include "ptr.h"#include "common.h"#include "errors.h"#include "inspector.h"#include "internal.h"#include "esm/remote.h"#include "gc/modules.h"#include "gc/roots.h"#include "modules/abort.h"#include "modules/blob.h"#include "modules/buffer.h"#include "modules/fetch.h"#include "modules/headers.h"#include "modules/http.h"#include "modules/request.h"#include "modules/response.h"#include "modules/url.h"#include "streams/readable.h"
struct fetch_request_s { fetch_request_t *next; fetch_request_t **pprev; ant_t *js; ant_value_t promise; ant_value_t request_obj; ant_value_t response_obj; ant_value_t abort_listener; ant_value_t upload_reader; ant_value_t upload_read_promise; ant_http_request_t *http_req; int refs; int redirect_count; bool settled; bool aborted; bool restart_pending; bool response_started; uint64_t network_request_id; size_t network_encoded_length;};
static constexpr int FETCH_MAX_REDIRECTS = 20;static constexpr uint32_t FETCH_REQUEST_NATIVE_TAG = Ant_NativeTag("FREQ");
static void destroy_fetch_request(fetch_request_t *req) { ant_value_t signal = 0;
if (!req) return; signal = request_get_signal(req->js, req->request_obj);
if (abort_signal_is_signal(signal) && is_callable(req->abort_listener)) abort_signal_remove_listener(req->js, signal, req->abort_listener);
Ant_List_Remove(req); free(req);}
static void fetch_request_release(fetch_request_t *req) { if (!req) return; if (--req->refs == 0) destroy_fetch_request(req);}
static ant_value_t fetch_type_error(ant_t *js, const char *message) { return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "fetch failed");}
static bool fetch_is_redirect_status(int status) { return status == 301 || status == 302 || status == 303 || status == 307 || status == 308;}
static void fetch_cancel_request_body(fetch_request_t *req, ant_value_t reason) { request_data_t *data = request_get_data(req->request_obj); ant_value_t stream = js_get_slot(req->request_obj, SLOT_REQUEST_BODY_STREAM);
if (!data || !data->body_is_stream || !rs_is_stream(stream)) return; readable_stream_cancel(req->js, stream, reason);}
static void fetch_error_response_body(fetch_request_t *req, ant_value_t reason) { reason = Ant_Error_ConsumeMarker(req->js, reason); ant_value_t stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); if (rs_is_stream(stream)) readable_stream_error(req->js, stream, reason);}
static void fetch_reject(fetch_request_t *req, ant_value_t reason) { if (!req) return;
GC_ROOT_SAVE(root_mark, req->js); reason = Ant_Error_ConsumeMarker(req->js, reason); GC_ROOT_PIN(req->js, reason);
if (!req->settled) { req->settled = true; js_reject_promise(req->js, req->promise, reason); }
fetch_cancel_request_body(req, reason); if (is_object_type(req->response_obj)) fetch_error_response_body(req, reason); GC_ROOT_RESTORE(req->js, root_mark);}
static void fetch_resolve(fetch_request_t *req, ant_value_t response_obj) { if (!req || req->settled) return; req->settled = true; req->response_started = true; req->response_obj = response_obj; js_resolve_promise(req->js, req->promise, response_obj);}
static bool fetch_is_http_url(const char *url) { return strncmp(url, "http://", 7) == 0 || strncmp(url, "https://", 8) == 0;}
static char *fetch_build_request_url(request_data_t *request) { if (!request) return NULL; return build_href(&request->url);}
static const char *fetch_find_header_value(const ant_http_header_t *headers, const char *name) { for (const ant_http_header_t *entry = headers; entry; entry = entry->next) { if (entry->name && strcasecmp(entry->name, name) == 0) return entry->value; } return NULL;}
static const char *fetch_response_mime_type(const ant_http_response_t *resp) { const char *content_type = resp ? fetch_find_header_value(resp->headers, "content-type") : NULL; return content_type ? content_type : "";}
static bool fetch_redirect_rewrites_to_get(int status, const char *method) { if (!method) return false; if (status == 303) return strcasecmp(method, "HEAD") != 0; return (status == 301 || status == 302) && strcasecmp(method, "POST") == 0;}
typedef struct { ant_t *js; ant_value_t headers; bool drop_body_headers; bool failed;} fetch_redirect_headers_ctx_t;
static void fetch_copy_redirect_header(const char *name, const char *value, void *ctx) { fetch_redirect_headers_ctx_t *copy = (fetch_redirect_headers_ctx_t *)ctx; ant_value_t step = 0;
if (!copy || copy->failed) return; if (copy->drop_body_headers && name && strcasecmp(name, "content-type") == 0) return;
step = headers_append_literal(copy->js, copy->headers, name, value); if (is_err(step)) copy->failed = true;}
static ant_value_t fetch_replace_request_headers(fetch_request_t *req, bool drop_body_headers) { ant_t *js = req->js; ant_value_t current = request_get_headers(req->request_obj); ant_value_t headers = headers_create_empty(js); fetch_redirect_headers_ctx_t ctx = { .js = js, .headers = headers, .drop_body_headers = drop_body_headers, .failed = false, };
if (is_err(headers)) return headers; headers_data_for_each(headers_get_data(current), fetch_copy_redirect_header, &ctx); if (ctx.failed) return js_mkerr(js, "out of memory"); js_set_slot_wb(js, req->request_obj, SLOT_REQUEST_HEADERS, headers); return js_mkundef();}
static ant_value_t fetch_clear_redirect_request_body(fetch_request_t *req) { request_data_t *request = request_get_data(req->request_obj); ant_value_t headers_step = 0; if (!request) return fetch_type_error(req->js, "Invalid Request object"); free(request->body_data); free(request->body_type); request->body_data = NULL; request->body_size = 0; request->body_type = NULL; request->body_is_stream = false; request->has_body = false; request->body_used = false; js_set_slot_wb(req->js, req->request_obj, SLOT_REQUEST_BODY_STREAM, js_mkundef());
headers_step = fetch_replace_request_headers(req, true); if (is_err(headers_step)) return headers_step; return js_mkundef();}
static ant_value_t fetch_set_redirect_method(fetch_request_t *req, const char *method) { request_data_t *request = request_get_data(req->request_obj); char *dup = NULL;
if (!request) return fetch_type_error(req->js, "Invalid Request object"); dup = strdup(method); if (!dup) return js_mkerr(req->js, "out of memory"); free(request->method); request->method = dup; js_set_slot(req->request_obj, SLOT_REQUEST_METHOD, js_mkundef()); return js_mkundef();}
static ant_value_t fetch_update_request_url(fetch_request_t *req, const char *location) { request_data_t *request = request_get_data(req->request_obj); url_state_t next = {0}; char *base = NULL;
if (!request || !location) return fetch_type_error(req->js, "Invalid redirect URL"); base = fetch_build_request_url(request); if (!base) return fetch_type_error(req->js, "Invalid request URL");
if (parse_url_to_state(location, base, &next) != 0) { free(base); url_state_clear(&next); return fetch_type_error(req->js, "Invalid redirect URL"); }
free(base); url_state_clear(&request->url); request->url = next; js_set_slot(req->request_obj, SLOT_REQUEST_URL, js_mkundef()); return js_mkundef();}
static ant_value_t fetch_prepare_redirect(fetch_request_t *req, const ant_http_response_t *resp) { request_data_t *request = request_get_data(req->request_obj); const char *location = fetch_find_header_value(resp->headers, "location"); ant_value_t step = 0; bool rewrite_to_get = false;
if (!request || !location || location[0] == '\0') return js_mkundef(); if (req->redirect_count >= FETCH_MAX_REDIRECTS) { return fetch_type_error(req->js, "fetch failed: too many redirects"); }
rewrite_to_get = fetch_redirect_rewrites_to_get(resp->status, request->method); if (!rewrite_to_get && request->body_is_stream) { return fetch_type_error(req->js, "fetch failed: cannot follow redirect with a streamed request body"); }
if (rewrite_to_get) { step = fetch_set_redirect_method(req, strcasecmp(request->method, "HEAD") == 0 ? "HEAD" : "GET"); if (is_err(step)) return step; step = fetch_clear_redirect_request_body(req); if (is_err(step)) return step; }
step = fetch_update_request_url(req, location); if (is_err(step)) return step;
req->redirect_count++; req->restart_pending = true; return js_mkundef();}
typedef struct { ant_http_header_t *head; ant_http_header_t **tail; bool failed; bool has_user_agent; bool has_accept; bool has_accept_language; bool has_sec_fetch_mode; bool has_accept_encoding; bool has_content_length;} fetch_header_builder_t;
static void fetch_collect_header(const char *name, const char *value, void *ctx) { fetch_header_builder_t *builder = (fetch_header_builder_t *)ctx; ant_http_header_t *header = NULL;
if (!builder || builder->failed) return; if (name && strcasecmp(name, "user-agent") == 0) builder->has_user_agent = true; if (name && strcasecmp(name, "accept") == 0) builder->has_accept = true; if (name && strcasecmp(name, "accept-language") == 0) builder->has_accept_language = true; if (name && strcasecmp(name, "sec-fetch-mode") == 0) builder->has_sec_fetch_mode = true; if (name && strcasecmp(name, "accept-encoding") == 0) builder->has_accept_encoding = true; if (name && strcasecmp(name, "content-length") == 0) builder->has_content_length = true;
header = calloc(1, sizeof(ant_http_header_t)); if (!header) { builder->failed = true; return; }
header->name = strdup(name ? name : ""); header->value = strdup(value ? value : "");
if (!header->name || !header->value) { free(header->name); free(header->value); free(header); builder->failed = true; return; }
*builder->tail = header; builder->tail = &header->next;}
static bool fetch_append_header(fetch_header_builder_t *builder, const char *name, const char *value) { ant_http_header_t *header = NULL;
if (!builder || builder->failed) return false; header = calloc(1, sizeof(ant_http_header_t)); if (!header) { builder->failed = true; return false; }
header->name = strdup(name); header->value = strdup(value); if (!header->name || !header->value) { free(header->name); free(header->value); free(header); builder->failed = true; return false; }
*builder->tail = header; builder->tail = &header->next; return true;}
static ant_http_header_t *fetch_build_http_headers(ant_value_t request_obj) { fetch_header_builder_t builder = {0}; request_data_t *request = request_get_data(request_obj);
char user_agent[256] = {0}; char content_length[32] = {0};
builder.tail = &builder.head; headers_data_for_each( headers_get_data(request_get_headers(request_obj)), fetch_collect_header, &builder);
if (builder.failed) { ant_http_headers_free(builder.head); return NULL; }
if (!builder.has_accept && !fetch_append_header(&builder, "accept", "*/*")) { ant_http_headers_free(builder.head); return NULL; } if (!builder.has_accept_language && !fetch_append_header(&builder, "accept-language", "*")) { ant_http_headers_free(builder.head); return NULL; } if (!builder.has_sec_fetch_mode && !fetch_append_header(&builder, "sec-fetch-mode", "cors")) { ant_http_headers_free(builder.head); return NULL; } if (!builder.has_accept_encoding && !fetch_append_header(&builder, "accept-encoding", "br, gzip, deflate")) { ant_http_headers_free(builder.head); return NULL; } if (request && request->has_body && !request->body_is_stream && !builder.has_content_length) { snprintf(content_length, sizeof(content_length), "%zu", request->body_size); if (!fetch_append_header(&builder, "content-length", content_length)) { ant_http_headers_free(builder.head); return NULL; } } if (builder.has_user_agent) return builder.head;
snprintf(user_agent, sizeof(user_agent), "ant/%s", ANT_VERSION); if (!fetch_append_header(&builder, "user-agent", user_agent)) { ant_http_headers_free(builder.head); return NULL; } return builder.head;}
static ant_value_t fetch_headers_from_http(ant_t *js, const ant_http_header_t *headers) { ant_value_t hdrs = headers_create_empty(js); if (is_err(hdrs)) return hdrs;
for (const ant_http_header_t *entry = headers; entry; entry = entry->next) { ant_value_t step = headers_append_literal(js, hdrs, entry->name, entry->value); if (is_err(step)) return step; }
return hdrs;}
static ant_value_t fetch_create_chunk(ant_t *js, const uint8_t *data, size_t len) { ArrayBufferData *ab = create_array_buffer_data(js, len); if (!ab) return js_mkerr(js, "out of memory"); if (len > 0) memcpy(ab->data, data, len); return create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, js->builtins.typedarray_protos[TYPED_ARRAY_UINT8]);}
static bool fetch_get_upload_chunk(ant_value_t value, const uint8_t **out, size_t *len) { TypedArrayData *ta = buffer_get_typedarray_data(value);
if (!ta || ta->type != TYPED_ARRAY_UINT8) return false; Ant_Buffer_GetViewBytes(ta, out, len); return true;}
static void fetch_http_on_response(ant_http_request_t *http_req, const ant_http_response_t *resp, void *user_data) { fetch_request_t *req = (fetch_request_t *)user_data; ant_t *js = req->js; request_data_t *request = request_get_data(req->request_obj); ant_value_t headers = 0; ant_value_t step = 0; ant_value_t stream = 0; ant_value_t response = 0; char *url = NULL; if (req->aborted) return; if (!request) { fetch_reject(req, fetch_type_error(js, "Invalid Request object")); ant_http_request_cancel(http_req); return; }
url = fetch_build_request_url(request); ant_inspector_network_response( req->network_request_id, url, resp->status, resp->status_text, fetch_response_mime_type(resp), "Fetch", resp->headers ); free(url); url = NULL;
if (fetch_is_redirect_status(resp->status)) { const char *location = fetch_find_header_value(resp->headers, "location"); const char *redirect_mode = request->redirect ? request->redirect : "follow"; if (location && location[0] != '\0' && strcmp(redirect_mode, "error") == 0) { fetch_reject(req, fetch_type_error(js, "fetch failed: redirect mode is set to error")); ant_http_request_cancel(http_req); return; } if (strcmp(redirect_mode, "follow") == 0) { step = fetch_prepare_redirect(req, resp); if (is_err(step)) { fetch_reject(req, step); ant_http_request_cancel(http_req); return; } if (req->restart_pending) { ant_http_request_cancel(http_req); return; }} }
headers = fetch_headers_from_http(js, resp->headers); if (is_err(headers)) { fetch_reject(req, headers); ant_http_request_cancel(http_req); return; }
stream = rs_create_stream(js, js_mkundef(), js_mkundef(), 1.0); if (is_err(stream)) { fetch_reject(req, stream); ant_http_request_cancel(http_req); return; }
url = fetch_build_request_url(request_get_data(req->request_obj)); response = response_create_fetched( js, resp->status, resp->status_text, url, req->redirect_count + 1, headers, NULL, 0, stream, NULL ); free(url);
if (is_err(response)) { fetch_reject(req, response); ant_http_request_cancel(http_req); return; }
fetch_resolve(req, response);}
static void fetch_http_on_body(ant_http_request_t *http_req, const uint8_t *chunk, size_t len, void *user_data) { fetch_request_t *req = (fetch_request_t *)user_data; ant_t *js = req->js; ant_value_t stream = 0; ant_value_t controller = 0; ant_value_t value = 0; ant_value_t step = 0;
if (req->aborted || !is_object_type(req->response_obj)) return; req->network_encoded_length += len; ant_inspector_network_append_response_body(req->network_request_id, chunk, len);
stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); if (!rs_is_stream(stream)) return;
controller = rs_stream_controller(js, stream); value = fetch_create_chunk(js, chunk, len); if (is_err(value)) { fetch_error_response_body(req, value); ant_http_request_cancel(http_req); return; }
step = rs_controller_enqueue(js, controller, value); if (is_err(step)) { fetch_error_response_body(req, step); ant_http_request_cancel(http_req); }}
static ant_value_t fetch_transport_reason(fetch_request_t *req, ant_http_result_t result, const char *error_message) { if (result == ANT_HTTP_RESULT_ABORTED && req->aborted) { ant_value_t signal = request_get_signal(req->js, req->request_obj); return abort_signal_get_reason(signal); }
return fetch_type_error(req->js, error_message ? error_message : "fetch failed");}
static char *fetch_data_url_content_type(const char *url) { const char *header = url + 5; const char *comma = strchr(header, ','); const char *base64 = NULL; size_t len = 0;
if (!comma) return strdup("text/plain;charset=US-ASCII"); base64 = strstr(header, ";base64"); len = base64 && base64 < comma ? (size_t)(base64 - header) : (size_t)(comma - header); if (len == 0) return strdup("text/plain;charset=US-ASCII"); return strndup(header, len);}
static bool fetch_handle_data_url(fetch_request_t *req) { ant_t *js = req->js; request_data_t *request = request_get_data(req->request_obj); char *url = fetch_build_request_url(request); size_t len = 0; char *body = NULL; char *content_type = NULL; ant_value_t headers = 0; ant_value_t response = 0;
if (!url || !esm_is_data_url(url)) { free(url); return false; }
body = esm_parse_data_url(url, &len); content_type = fetch_data_url_content_type(url); headers = headers_create_empty(js);
if (!body || !content_type || is_err(headers)) { free(url); free(body); free(content_type); fetch_reject(req, fetch_type_error(js, "Failed to decode data URL")); fetch_request_release(req); return true; }
headers_set_literal(headers, "content-type", content_type); response = response_create_fetched( js, 200, "OK", url, 1, headers, (const uint8_t *)body, len, js_mkundef(), content_type );
free(url); free(body); free(content_type);
if (is_err(response)) { fetch_reject(req, response); } else fetch_resolve(req, response);
fetch_request_release(req); return true;}
static bool fetch_handle_blob_url(fetch_request_t *req) { ant_t *js = req->js; request_data_t *request = request_get_data(req->request_obj); char *url = fetch_build_request_url(request);
if (!url || strncmp(url, "blob:", 5) != 0) { free(url); return false; }
ant_value_t blob = url_resolve_object_url(js, url); blob_data_t *data = blob_is_blob(js, blob) ? blob_get_data(blob) : NULL; if (!data) { free(url); fetch_reject(req, fetch_type_error(js, "Failed to fetch blob URL")); fetch_request_release(req); return true; }
ant_value_t headers = headers_create_empty(js); const char *content_type = data->type && *data->type ? data->type : NULL; if (content_type) headers_set_literal(headers, "content-type", content_type); ant_value_t response = response_create_fetched( js, 200, "OK", url, 1, headers, data->data, data->size, js_mkundef(), content_type ); free(url);
if (is_err(response)) fetch_reject(req, response); else fetch_resolve(req, response);
fetch_request_release(req); return true;}
static ant_value_t fetch_upload_on_reject(ant_params_t) { fetch_request_t *req = (fetch_request_t *)js_get_native(js->current_func, FETCH_REQUEST_NATIVE_TAG); ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
if (!req) return js_mkundef(); req->upload_read_promise = js_mkundef();
if (!req->aborted) { if (req->http_req) ant_http_request_cancel(req->http_req); fetch_reject(req, reason); if (!req->http_req) fetch_request_release(req); }
fetch_request_release(req); return js_mkundef();}
static void fetch_upload_schedule_next_read(fetch_request_t *req);static ant_value_t fetch_upload_on_read(ant_params_t) { fetch_request_t *req = (fetch_request_t *)js_get_native(js->current_func, FETCH_REQUEST_NATIVE_TAG); ant_value_t result = (nargs > 0) ? args[0] : js_mkundef(); ant_value_t done = 0; ant_value_t value = 0; const uint8_t *chunk = NULL; size_t chunk_len = 0; int rc = 0;
if (!req) return js_mkundef(); req->upload_read_promise = js_mkundef();
if (req->aborted || !req->http_req) { fetch_request_release(req); return js_mkundef(); }
done = js_get(js, result, "done"); value = js_get(js, result, "value"); if (done == js_true) { ant_http_request_end(req->http_req); fetch_request_release(req); return js_mkundef(); }
if (!fetch_get_upload_chunk(value, &chunk, &chunk_len)) { GC_ROOT_SAVE(root_mark, js); ant_value_t reason = Ant_Error_Create(js, JS_ERR_TYPE, "fetch request body stream chunk must be a Uint8Array");
GC_ROOT_PIN(js, reason); ant_http_request_cancel(req->http_req);
fetch_reject(req, reason); fetch_request_release(req); GC_ROOT_RESTORE(js, root_mark);
return js_mkundef(); }
rc = ant_http_request_write(req->http_req, chunk, chunk_len); if (rc != 0) { GC_ROOT_SAVE(root_mark, js); ant_value_t reason = Ant_Error_Create(js, JS_ERR_TYPE, uv_strerror(rc));
GC_ROOT_PIN(js, reason); ant_http_request_cancel(req->http_req);
fetch_reject(req, reason); fetch_request_release(req); GC_ROOT_RESTORE(js, root_mark);
return js_mkundef(); }
fetch_upload_schedule_next_read(req); fetch_request_release(req);
return js_mkundef();}
static void fetch_upload_schedule_next_read(fetch_request_t *req) { ant_t *js = req->js; ant_value_t next_p = 0; ant_value_t fulfill = 0; ant_value_t reject = 0;
if (!req || !is_object_type(req->upload_reader)) return; next_p = rs_default_reader_read(js, req->upload_reader); req->upload_read_promise = next_p; fulfill = js_heavy_mkfun_native(js, fetch_upload_on_read, req, FETCH_REQUEST_NATIVE_TAG); reject = js_heavy_mkfun_native(js, fetch_upload_on_reject, req, FETCH_REQUEST_NATIVE_TAG); req->refs++; Ant_Promise_Observe(js, next_p, fulfill, reject);}
static void fetch_start_upload(fetch_request_t *req) { ant_t *js = req->js;
ant_value_t stream = js_get_slot(req->request_obj, SLOT_REQUEST_BODY_STREAM); ant_value_t reader_args[1] = { stream };
if (!rs_is_stream(stream)) return; ant_value_t reader = js_construct_native(js, js_rs_reader_ctor, reader_args, 1);
if (is_err(reader)) { GC_ROOT_SAVE(root_mark, js); ant_value_t reason = js_take_thrown(js, reader); GC_ROOT_PIN(js, reason);
if (req->http_req) ant_http_request_cancel(req->http_req); fetch_reject(req, reason);
if (!req->http_req) fetch_request_release(req); GC_ROOT_RESTORE(js, root_mark);
return; }
req->upload_reader = reader; fetch_upload_schedule_next_read(req);}
static void fetch_http_on_complete( ant_http_request_t *http_req, ant_http_result_t result, int error_code, const char *error_message, void *user_data) { fetch_request_t *req = (fetch_request_t *)user_data; ant_t *js = req->js; ant_value_t stream = 0; ant_value_t controller = 0; ant_value_t reason = 0; req->http_req = NULL;
if (req->restart_pending) { ant_inspector_network_finish(req->network_request_id, req->network_encoded_length); req->network_request_id = 0; req->network_encoded_length = 0; req->restart_pending = false; fetch_start_http(req); return; }
if (result != ANT_HTTP_RESULT_OK || error_code != 0) { ant_inspector_network_fail( req->network_request_id, error_message ? error_message : "fetch failed", result == ANT_HTTP_RESULT_ABORTED, "Fetch" ); reason = fetch_transport_reason(req, result, error_message); if (is_object_type(req->response_obj)) fetch_error_response_body(req, reason); else fetch_reject(req, reason); fetch_request_release(req); return; }
if (is_object_type(req->response_obj)) { stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); if (rs_is_stream(stream)) { controller = rs_stream_controller(js, stream); rs_controller_close(js, controller); } } else fetch_reject(req, fetch_type_error(js, "fetch completed without a response"));
ant_inspector_network_finish(req->network_request_id, req->network_encoded_length); fetch_request_release(req);}
void fetch_start_http(fetch_request_t *req) { request_data_t *request = request_get_data(req->request_obj); ant_http_request_options_t options = {0}; ant_http_header_t *headers = NULL; char *url = NULL; int rc = 0;
if (!request) { fetch_reject(req, fetch_type_error(req->js, "Invalid Request object")); fetch_request_release(req); return; }
url = fetch_build_request_url(request); if (!url) { fetch_reject(req, fetch_type_error(req->js, "Invalid request URL")); fetch_request_release(req); return; }
if (esm_is_data_url(url)) { free(url); fetch_handle_data_url(req); return; } if (strncmp(url, "blob:", 5) == 0) { free(url); fetch_handle_blob_url(req); return; } if (!fetch_is_http_url(url)) { free(url); fetch_reject(req, fetch_type_error(req->js, "fetch only supports http:, https:, data:, and blob: URLs")); fetch_request_release(req); return; }
headers = fetch_build_http_headers(req->request_obj); if (!headers) { free(url); fetch_reject(req, fetch_type_error(req->js, "out of memory")); fetch_request_release(req); return; }
options.method = request->method; options.url = url; options.headers = headers; options.body = request->body_data; options.body_len = request->body_size; options.chunked_body = request->body_is_stream; if (!req->js->http_pool) req->js->http_pool = ant_http_pool_create(req->js->loop); options.pool = req->js->http_pool; req->network_encoded_length = 0; req->network_request_id = ant_inspector_network_request( request->method, url, "Fetch", "Fetch", request->has_body && !request->body_is_stream && request->body_data && request->body_size > 0, headers ); if (request->has_body && !request->body_is_stream && request->body_data && request->body_size > 0) ant_inspector_network_set_request_body(req->network_request_id, request->body_data, request->body_size);
rc = ant_http_request_start( req->js->loop, &options, fetch_http_on_response, fetch_http_on_body, fetch_http_on_complete, req, &req->http_req );
ant_http_headers_free(headers); free(url);
if (rc != 0) { ant_inspector_network_fail(req->network_request_id, uv_strerror(rc), false, "Fetch"); fetch_reject(req, fetch_type_error(req->js, uv_strerror(rc))); fetch_request_release(req); return; }
if (request->body_is_stream) fetch_start_upload(req);}
static ant_value_t fetch_abort_listener(ant_params_t) { fetch_request_t *req = (fetch_request_t *)js_get_native(js->current_func, FETCH_REQUEST_NATIVE_TAG); ant_value_t signal = 0; ant_value_t reason = 0;
if (!req || req->aborted) return js_mkundef(); req->aborted = true; signal = request_get_signal(js, req->request_obj); reason = abort_signal_get_reason(signal);
if (req->http_req) ant_http_request_cancel(req->http_req); fetch_reject(req, reason); if (!req->http_req) fetch_request_release(req);
return js_mkundef();}
ant_value_t ant_fetch(ant_params_t) { ant_value_t input = (nargs >= 1) ? args[0] : js_mkundef(); ant_value_t init = (nargs >= 2) ? args[1] : js_mkundef(); ant_value_t promise = js_mkpromise(js); ant_value_t request_obj = 0; request_data_t *request = NULL; fetch_request_t *req = NULL; ant_value_t signal = 0;
request_obj = request_create_from_input_init(js, input, init); if (is_err(request_obj)) { js_reject_promise(js, promise, request_obj); return promise; }
request = request_get_data(request_obj); if (!request) { js_reject_promise(js, promise, fetch_type_error(js, "Invalid Request object")); return promise; }
req = calloc(1, sizeof(fetch_request_t)); if (!req) { js_reject_promise(js, promise, fetch_type_error(js, "out of memory")); return promise; }
req->js = js; req->promise = promise; req->request_obj = request_obj; req->response_obj = js_mkundef(); req->abort_listener = js_mkundef(); req->upload_reader = js_mkundef(); req->upload_read_promise = js_mkundef(); req->refs = 1; Ant_List_Insert(&js->fetch_requests, req);
signal = request_get_signal(js, request_obj); if (abort_signal_is_signal(signal)) { if (abort_signal_is_aborted(signal)) { fetch_reject(req, abort_signal_get_reason(signal)); fetch_request_release(req); return promise; } req->abort_listener = js_heavy_mkfun_native(js, fetch_abort_listener, req, FETCH_REQUEST_NATIVE_TAG); abort_signal_add_listener(js, signal, req->abort_listener); }
if (request->has_body) request->body_used = true; fetch_start_http(req); return promise;}
void init_fetch_module(ant_t *js) { js_set(js, js->global, "fetch", Ant_Function_CreateEx(ant_fetch, 1, CFUNC_HAS_PROTOTYPE));}
void cleanup_fetch_module(ant_t *js) { for (fetch_request_t *req = js->fetch_requests; req; req = req->next) { req->restart_pending = false; if (req->http_req) ant_http_request_abort(req->http_req); }}
int has_pending_fetches(ant_t *js) { return js->fetch_requests != NULL;}
void gc_mark_fetch(ant_t *js, gc_mark_fn mark) { for (fetch_request_t *req = js->fetch_requests; req; req = req->next) { mark(js, req->promise); mark(js, req->request_obj); mark(js, req->response_obj); mark(js, req->abort_listener); mark(js, req->upload_reader); mark(js, req->upload_read_promise); }}