MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

revise fetch api surface

+495 -270
examples/npm/ky/ant.lockb

This is a binary file and will not be displayed.

+9
examples/npm/ky/index.js
··· 1 + import ky from 'ky'; 2 + 3 + const json = await ky 4 + .post('https://httpbingo.org/post', { 5 + json: { kitty: '๐Ÿฑ' } 6 + }) 7 + .json(); 8 + 9 + console.log(json);
+13
examples/npm/ky/package.json
··· 1 + { 2 + "name": "ky", 3 + "version": "1.0.0", 4 + "type": "module", 5 + "main": "index.js", 6 + "scripts": { 7 + "start": "ant index.js" 8 + }, 9 + "dependencies": { 10 + "ky": "^1.14.3" 11 + }, 12 + "devDependencies": {} 13 + }
+473 -270
src/modules/fetch.c
··· 1 1 #include <compat.h> // IWYU pragma: keep 2 2 3 - #include <uv.h> 4 - #include <stdio.h> 3 + #include <stdbool.h> 4 + #include <stdint.h> 5 5 #include <stdlib.h> 6 6 #include <string.h> 7 - #include <tlsuv/tlsuv.h> 8 - #include <tlsuv/http.h> 7 + #include <strings.h> 8 + #include <uv.h> 9 9 #include <utarray.h> 10 10 11 11 #include "ant.h" 12 - #include "errors.h" 13 12 #include "common.h" 13 + #include "errors.h" 14 14 #include "internal.h" 15 15 #include "runtime.h" 16 - #include "modules/fetch.h" 17 - #include "modules/json.h" 18 - #include "modules/timer.h" 16 + #include "esm/remote.h" 19 17 #include "gc/modules.h" 20 - 21 - typedef struct { 22 - char *data; 23 - size_t size; 24 - size_t capacity; 25 - } fetch_buffer_t; 18 + #include "modules/abort.h" 19 + #include "modules/buffer.h" 20 + #include "modules/fetch.h" 21 + #include "modules/headers.h" 22 + #include "modules/http.h" 23 + #include "modules/request.h" 24 + #include "modules/response.h" 25 + #include "modules/url.h" 26 + #include "streams/readable.h" 26 27 27 28 typedef struct fetch_request_s { 28 29 ant_t *js; 30 + 29 31 ant_value_t promise; 30 - tlsuv_http_t http_client; 31 - tlsuv_http_req_t *http_req; 32 - fetch_buffer_t response_buffer; 33 - int status_code; 34 - int completed; 35 - int failed; 36 - char *error_msg; 37 - ant_value_t headers_obj; 38 - char *method; 39 - char *body; 40 - size_t body_len; 32 + ant_value_t request_obj; 33 + ant_value_t response_obj; 34 + ant_value_t abort_listener; 35 + ant_http_request_t *http_req; 36 + 37 + bool settled; 38 + bool aborted; 41 39 } fetch_request_t; 42 40 43 41 static UT_array *pending_requests = NULL; 44 - 45 - static void free_fetch_request(fetch_request_t *req) { 46 - if (!req) return; 47 - 48 - if (req->response_buffer.data) free(req->response_buffer.data); 49 - if (req->error_msg) free(req->error_msg); 50 - if (req->method) free(req->method); 51 - if (req->body) free(req->body); 52 - 53 - free(req); 54 - } 55 42 56 43 static void remove_pending_request(fetch_request_t *req) { 57 44 if (!req || !pending_requests) return; 58 - 45 + 59 46 fetch_request_t **p = NULL; 60 47 unsigned int i = 0; 61 48 62 - while ((p = (fetch_request_t**)utarray_next(pending_requests, p))) { 63 - if (*p == req) { utarray_erase(pending_requests, i, 1); break; } 64 - i++; 49 + while ((p = (fetch_request_t **)utarray_next(pending_requests, p))) { 50 + if (*p == req) { utarray_erase(pending_requests, i, 1); return; } i++; 65 51 } 66 52 } 67 53 68 - static ant_value_t fetch_fail_oom(ant_t *js, ant_value_t promise, fetch_request_t *req, bool close_http, const char *msg, size_t len) { 69 - ant_value_t err = js_mkstr(js, msg, len); 70 - js_reject_promise(js, promise, err); 71 - if (close_http) tlsuv_http_close(&req->http_client, NULL); 72 - remove_pending_request(req); free_fetch_request(req); 73 - return js_mkundef(); 54 + static void free_fetch_request(fetch_request_t *req) { 55 + if (!req) return; 56 + 57 + if (abort_signal_is_signal(request_get_signal(req->request_obj)) && is_callable(req->abort_listener)) 58 + abort_signal_remove_listener(req->js, request_get_signal(req->request_obj), req->abort_listener); 59 + 60 + remove_pending_request(req); 61 + free(req); 74 62 } 75 63 76 - static ant_value_t response_text(ant_t *js, ant_value_t *args, int nargs) { 77 - ant_value_t this = js_getthis(js); 78 - ant_value_t body = js_get_slot(this, SLOT_DATA); 79 - 80 - ant_value_t promise = js_mkpromise(js); 81 - js_resolve_promise(js, promise, body); 82 - 83 - return promise; 64 + static ant_value_t fetch_type_error(ant_t *js, const char *message) { 65 + return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "fetch failed"); 84 66 } 85 67 86 - static ant_value_t response_json(ant_t *js, ant_value_t *args, int nargs) { 87 - ant_value_t this = js_getthis(js); 88 - ant_value_t body = js_get_slot(this, SLOT_DATA); 89 - ant_value_t parsed = json_parse_value(js, body); 90 - ant_value_t promise = js_mkpromise(js); 91 - 92 - if (vtype(parsed) == T_ERR) { 93 - js_reject_promise(js, promise, parsed); 94 - } else js_resolve_promise(js, promise, parsed); 95 - 96 - return promise; 68 + static ant_value_t fetch_rejection_reason(ant_t *js, ant_value_t value) { 69 + if (!is_err(value)) return value; 70 + if (js->thrown_exists) { 71 + ant_value_t reason = js->thrown_value; 72 + js->thrown_exists = false; 73 + js->thrown_value = js_mkundef(); 74 + js->thrown_stack = js_mkundef(); 75 + return reason; 76 + } 77 + return value; 78 + } 79 + 80 + static void fetch_cancel_request_body(fetch_request_t *req, ant_value_t reason) { 81 + request_data_t *data = request_get_data(req->request_obj); 82 + ant_value_t stream = js_get_slot(req->request_obj, SLOT_REQUEST_BODY_STREAM); 83 + 84 + if (!data || !data->body_is_stream || !rs_is_stream(stream)) return; 85 + readable_stream_cancel(req->js, stream, reason); 86 + } 87 + 88 + static void fetch_error_response_body(fetch_request_t *req, ant_value_t reason) { 89 + ant_value_t stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); 90 + if (rs_is_stream(stream)) readable_stream_error(req->js, stream, reason); 91 + } 92 + 93 + static void fetch_reject(fetch_request_t *req, ant_value_t reason) { 94 + if (!req) return; 95 + 96 + if (!req->settled) { 97 + req->settled = true; 98 + js_reject_promise(req->js, req->promise, reason); 99 + } 100 + 101 + fetch_cancel_request_body(req, reason); 102 + if (is_object_type(req->response_obj)) fetch_error_response_body(req, reason); 103 + } 104 + 105 + static void fetch_resolve(fetch_request_t *req, ant_value_t response_obj) { 106 + if (!req || req->settled) return; 107 + req->settled = true; 108 + req->response_obj = response_obj; 109 + js_resolve_promise(req->js, req->promise, response_obj); 110 + } 111 + 112 + static bool fetch_is_http_url(const char *url) { 113 + return strncmp(url, "http://", 7) == 0 || strncmp(url, "https://", 8) == 0; 114 + } 115 + 116 + static char *fetch_build_request_url(request_data_t *request) { 117 + if (!request) return NULL; 118 + return build_href(&request->url); 97 119 } 98 120 99 - static ant_value_t create_response(ant_t *js, int status, const char *body, size_t body_len) { 100 - ant_value_t response_obj = js_mkobj(js); 101 - ant_value_t body_str = js_mkstr(js, body, body_len); 121 + typedef struct { 122 + ant_http_header_t *head; 123 + ant_http_header_t **tail; 124 + bool failed; 125 + bool has_user_agent; 126 + bool has_accept; 127 + bool has_accept_language; 128 + bool has_sec_fetch_mode; 129 + bool has_accept_encoding; 130 + } fetch_header_builder_t; 102 131 103 - js_set(js, response_obj, "ok", js_bool(status >= 200 && status < 300)); 104 - js_set(js, response_obj, "status", js_mknum(status)); 105 - 106 - js_set_slot(response_obj, SLOT_DATA, body_str); 132 + static void fetch_collect_header(const char *name, const char *value, void *ctx) { 133 + fetch_header_builder_t *builder = (fetch_header_builder_t *)ctx; 134 + ant_http_header_t *header = NULL; 135 + 136 + if (!builder || builder->failed) return; 137 + if (name && strcasecmp(name, "user-agent") == 0) builder->has_user_agent = true; 138 + if (name && strcasecmp(name, "accept") == 0) builder->has_accept = true; 139 + if (name && strcasecmp(name, "accept-language") == 0) builder->has_accept_language = true; 140 + if (name && strcasecmp(name, "sec-fetch-mode") == 0) builder->has_sec_fetch_mode = true; 141 + if (name && strcasecmp(name, "accept-encoding") == 0) builder->has_accept_encoding = true; 142 + header = calloc(1, sizeof(ant_http_header_t)); 143 + if (!header) { 144 + builder->failed = true; 145 + return; 146 + } 147 + 148 + header->name = strdup(name ? name : ""); 149 + header->value = strdup(value ? value : ""); 150 + if (!header->name || !header->value) { 151 + free(header->name); 152 + free(header->value); 153 + free(header); 154 + builder->failed = true; 155 + return; 156 + } 157 + 158 + *builder->tail = header; 159 + builder->tail = &header->next; 160 + } 161 + 162 + static bool fetch_append_header(fetch_header_builder_t *builder, const char *name, const char *value) { 163 + ant_http_header_t *header = NULL; 164 + 165 + if (!builder || builder->failed) return false; 166 + header = calloc(1, sizeof(ant_http_header_t)); 167 + if (!header) { 168 + builder->failed = true; 169 + return false; 170 + } 171 + 172 + header->name = strdup(name); 173 + header->value = strdup(value); 174 + if (!header->name || !header->value) { 175 + free(header->name); 176 + free(header->value); 177 + free(header); 178 + builder->failed = true; 179 + return false; 180 + } 181 + 182 + *builder->tail = header; 183 + builder->tail = &header->next; 184 + return true; 185 + } 186 + 187 + static ant_http_header_t *fetch_build_http_headers(ant_value_t request_obj) { 188 + fetch_header_builder_t builder = {0}; 189 + char user_agent[256] = {0}; 190 + 191 + builder.tail = &builder.head; 192 + headers_for_each(request_get_headers(request_obj), fetch_collect_header, &builder); 107 193 108 - js_set(js, response_obj, "text", js_mkfun(response_text)); 109 - js_set(js, response_obj, "json", js_mkfun(response_json)); 194 + if (builder.failed) { 195 + ant_http_headers_free(builder.head); 196 + return NULL; 197 + } 198 + 199 + if (!builder.has_accept && !fetch_append_header(&builder, "accept", "*/*")) { 200 + ant_http_headers_free(builder.head); 201 + return NULL; 202 + } 203 + if (!builder.has_accept_language && !fetch_append_header(&builder, "accept-language", "*")) { 204 + ant_http_headers_free(builder.head); 205 + return NULL; 206 + } 207 + if (!builder.has_sec_fetch_mode && !fetch_append_header(&builder, "sec-fetch-mode", "cors")) { 208 + ant_http_headers_free(builder.head); 209 + return NULL; 210 + } 211 + if (!builder.has_accept_encoding && !fetch_append_header(&builder, "accept-encoding", "br, gzip, deflate")) { 212 + ant_http_headers_free(builder.head); 213 + return NULL; 214 + } 215 + if (builder.has_user_agent) return builder.head; 216 + 217 + snprintf(user_agent, sizeof(user_agent), "ant/%s", ANT_VERSION); 218 + if (!fetch_append_header(&builder, "user-agent", user_agent)) { 219 + ant_http_headers_free(builder.head); 220 + return NULL; 221 + } 110 222 111 - return response_obj; 223 + return builder.head; 112 224 } 113 225 114 - static void complete_request(fetch_request_t *req) { 115 - if (req->failed) { 116 - ant_value_t err = js_mkstr(req->js, req->error_msg ? req->error_msg : "Unknown error", req->error_msg ? strlen(req->error_msg) : 13); 117 - js_reject_promise(req->js, req->promise, err); 118 - } else { 119 - const char *body = req->response_buffer.data ? req->response_buffer.data : ""; 120 - size_t body_len = req->response_buffer.data ? req->response_buffer.size : 0; 121 - ant_value_t response = create_response(req->js, req->status_code, body, body_len); 122 - js_resolve_promise(req->js, req->promise, response); 226 + static ant_value_t fetch_headers_from_http(ant_t *js, const ant_http_header_t *headers) { 227 + ant_value_t hdrs = headers_create_empty(js); 228 + if (is_err(hdrs)) return hdrs; 229 + 230 + for (const ant_http_header_t *entry = headers; entry; entry = entry->next) { 231 + ant_value_t step = headers_append_value( 232 + js, hdrs, 233 + js_mkstr(js, entry->name, strlen(entry->name)), 234 + js_mkstr(js, entry->value, strlen(entry->value)) 235 + ); 236 + if (is_err(step)) return step; 123 237 } 124 - 125 - remove_pending_request(req); 126 - free_fetch_request(req); 238 + 239 + return hdrs; 127 240 } 128 241 129 - static void on_http_close(tlsuv_http_t *client) { 130 - fetch_request_t *req = (fetch_request_t *)client->data; 131 - if (req && req->completed) complete_request(req); 242 + static ant_value_t fetch_create_chunk(ant_t *js, const uint8_t *data, size_t len) { 243 + ArrayBufferData *ab = create_array_buffer_data(len); 244 + if (!ab) return js_mkerr(js, "out of memory"); 245 + if (len > 0) memcpy(ab->data, data, len); 246 + return create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Uint8Array"); 132 247 } 133 248 134 - static void body_cb(tlsuv_http_req_t *http_req, char *body, ssize_t len) { 135 - fetch_request_t *req = (fetch_request_t *)http_req->data; 249 + static void fetch_http_on_response(ant_http_request_t *http_req, const ant_http_response_t *resp, void *user_data) { 250 + fetch_request_t *req = (fetch_request_t *)user_data; 251 + 252 + ant_t *js = req->js; 253 + ant_value_t headers = 0; 254 + ant_value_t stream = 0; 255 + ant_value_t response = 0; 136 256 137 - if (len == UV_EOF) { 138 - req->completed = 1; 139 - tlsuv_http_close(&req->http_client, on_http_close); 257 + char *url = NULL; 258 + if (req->aborted) return; 259 + 260 + headers = fetch_headers_from_http(js, resp->headers); 261 + if (is_err(headers)) { 262 + fetch_reject(req, fetch_rejection_reason(js, headers)); 263 + ant_http_request_cancel(http_req); 264 + return; 265 + } 266 + 267 + stream = rs_create_stream(js, js_mkundef(), js_mkundef(), 1.0); 268 + if (is_err(stream)) { 269 + fetch_reject(req, fetch_rejection_reason(js, stream)); 270 + ant_http_request_cancel(http_req); 271 + return; 272 + } 273 + 274 + url = fetch_build_request_url(request_get_data(req->request_obj)); 275 + response = response_create_fetched( 276 + js, resp->status, resp->status_text, url, headers, NULL, 0, stream, NULL 277 + ); 278 + free(url); 279 + 280 + if (is_err(response)) { 281 + fetch_reject(req, fetch_rejection_reason(js, response)); 282 + ant_http_request_cancel(http_req); 140 283 return; 141 284 } 142 - 143 - if (len < 0) { 144 - req->failed = 1; 145 - req->error_msg = strdup(uv_strerror((int)len)); 146 - req->completed = 1; 147 - tlsuv_http_close(&req->http_client, on_http_close); 285 + 286 + fetch_resolve(req, response); 287 + } 288 + 289 + static void fetch_http_on_body(ant_http_request_t *http_req, const uint8_t *chunk, size_t len, void *user_data) { 290 + fetch_request_t *req = (fetch_request_t *)user_data; 291 + ant_t *js = req->js; 292 + ant_value_t stream = 0; 293 + ant_value_t controller = 0; 294 + ant_value_t value = 0; 295 + ant_value_t step = 0; 296 + 297 + (void)http_req; 298 + if (req->aborted || !is_object_type(req->response_obj)) return; 299 + 300 + stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); 301 + if (!rs_is_stream(stream)) return; 302 + 303 + controller = rs_stream_controller(js, stream); 304 + value = fetch_create_chunk(js, chunk, len); 305 + if (is_err(value)) { 306 + fetch_error_response_body(req, fetch_rejection_reason(js, value)); 307 + ant_http_request_cancel(http_req); 148 308 return; 149 309 } 150 - 151 - if (req->response_buffer.size + len > req->response_buffer.capacity) { 152 - size_t new_capacity = req->response_buffer.capacity * 2; 153 - while (new_capacity < req->response_buffer.size + len) new_capacity *= 2; 154 - char *new_data = realloc(req->response_buffer.data, new_capacity); 155 - 156 - if (!new_data) { 157 - req->failed = 1; 158 - req->error_msg = strdup("Out of memory"); 159 - req->completed = 1; 160 - tlsuv_http_close(&req->http_client, on_http_close); 161 - return; 162 - } 163 - 164 - req->response_buffer.data = new_data; 165 - req->response_buffer.capacity = new_capacity; 310 + 311 + step = rs_controller_enqueue(js, controller, value); 312 + if (is_err(step)) { 313 + fetch_error_response_body(req, fetch_rejection_reason(js, step)); 314 + ant_http_request_cancel(http_req); 166 315 } 167 - 168 - memcpy(req->response_buffer.data + req->response_buffer.size, body, len); 169 - req->response_buffer.size += len; 170 316 } 171 317 172 - static void resp_cb(tlsuv_http_resp_t *resp, void *data) { 173 - (void)data; 174 - fetch_request_t *req = (fetch_request_t *)resp->req->data; 175 - 176 - if (resp->code < 0) { 177 - req->failed = 1; 178 - req->error_msg = strdup(uv_strerror(resp->code)); 179 - req->completed = 1; 180 - tlsuv_http_close(&req->http_client, on_http_close); 318 + static void fetch_http_on_complete(ant_http_request_t *http_req, int error_code, const char *error_message, void *user_data) { 319 + fetch_request_t *req = (fetch_request_t *)user_data; 320 + ant_t *js = req->js; 321 + ant_value_t stream = 0; 322 + ant_value_t controller = 0; 323 + ant_value_t reason = 0; 324 + 325 + (void)http_req; 326 + req->http_req = NULL; 327 + 328 + if (error_code != 0) { 329 + reason = fetch_type_error(js, error_message ? error_message : "fetch failed"); 330 + if (is_object_type(req->response_obj)) fetch_error_response_body(req, reason); 331 + else fetch_reject(req, reason); 332 + free_fetch_request(req); 181 333 return; 182 334 } 335 + 336 + if (is_object_type(req->response_obj)) { 337 + stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); 338 + if (rs_is_stream(stream)) { 339 + controller = rs_stream_controller(js, stream); 340 + rs_controller_close(js, controller); 341 + } 342 + } else fetch_reject(req, fetch_type_error(js, "fetch completed without a response")); 343 + 344 + free_fetch_request(req); 345 + } 346 + 347 + static char *fetch_data_url_content_type(const char *url) { 348 + const char *header = url + 5; 349 + const char *comma = strchr(header, ','); 350 + const char *base64 = NULL; 351 + size_t len = 0; 352 + 353 + if (!comma) return strdup("text/plain;charset=US-ASCII"); 354 + base64 = strstr(header, ";base64"); 355 + len = base64 && base64 < comma ? (size_t)(base64 - header) : (size_t)(comma - header); 356 + if (len == 0) return strdup("text/plain;charset=US-ASCII"); 183 357 184 - req->status_code = resp->code; 185 - resp->body_cb = body_cb; 358 + return strndup(header, len); 186 359 } 187 360 188 - static ant_value_t do_fetch_microtask(ant_t *js, ant_value_t *args, int nargs) { 189 - (void)args; 190 - (void)nargs; 361 + static bool fetch_handle_data_url(fetch_request_t *req) { 362 + ant_t *js = req->js; 363 + request_data_t *request = request_get_data(req->request_obj); 191 364 192 - ant_value_t current_func = js_getcurrentfunc(js); 193 - ant_value_t url_val = js_get(js, current_func, "url"); 194 - ant_value_t options_val = js_get(js, current_func, "options"); 195 - ant_value_t promise = js_get_slot(current_func, SLOT_DATA); 365 + char *url = fetch_build_request_url(request); 366 + size_t len = 0; 367 + char *body = NULL; 368 + char *content_type = NULL; 196 369 197 - char *url_str = js_getstr(js, url_val, NULL); 198 - if (!url_str) { 199 - ant_value_t err = js_mkstr(js, "Invalid URL", 11); 200 - js_reject_promise(js, promise, err); 201 - return js_mkundef(); 370 + ant_value_t headers = 0; 371 + ant_value_t response = 0; 372 + 373 + if (!url || !esm_is_data_url(url)) { 374 + free(url); 375 + return false; 202 376 } 203 - 204 - fetch_request_t *req = calloc(1, sizeof(fetch_request_t)); 205 - if (!req) { 206 - ant_value_t err = js_mkstr(js, "Out of memory", 13); 207 - js_reject_promise(js, promise, err); 208 - return js_mkundef(); 377 + 378 + body = esm_parse_data_url(url, &len); 379 + content_type = fetch_data_url_content_type(url); 380 + headers = headers_create_empty(js); 381 + 382 + if (!body || !content_type || is_err(headers)) { 383 + free(url); 384 + free(body); 385 + free(content_type); 386 + fetch_reject(req, fetch_type_error(js, "Failed to decode data URL")); 387 + free_fetch_request(req); 388 + return true; 389 + } 390 + 391 + headers_set_literal(js, headers, "content-type", content_type); 392 + response = response_create_fetched( 393 + js, 200, "OK", url, headers, (const uint8_t *)body, len, js_mkundef(), content_type 394 + ); 395 + 396 + free(url); 397 + free(body); 398 + free(content_type); 399 + 400 + if (is_err(response)) { 401 + fetch_reject(req, fetch_rejection_reason(js, response)); 402 + } else fetch_resolve(req, response); 403 + 404 + free_fetch_request(req); 405 + return true; 406 + } 407 + 408 + static void fetch_start_http(fetch_request_t *req) { 409 + request_data_t *request = request_get_data(req->request_obj); 410 + ant_http_request_options_t options = {0}; 411 + ant_http_header_t *headers = NULL; 412 + char *url = NULL; 413 + int rc = 0; 414 + 415 + if (!request) { 416 + fetch_reject(req, fetch_type_error(req->js, "Invalid Request object")); 417 + free_fetch_request(req); 418 + return; 209 419 } 210 - 211 - req->js = js; 212 - req->promise = promise; 213 - req->headers_obj = options_val; 214 - 215 - req->response_buffer.capacity = 16384; 216 - req->response_buffer.data = malloc(req->response_buffer.capacity); 217 - req->response_buffer.size = 0; 218 - 219 - if (!req->response_buffer.data) { 220 - ant_value_t err = js_mkstr(js, "Out of memory", 13); 221 - js_reject_promise(js, promise, err); 222 - free(req); 223 - return js_mkundef(); 420 + 421 + url = fetch_build_request_url(request); 422 + if (!url) { 423 + fetch_reject(req, fetch_type_error(req->js, "Invalid request URL")); 424 + free_fetch_request(req); 425 + return; 224 426 } 225 - 226 - utarray_push_back(pending_requests, &req); 227 - 228 - const char *scheme_end = strstr(url_str, "://"); 229 - if (!scheme_end) return fetch_fail_oom(js, promise, req, false, "Invalid URL: no scheme", 22); 230 - 231 - const char *host_start = scheme_end + 3; 232 - const char *path_start = strchr(host_start, '/'); 233 - const char *at_in_host = NULL; 234 - 235 - for (const char *p = host_start; p < (path_start ? path_start : host_start + strlen(host_start)); p++) { 236 - if (*p == '@') at_in_host = p; 237 - } if (at_in_host) host_start = at_in_host + 1; 238 - 239 - size_t scheme_len = scheme_end - url_str; 240 - size_t host_len = path_start ? (size_t)(path_start - host_start) : strlen(host_start); 241 - 242 - const char *path = path_start ? path_start : "/"; 243 - if (host_len == 0) return fetch_fail_oom(js, promise, req, false, "Invalid URL: no host", 20); 244 - 245 - char *host_url = calloc(1, scheme_len + 3 + host_len + 1); 246 - if (!host_url) return fetch_fail_oom(js, promise, req, false, "Out of memory", 13); 247 - snprintf(host_url, scheme_len + 3 + host_len + 1, "%.*s://%.*s", (int)scheme_len, url_str, (int)host_len, host_start); 248 - 249 - int rc = tlsuv_http_init(uv_default_loop(), &req->http_client, host_url); free(host_url); 250 - if (rc != 0) return fetch_fail_oom(js, promise, req, false, "Failed to initialize HTTP client", 33); 251 - 252 - req->http_client.data = req; 253 - req->method = NULL; 254 - req->body = NULL; 255 - req->body_len = 0; 256 - 257 - if (is_special_object(options_val)) { 258 - ant_value_t method_val = js_get(js, options_val, "method"); 259 - if (vtype(method_val) == T_STR) { 260 - char *str = js_getstr(js, method_val, NULL); 261 - if (str) { 262 - req->method = strdup(str); 263 - if (!req->method) return fetch_fail_oom(js, promise, req, true, "Out of memory", 13); 264 - } 265 - } 266 - 267 - ant_value_t body_val = js_get(js, options_val, "body"); 268 - if (vtype(body_val) == T_STR) { 269 - size_t len; 270 - char *str = js_getstr(js, body_val, &len); 271 - if (str && len > 0) { 272 - req->body = malloc(len); 273 - if (!req->body) return fetch_fail_oom(js, promise, req, true, "Out of memory", 13); 274 - memcpy(req->body, str, len); 275 - req->body_len = len; 276 - } 277 - } 427 + 428 + if (esm_is_data_url(url)) { 429 + free(url); 430 + fetch_handle_data_url(req); 431 + return; 278 432 } 279 - 280 - if (!req->method) { 281 - req->method = strdup("GET"); 282 - if (!req->method) return fetch_fail_oom(js, promise, req, true, "Out of memory", 13); 433 + if (!fetch_is_http_url(url)) { 434 + free(url); 435 + fetch_reject(req, fetch_type_error(req->js, "fetch only supports http:, https:, and data: URLs")); 436 + free_fetch_request(req); 437 + return; 283 438 } 284 - 285 - req->http_req = tlsuv_http_req(&req->http_client, req->method, path, resp_cb, req); 286 - if (!req->http_req) return fetch_fail_oom(js, promise, req, true, "Failed to create HTTP request", 30); 287 - req->http_req->data = req; 288 - 289 - char user_agent[256]; 290 - snprintf(user_agent, sizeof(user_agent), "ant/%s", ANT_VERSION); 291 - tlsuv_http_req_header(req->http_req, "User-Agent", user_agent); 292 - 293 - if (is_special_object(options_val)) { 294 - ant_value_t headers_val = js_get(js, options_val, "headers"); 295 - if (is_special_object(headers_val)) { 296 - ant_iter_t iter = js_prop_iter_begin(js, headers_val); 297 - const char *key; 298 - size_t key_len; 299 - ant_value_t value; 300 - 301 - while (js_prop_iter_next(&iter, &key, &key_len, &value)) { 302 - char *value_str = js_getstr(js, value, NULL); 303 - if (value_str) { 304 - char *key_str = strndup(key, key_len); 305 - if (key_str) { tlsuv_http_req_header(req->http_req, key_str, value_str); free(key_str); } 306 - } 307 - } 308 - js_prop_iter_end(&iter); 309 - } 439 + 440 + headers = fetch_build_http_headers(req->request_obj); 441 + if (!headers) { 442 + free(url); 443 + fetch_reject(req, fetch_type_error(req->js, "out of memory")); 444 + free_fetch_request(req); 445 + return; 310 446 } 311 - 312 - if (req->body && req->body_len > 0) { 313 - tlsuv_http_req_data(req->http_req, req->body, req->body_len, body_cb); 447 + 448 + options.method = request->method; 449 + options.url = url; 450 + options.headers = headers; 451 + options.body = request->body_data; 452 + options.body_len = request->body_size; 453 + 454 + rc = ant_http_request_start( 455 + uv_default_loop(), &options, 456 + fetch_http_on_response, fetch_http_on_body, fetch_http_on_complete, 457 + req, &req->http_req 458 + ); 459 + 460 + ant_http_headers_free(headers); 461 + free(url); 462 + 463 + if (rc != 0) { 464 + fetch_reject(req, fetch_type_error(req->js, uv_strerror(rc))); 465 + free_fetch_request(req); 314 466 } 315 - 467 + } 468 + 469 + static ant_value_t fetch_abort_listener(ant_t *js, ant_value_t *args, int nargs) { 470 + fetch_request_t *req = (fetch_request_t *)(uintptr_t)(size_t)js_getnum(js_get_slot(js->current_func, SLOT_DATA)); 471 + ant_value_t signal = 0; 472 + ant_value_t reason = 0; 473 + 474 + if (!req || req->aborted) return js_mkundef(); 475 + req->aborted = true; 476 + signal = request_get_signal(req->request_obj); 477 + reason = abort_signal_get_reason(signal); 478 + 479 + if (req->http_req) ant_http_request_cancel(req->http_req); 480 + fetch_reject(req, reason); 481 + if (!req->http_req) free_fetch_request(req); 482 + 316 483 return js_mkundef(); 317 484 } 318 485 319 486 static ant_value_t js_fetch(ant_t *js, ant_value_t *args, int nargs) { 320 - if (nargs < 1) return js_mkerr(js, "fetch requires at least 1 argument"); 321 - if (vtype(args[0]) != T_STR) return js_mkerr(js, "fetch URL must be a string"); 322 - 323 - ant_value_t url_val = args[0]; 324 - ant_value_t options_val = nargs > 1 ? args[1] : js_mkundef(); 325 - 487 + ant_value_t input = (nargs >= 1) ? args[0] : js_mkundef(); 488 + ant_value_t init = (nargs >= 2) ? args[1] : js_mkundef(); 326 489 ant_value_t promise = js_mkpromise(js); 327 - ant_value_t wrapper_obj = js_mkobj(js); 328 - 329 - js_set_slot(wrapper_obj, SLOT_DATA, promise); 330 - js_set_slot(wrapper_obj, SLOT_CFUNC, js_mkfun(do_fetch_microtask)); 331 - 332 - js_set(js, wrapper_obj, "url", url_val); 333 - js_set(js, wrapper_obj, "options", options_val); 334 - 335 - queue_microtask(js, js_obj_to_func(wrapper_obj)); 336 - 490 + ant_value_t request_obj = 0; 491 + request_data_t *request = NULL; 492 + fetch_request_t *req = NULL; 493 + ant_value_t signal = 0; 494 + 495 + request_obj = request_create_from_input_init(js, input, init); 496 + if (is_err(request_obj)) { 497 + js_reject_promise(js, promise, fetch_rejection_reason(js, request_obj)); 498 + return promise; 499 + } 500 + 501 + request = request_get_data(request_obj); 502 + if (!request) { 503 + js_reject_promise(js, promise, fetch_type_error(js, "Invalid Request object")); 504 + return promise; 505 + } 506 + 507 + if (request->body_is_stream) { 508 + js_reject_promise(js, promise, 509 + fetch_type_error(js, "fetch does not yet support ReadableStream request bodies")); 510 + return promise; 511 + } 512 + 513 + req = calloc(1, sizeof(fetch_request_t)); 514 + if (!req) { 515 + js_reject_promise(js, promise, fetch_type_error(js, "out of memory")); 516 + return promise; 517 + } 518 + 519 + req->js = js; 520 + req->promise = promise; 521 + req->request_obj = request_obj; 522 + utarray_push_back(pending_requests, &req); 523 + 524 + signal = request_get_signal(request_obj); 525 + if (abort_signal_is_signal(signal)) { 526 + if (abort_signal_is_aborted(signal)) { 527 + fetch_reject(req, abort_signal_get_reason(signal)); 528 + free_fetch_request(req); 529 + return promise; 530 + } 531 + 532 + req->abort_listener = js_heavy_mkfun(js, fetch_abort_listener, ANT_PTR(req)); 533 + abort_signal_add_listener(js, signal, req->abort_listener); 534 + } 535 + 536 + if (request->has_body) request->body_used = true; 537 + fetch_start_http(req); 337 538 return promise; 338 539 } 339 540 ··· 351 552 unsigned int len = utarray_len(pending_requests); 352 553 353 554 for (unsigned int i = 0; i < len; i++) { 354 - fetch_request_t **reqp = (fetch_request_t **)utarray_eltptr(pending_requests, i); 355 - if (reqp && *reqp) { 555 + fetch_request_t **reqp = (fetch_request_t **)utarray_eltptr(pending_requests, i); 556 + if (!reqp || !*reqp) continue; 356 557 mark(js, (*reqp)->promise); 357 - mark(js, (*reqp)->headers_obj); 358 - }} 558 + mark(js, (*reqp)->request_obj); 559 + mark(js, (*reqp)->response_obj); 560 + mark(js, (*reqp)->abort_listener); 561 + } 359 562 }