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.

add buffer support to fetch

+188 -25
+7 -7
README.md
··· 1 1 # 🐜 Ant 2 2 3 - **A 7MB JavaScript runtime with 1ms cold starts.** 3 + **A 8MB JavaScript runtime with 1ms cold starts.** 4 4 5 5 Ant is a lightweight, high-performance JavaScript runtime built from scratch. <br> 6 6 Fits in your pocket while Delivering near-V8 speeds in a binary smaller than most npm packages. 7 7 8 8 ``` 9 9 $ ls -lh ant 10 - -rwxr-xr-x⠀5.2M⠀ant* 10 + -rwxr-xr-x⠀8.1M⠀ant* 11 11 ``` 12 12 13 13 ## Table of contents ··· 24 24 25 25 | | Ant | Node | Bun | Deno | 26 26 | ------------------- | ----------- | --------- | -------- | --------- | 27 - | Binary size | **~7 MB** | ~120 MB | ~60 MB | ~90 MB | 27 + | Binary size | **~8 MB** | ~120 MB | ~60 MB | ~90 MB | 28 28 | Cold start | **~3-5 ms** | ~30-50 ms | ~5-10 ms | ~20-30 ms | 29 29 | Engine | Ant Silver | V8 | JSC | V8 | 30 30 | JIT | ✓ | ✓ | ✓ | ✓ | 31 - | WinterTC conformant | partial | partial | ✓ | ✓ | 31 + | WinterTC conformant | ✓ | partial | ✓ | ✓ | 32 32 33 33 Ant is designed for environments where size and startup time matter: serverless functions, edge computing, embedded systems, CLI tools, and anywhere you'd want JavaScript but can't afford a 50MB+ runtime. 34 34 ··· 45 45 46 46 ## Spec conformance 47 47 48 - Ant plans to fully target the [WinterTC Minimum Common API](https://min-common-api.proposal.wintertc.org/) specification, the standard for server-side JavaScript interoperability developed by Ecma TC55. 48 + Ant targets the [WinterTC Minimum Common API](https://min-common-api.proposal.wintertc.org/) specification, the standard for server-side JavaScript interoperability developed by Ecma TC55. 49 49 50 50 | Suite | Pass rate | Notes | 51 51 | ---------------- | --------- | ------------------------------------------ | 52 52 | js-zoo (ES1–ES5) | ~100% | | 53 53 | js-zoo (ES6) | ~80%\* | \*Generators unsupported | 54 - | js-zoo (ES2016+) | ~85% | | 55 - | test262 | ~40% | Improving, focus is on real-world coverage | 54 + | js-zoo (ES2016+) | ~90% | | 55 + | test262 | ~50% | Improving, focus is on real-world coverage | 56 56 57 57 ## Building Ant 58 58
+6
include/modules/http.h
··· 5 5 #include <types.h> 6 6 #include <stddef.h> 7 7 #include <stdint.h> 8 + #include <stdbool.h> 8 9 9 10 typedef struct ant_http_header_s { 10 11 char *name; ··· 18 19 const ant_http_header_t *headers; 19 20 const uint8_t *body; 20 21 size_t body_len; 22 + bool chunked_body; 21 23 } ant_http_request_options_t; 22 24 23 25 typedef struct { ··· 57 59 ); 58 60 59 61 int ant_http_request_cancel(ant_http_request_t *req); 62 + int ant_http_request_write(ant_http_request_t *req, const uint8_t *chunk, size_t len); 63 + 64 + void ant_http_request_end(ant_http_request_t *req); 60 65 void ant_http_headers_free(ant_http_header_t *headers); 66 + 61 67 const ant_http_response_t *ant_http_request_response(ant_http_request_t *req); 62 68 63 69 #endif
+148 -18
src/modules/fetch.c
··· 16 16 #include "esm/remote.h" 17 17 #include "gc/modules.h" 18 18 #include "modules/abort.h" 19 + #include "modules/assert.h" 19 20 #include "modules/buffer.h" 20 21 #include "modules/fetch.h" 21 22 #include "modules/headers.h" ··· 32 33 ant_value_t request_obj; 33 34 ant_value_t response_obj; 34 35 ant_value_t abort_listener; 36 + ant_value_t upload_reader; 37 + ant_value_t upload_read_promise; 35 38 ant_http_request_t *http_req; 36 39 40 + int refs; 37 41 bool settled; 38 42 bool aborted; 39 43 } fetch_request_t; 40 44 41 45 static UT_array *pending_requests = NULL; 46 + 47 + static void fetch_request_retain(fetch_request_t *req) { 48 + if (req) req->refs++; 49 + } 42 50 43 51 static void remove_pending_request(fetch_request_t *req) { 44 52 if (!req || !pending_requests) return; ··· 51 59 } 52 60 } 53 61 54 - static void free_fetch_request(fetch_request_t *req) { 62 + static void destroy_fetch_request(fetch_request_t *req) { 55 63 if (!req) return; 56 64 57 65 if (abort_signal_is_signal(request_get_signal(req->request_obj)) && is_callable(req->abort_listener)) ··· 59 67 60 68 remove_pending_request(req); 61 69 free(req); 70 + } 71 + 72 + static void fetch_request_release(fetch_request_t *req) { 73 + if (!req) return; 74 + if (--req->refs == 0) destroy_fetch_request(req); 62 75 } 63 76 64 77 static ant_value_t fetch_type_error(ant_t *js, const char *message) { ··· 329 342 reason = fetch_type_error(js, error_message ? error_message : "fetch failed"); 330 343 if (is_object_type(req->response_obj)) fetch_error_response_body(req, reason); 331 344 else fetch_reject(req, reason); 332 - free_fetch_request(req); 345 + fetch_request_release(req); 333 346 return; 334 347 } 335 348 ··· 341 354 } 342 355 } else fetch_reject(req, fetch_type_error(js, "fetch completed without a response")); 343 356 344 - free_fetch_request(req); 357 + fetch_request_release(req); 345 358 } 346 359 347 360 static char *fetch_data_url_content_type(const char *url) { ··· 384 397 free(body); 385 398 free(content_type); 386 399 fetch_reject(req, fetch_type_error(js, "Failed to decode data URL")); 387 - free_fetch_request(req); 400 + fetch_request_release(req); 388 401 return true; 389 402 } 390 403 ··· 401 414 fetch_reject(req, fetch_rejection_reason(js, response)); 402 415 } else fetch_resolve(req, response); 403 416 404 - free_fetch_request(req); 417 + fetch_request_release(req); 405 418 return true; 406 419 } 407 420 421 + static ant_value_t fetch_upload_on_reject(ant_t *js, ant_value_t *args, int nargs) { 422 + fetch_request_t *req = (fetch_request_t *)(uintptr_t)(size_t)js_getnum(js_get_slot(js->current_func, SLOT_DATA)); 423 + ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef(); 424 + 425 + if (!req) return js_mkundef(); 426 + req->upload_read_promise = js_mkundef(); 427 + 428 + if (!req->aborted) { 429 + if (req->http_req) ant_http_request_cancel(req->http_req); 430 + fetch_reject(req, reason); 431 + if (!req->http_req) fetch_request_release(req); 432 + } 433 + 434 + fetch_request_release(req); 435 + return js_mkundef(); 436 + } 437 + 438 + static void fetch_upload_schedule_next_read(fetch_request_t *req); 439 + static ant_value_t fetch_upload_on_read(ant_t *js, ant_value_t *args, int nargs) { 440 + fetch_request_t *req = (fetch_request_t *)(uintptr_t)(size_t)js_getnum(js_get_slot(js->current_func, SLOT_DATA)); 441 + 442 + ant_value_t result = (nargs > 0) ? args[0] : js_mkundef(); 443 + ant_value_t done = 0; 444 + ant_value_t value = 0; 445 + 446 + const uint8_t *chunk = NULL; 447 + size_t chunk_len = 0; 448 + int rc = 0; 449 + 450 + if (!req) return js_mkundef(); 451 + req->upload_read_promise = js_mkundef(); 452 + 453 + if (req->aborted || !req->http_req) { 454 + fetch_request_release(req); 455 + return js_mkundef(); 456 + } 457 + 458 + done = js_get(js, result, "done"); 459 + value = js_get(js, result, "value"); 460 + if (done == js_true) { 461 + ant_http_request_end(req->http_req); 462 + fetch_request_release(req); 463 + return js_mkundef(); 464 + } 465 + 466 + if (!buffer_source_get_bytes(js, value, &chunk, &chunk_len)) { 467 + ant_value_t reason = js_mkerr_typed(js, JS_ERR_TYPE, "fetch request body stream chunk must be a BufferSource"); 468 + ant_http_request_cancel(req->http_req); 469 + fetch_reject(req, fetch_rejection_reason(js, reason)); 470 + fetch_request_release(req); 471 + return js_mkundef(); 472 + } 473 + 474 + rc = ant_http_request_write(req->http_req, chunk, chunk_len); 475 + if (rc != 0) { 476 + ant_value_t reason = fetch_type_error(js, uv_strerror(rc)); 477 + ant_http_request_cancel(req->http_req); 478 + fetch_reject(req, reason); 479 + fetch_request_release(req); 480 + return js_mkundef(); 481 + } 482 + 483 + fetch_upload_schedule_next_read(req); 484 + fetch_request_release(req); 485 + return js_mkundef(); 486 + } 487 + 488 + static void fetch_upload_schedule_next_read(fetch_request_t *req) { 489 + ant_t *js = req->js; 490 + 491 + ant_value_t next_p = 0; 492 + ant_value_t fulfill = 0; 493 + ant_value_t reject = 0; 494 + ant_value_t then_result = 0; 495 + 496 + if (!req || !is_object_type(req->upload_reader)) return; 497 + next_p = rs_default_reader_read(js, req->upload_reader); 498 + req->upload_read_promise = next_p; 499 + 500 + fulfill = js_heavy_mkfun(js, fetch_upload_on_read, ANT_PTR(req)); 501 + reject = js_heavy_mkfun(js, fetch_upload_on_reject, ANT_PTR(req)); 502 + 503 + fetch_request_retain(req); 504 + then_result = js_promise_then(js, next_p, fulfill, reject); 505 + promise_mark_handled(then_result); 506 + } 507 + 508 + static void fetch_start_upload(fetch_request_t *req) { 509 + ant_t *js = req->js; 510 + 511 + ant_value_t stream = js_get_slot(req->request_obj, SLOT_REQUEST_BODY_STREAM); 512 + ant_value_t reader_args[1] = { stream }; 513 + ant_value_t saved = js->new_target; 514 + ant_value_t reader = 0; 515 + 516 + if (!rs_is_stream(stream)) return; 517 + 518 + js->new_target = g_reader_proto; 519 + reader = js_rs_reader_ctor(js, reader_args, 1); 520 + js->new_target = saved; 521 + 522 + if (is_err(reader)) { 523 + if (req->http_req) ant_http_request_cancel(req->http_req); 524 + fetch_reject(req, fetch_rejection_reason(js, reader)); 525 + if (!req->http_req) fetch_request_release(req); 526 + return; 527 + } 528 + 529 + req->upload_reader = reader; 530 + fetch_upload_schedule_next_read(req); 531 + } 532 + 408 533 static void fetch_start_http(fetch_request_t *req) { 409 534 request_data_t *request = request_get_data(req->request_obj); 410 535 ant_http_request_options_t options = {0}; ··· 414 539 415 540 if (!request) { 416 541 fetch_reject(req, fetch_type_error(req->js, "Invalid Request object")); 417 - free_fetch_request(req); 542 + fetch_request_release(req); 418 543 return; 419 544 } 420 545 421 546 url = fetch_build_request_url(request); 422 547 if (!url) { 423 548 fetch_reject(req, fetch_type_error(req->js, "Invalid request URL")); 424 - free_fetch_request(req); 549 + fetch_request_release(req); 425 550 return; 426 551 } 427 552 ··· 433 558 if (!fetch_is_http_url(url)) { 434 559 free(url); 435 560 fetch_reject(req, fetch_type_error(req->js, "fetch only supports http:, https:, and data: URLs")); 436 - free_fetch_request(req); 561 + fetch_request_release(req); 437 562 return; 438 563 } 439 564 ··· 441 566 if (!headers) { 442 567 free(url); 443 568 fetch_reject(req, fetch_type_error(req->js, "out of memory")); 444 - free_fetch_request(req); 569 + fetch_request_release(req); 445 570 return; 446 571 } 447 572 ··· 450 575 options.headers = headers; 451 576 options.body = request->body_data; 452 577 options.body_len = request->body_size; 578 + options.chunked_body = request->body_is_stream; 453 579 454 580 rc = ant_http_request_start( 455 581 uv_default_loop(), &options, ··· 462 588 463 589 if (rc != 0) { 464 590 fetch_reject(req, fetch_type_error(req->js, uv_strerror(rc))); 465 - free_fetch_request(req); 591 + fetch_request_release(req); 592 + return; 466 593 } 594 + 595 + if (request->body_is_stream) fetch_start_upload(req); 467 596 } 468 597 469 598 static ant_value_t fetch_abort_listener(ant_t *js, ant_value_t *args, int nargs) { ··· 478 607 479 608 if (req->http_req) ant_http_request_cancel(req->http_req); 480 609 fetch_reject(req, reason); 481 - if (!req->http_req) free_fetch_request(req); 610 + if (!req->http_req) fetch_request_release(req); 482 611 483 612 return js_mkundef(); 484 613 } ··· 504 633 return promise; 505 634 } 506 635 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 636 req = calloc(1, sizeof(fetch_request_t)); 514 637 if (!req) { 515 638 js_reject_promise(js, promise, fetch_type_error(js, "out of memory")); ··· 519 642 req->js = js; 520 643 req->promise = promise; 521 644 req->request_obj = request_obj; 645 + req->response_obj = js_mkundef(); 646 + req->abort_listener = js_mkundef(); 647 + req->upload_reader = js_mkundef(); 648 + req->upload_read_promise = js_mkundef(); 649 + req->refs = 1; 522 650 utarray_push_back(pending_requests, &req); 523 651 524 652 signal = request_get_signal(request_obj); 525 653 if (abort_signal_is_signal(signal)) { 526 654 if (abort_signal_is_aborted(signal)) { 527 655 fetch_reject(req, abort_signal_get_reason(signal)); 528 - free_fetch_request(req); 656 + fetch_request_release(req); 529 657 return promise; 530 658 } 531 659 ··· 558 686 mark(js, (*reqp)->request_obj); 559 687 mark(js, (*reqp)->response_obj); 560 688 mark(js, (*reqp)->abort_listener); 689 + mark(js, (*reqp)->upload_reader); 690 + mark(js, (*reqp)->upload_read_promise); 561 691 } 562 692 }
+27
src/modules/http.c
··· 43 43 free(req); 44 44 } 45 45 46 + static void ant_http_upload_chunk_cb(tlsuv_http_req_t *http_req, char *body, ssize_t len) { 47 + free(body); 48 + } 49 + 46 50 static char *ant_http_copy_slice(const char *src, size_t len) { 47 51 char *out = malloc(len + 1); 48 52 if (!out) return NULL; ··· 236 240 return tlsuv_http_req_cancel(&req->client, req->req); 237 241 } 238 242 243 + int ant_http_request_write(ant_http_request_t *req, const uint8_t *chunk, size_t len) { 244 + uint8_t *copy = NULL; 245 + int rc = 0; 246 + 247 + if (!req || !req->req || req->completed) return UV_EINVAL; 248 + if (len == 0) return 0; 249 + 250 + copy = malloc(len); 251 + if (!copy) return UV_ENOMEM; 252 + memcpy(copy, chunk, len); 253 + 254 + rc = tlsuv_http_req_data(req->req, (const char *)copy, len, ant_http_upload_chunk_cb); 255 + if (rc != 0) free(copy); 256 + return rc; 257 + } 258 + 259 + void ant_http_request_end(ant_http_request_t *req) { 260 + if (!req || !req->req || req->completed) return; 261 + tlsuv_http_req_end(req->req); 262 + } 263 + 239 264 int ant_http_request_start( 240 265 uv_loop_t *loop, 241 266 const ant_http_request_options_t *options, ··· 298 323 } 299 324 300 325 req->req->data = req; 326 + if (options->chunked_body) tlsuv_http_req_header(req->req, "transfer-encoding", "chunked"); 301 327 for (const ant_http_header_t *hdr = options->headers; hdr; hdr = hdr->next) { 302 328 tlsuv_http_req_header(req->req, hdr->name, hdr->value); 303 329 } 304 330 305 331 if (options->body && options->body_len > 0) { 306 332 rc = tlsuv_http_req_data(req->req, (const char *)options->body, options->body_len, NULL); 333 + 307 334 if (rc != 0) { 308 335 ant_http_complete(req, rc, uv_strerror(rc)); 309 336 if (out_req) *out_req = req;