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.

better server utils

+314 -72
+32 -7
examples/server/server.js
··· 9 9 router.get('/', c => c.res.body(`Welcome to Ant ${Ant.version}!`)); 10 10 11 11 router.get('/meow', async c => { 12 - return c.res.body(meow); 12 + const userAgent = c.req.header('User-Agent'); 13 + c.res.header('X-Ant', 'meow'); 14 + 15 + return c.res.body(`${meow}\n\n${userAgent}`); 16 + }); 17 + 18 + router.get('/echo', c => 19 + fetch('http://localhost:8000/meow').then(res => { 20 + c.res.header('X-Ant', 'meow'); 21 + c.res.body(res.body); 22 + }) 23 + ); 24 + 25 + router.get('/get', c => { 26 + console.log(c.get('meow')); 27 + c.res.body(c.get('meow')); 28 + }); 29 + 30 + router.get('/set/1', c => { 31 + c.set('meow', '1'); 32 + c.res.body('meow = 1'); 33 + }); 34 + 35 + router.get('/set/2', c => { 36 + c.set('meow', '2'); 37 + c.res.body('meow = 2'); 13 38 }); 14 39 15 40 router.get('/fs/meow', async c => { ··· 56 81 router.printTree(); 57 82 console.log(''); 58 83 59 - async function handleRequest(req, res) { 60 - console.log('request:', req.method, req.uri); 61 - const result = router.lookup(req.uri, req.method); 84 + async function handleRequest(c) { 85 + console.log('request:', c.req.method, c.req.uri); 86 + const result = router.lookup(c.req.uri, c.req.method); 62 87 63 88 if (result?.handler) { 64 - const ctx = { req, res, params: result.params }; 65 - return await result.handler(ctx); 89 + c.params = result.params; 90 + return await result.handler(c); 66 91 } 67 92 68 - res.body('not found: ' + req.uri, 404); 93 + c.res.body('not found: ' + c.req.uri, 404); 69 94 } 70 95 71 96 console.log('started on http://localhost:8000');
+6 -6
examples/spa/server.js
··· 57 57 router.printTree(); 58 58 console.log(''); 59 59 60 - async function handleRequest(req, res) { 61 - console.log('request:', req.method, req.uri); 62 - const result = router.lookup(req.uri, req.method); 60 + async function handleRequest(c) { 61 + console.log('request:', c.req.method, c.req.uri); 62 + const result = router.lookup(c.req.uri, c.req.method); 63 63 64 64 if (result?.handler) { 65 - const ctx = { req, res, params: result.params }; 66 - return await result.handler(ctx); 65 + c.params = result.params; 66 + return await result.handler(c); 67 67 } 68 68 69 - res.body('not found: ' + req.uri, 404); 69 + c.res.body('not found: ' + c.req.uri, 404); 70 70 } 71 71 72 72 console.log('started on http://localhost:8000');
+1 -1
meson.build
··· 74 74 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 75 75 76 76 version_conf = configuration_data() 77 - version_conf.set('ANT_VERSION', '0.1.0.12') 77 + version_conf.set('ANT_VERSION', '0.1.0.13') 78 78 version_conf.set('ANT_GIT_HASH', git_hash) 79 79 version_conf.set('ANT_BUILD_DATE', build_date) 80 80
+13 -1
src/ant.c
··· 497 497 static void free_coroutine(coroutine_t *coro); 498 498 static bool has_ready_coroutines(void); 499 499 500 + static size_t calculate_coro_stack_size(void) { 501 + const char *env_stack = getenv("ANT_CORO_STACK_SIZE"); 502 + if (env_stack) { 503 + size_t size = (size_t)atoi(env_stack) * 1024; 504 + if (size >= 16 * 1024 && size <= 8 * 1024 * 1024) return size; 505 + } 506 + 507 + return 128 * 1024; 508 + } 509 + 500 510 static void mco_async_entry(mco_coro* mco) { 501 511 async_exec_context_t *ctx = (async_exec_context_t *)mco_get_user_data(mco); 502 512 ··· 641 651 ctx->has_error = false; 642 652 ctx->coro = NULL; 643 653 644 - mco_desc desc = mco_desc_init(mco_async_entry, 0); 654 + size_t stack_size = calculate_coro_stack_size(); 655 + mco_desc desc = mco_desc_init(mco_async_entry, stack_size); 645 656 desc.user_data = ctx; 646 657 647 658 mco_coro* mco = NULL; ··· 706 717 static void free_coroutine(coroutine_t *coro) { 707 718 if (coro) { 708 719 if (coro->mco) { 720 + if (mco_running() == coro->mco) fprintf(stderr, "WARNING: Attempting to free a running coroutine\n"); 709 721 mco_destroy(coro->mco); 710 722 coro->mco = NULL; 711 723 }
+232 -7
src/modules/server.c
··· 5 5 #include <unistd.h> 6 6 #include <uv.h> 7 7 #include <zlib.h> 8 + #include <utarray.h> 8 9 9 10 #include "ant.h" 10 11 #include "config.h" ··· 16 17 #define MAX_WRITE_HANDLES 1000 17 18 #define READ_BUFFER_SIZE 8192 18 19 #define GZIP_MIN_SIZE 1024 20 + #define MAX_HEADER_NAME_LEN 128 21 + #define MAX_HEADER_VALUE_LEN 2048 22 + 23 + typedef struct { 24 + char name[MAX_HEADER_NAME_LEN]; 25 + char value[MAX_HEADER_VALUE_LEN]; 26 + } http_header_t; 27 + 28 + typedef struct { 29 + char *name; 30 + char *value; 31 + } custom_header_t; 32 + 33 + UT_icd header_icd = {sizeof(http_header_t), NULL, NULL, NULL}; 34 + UT_icd custom_header_icd = {sizeof(custom_header_t), NULL, NULL, NULL}; 19 35 20 36 typedef struct response_ctx_s { 21 37 int status; ··· 24 40 char *content_type; 25 41 int sent; 26 42 int supports_gzip; 43 + UT_array *custom_headers; 44 + char *redirect_location; 27 45 uv_tcp_t *client_handle; 28 46 struct response_ctx_s *next; 29 47 } response_ctx_t; ··· 190 208 char *body; 191 209 size_t body_len; 192 210 int accepts_gzip; 211 + UT_array *headers; 193 212 } http_request_t; 194 213 195 214 static int parse_http_request(const char *buffer, size_t len, http_request_t *req) { ··· 222 241 req->query[0] = '\0'; 223 242 } 224 243 244 + utarray_new(req->headers, &header_icd); 245 + 246 + const char *header_start = strstr(buffer, "\r\n"); 247 + if (header_start) { 248 + header_start += 2; 249 + 250 + while (header_start && header_start < buffer + len) { 251 + if (header_start[0] == '\r' && header_start[1] == '\n') break; 252 + 253 + const char *colon = strchr(header_start, ':'); 254 + const char *line_end = strstr(header_start, "\r\n"); 255 + 256 + if (colon && line_end && colon < line_end) { 257 + size_t name_len = colon - header_start; 258 + if (name_len < MAX_HEADER_NAME_LEN) { 259 + http_header_t header; 260 + memcpy(header.name, header_start, name_len); 261 + header.name[name_len] = '\0'; 262 + 263 + const char *value_start = colon + 1; 264 + while (*value_start == ' ') value_start++; 265 + 266 + size_t value_len = line_end - value_start; 267 + if (value_len < MAX_HEADER_VALUE_LEN) { 268 + memcpy(header.value, value_start, value_len); 269 + header.value[value_len] = '\0'; 270 + utarray_push_back(req->headers, &header); 271 + } 272 + } 273 + 274 + header_start = line_end + 2; 275 + } else break; 276 + } 277 + } 278 + 225 279 req->accepts_gzip = parse_accept_encoding(buffer, len); 226 280 227 281 const char *body_start = strstr(buffer, "\r\n\r\n"); ··· 250 304 free(req->body); 251 305 req->body = NULL; 252 306 } 307 + if (req->headers) { 308 + utarray_free(req->headers); 309 + req->headers = NULL; 310 + } 253 311 } 254 312 255 313 static void on_close(uv_handle_t *handle); 256 314 static void send_response(uv_stream_t *client, response_ctx_t *res_ctx); 257 315 316 + static jsval_t js_set_prop(struct js *js, jsval_t *args, int nargs) { 317 + if (nargs < 2) return js_mkundef(); 318 + 319 + jsval_t this_val = js_getthis(js); 320 + jsval_t var_obj = js_get(js, this_val, "var"); 321 + 322 + if (js_type(var_obj) == JS_UNDEF) { 323 + return js_mkundef(); 324 + } 325 + 326 + if (js_type(args[0]) == JS_STR) { 327 + size_t key_len; 328 + const char *key = js_getstr(js, args[0], &key_len); 329 + js_set(js, var_obj, key, args[1]); 330 + } 331 + 332 + return js_mkundef(); 333 + } 334 + 335 + static jsval_t js_get_prop(struct js *js, jsval_t *args, int nargs) { 336 + if (nargs < 1) return js_mkundef(); 337 + 338 + jsval_t this_val = js_getthis(js); 339 + jsval_t var_obj = js_get(js, this_val, "var"); 340 + 341 + if (js_type(var_obj) == JS_UNDEF) { 342 + return js_mkundef(); 343 + } 344 + 345 + if (js_type(args[0]) == JS_STR) { 346 + size_t key_len; 347 + const char *key = js_getstr(js, args[0], &key_len); 348 + return js_get(js, var_obj, key); 349 + } 350 + 351 + return js_mkundef(); 352 + } 353 + 354 + static jsval_t req_header(struct js *js, jsval_t *args, int nargs) { 355 + if (nargs < 1) return js_mkundef(); 356 + 357 + jsval_t this_val = js_getthis(js); 358 + jsval_t headers_val = js_get(js, this_val, "__headers"); 359 + if (js_type(headers_val) != JS_NUM) return js_mkundef(); 360 + 361 + UT_array *headers = (UT_array *)(unsigned long)js_getnum(headers_val); 362 + if (!headers) return js_mkundef(); 363 + 364 + if (js_type(args[0]) != JS_STR) return js_mkundef(); 365 + size_t name_len; 366 + const char *search_name = js_getstr(js, args[0], &name_len); 367 + 368 + http_header_t *header = NULL; 369 + while ((header = (http_header_t*)utarray_next(headers, header))) { 370 + if (strcasecmp(header->name, search_name) == 0) { 371 + return js_mkstr(js, header->value, strlen(header->value)); 372 + } 373 + } 374 + 375 + return js_mkundef(); 376 + } 377 + 378 + static jsval_t res_header(struct js *js, jsval_t *args, int nargs) { 379 + if (nargs < 2) return js_mkundef(); 380 + 381 + jsval_t this_val = js_getthis(js); 382 + jsval_t ctx_val = js_get(js, this_val, "__response_ctx"); 383 + if (js_type(ctx_val) != JS_NUM) return js_mkundef(); 384 + 385 + response_ctx_t *ctx = (response_ctx_t *)(unsigned long)js_getnum(ctx_val); 386 + if (!ctx || !ctx->custom_headers) return js_mkundef(); 387 + 388 + if (js_type(args[0]) == JS_STR && js_type(args[1]) == JS_STR) { 389 + custom_header_t header; 390 + header.name = js_getstr(js, args[0], NULL); 391 + header.value = js_getstr(js, args[1], NULL); 392 + utarray_push_back(ctx->custom_headers, &header); 393 + } 394 + 395 + return js_mkundef(); 396 + } 397 + 258 398 static jsval_t res_status(struct js *js, jsval_t *args, int nargs) { 259 399 if (nargs < 1) return js_mkundef(); 260 400 ··· 358 498 return js_mkundef(); 359 499 } 360 500 501 + static jsval_t res_notFound(struct js *js, jsval_t *args, int nargs) { 502 + jsval_t this_val = js_getthis(js); 503 + jsval_t ctx_val = js_get(js, this_val, "__response_ctx"); 504 + if (js_type(ctx_val) != JS_NUM) return js_mkundef(); 505 + 506 + response_ctx_t *ctx = (response_ctx_t *)(unsigned long)js_getnum(ctx_val); 507 + if (!ctx) return js_mkundef(); 508 + 509 + ctx->status = 404; 510 + ctx->body = "not found\nant http v" ANT_VERSION " (" ANT_GIT_HASH ")"; 511 + ctx->body_len = strlen(ctx->body); 512 + ctx->content_type = "text/plain"; 513 + ctx->sent = 1; 514 + 515 + return js_mkundef(); 516 + } 517 + 518 + static jsval_t res_redirect(struct js *js, jsval_t *args, int nargs) { 519 + if (nargs < 1) return js_mkundef(); 520 + 521 + jsval_t this_val = js_getthis(js); 522 + jsval_t ctx_val = js_get(js, this_val, "__response_ctx"); 523 + if (js_type(ctx_val) != JS_NUM) return js_mkundef(); 524 + 525 + response_ctx_t *ctx = (response_ctx_t *)(unsigned long)js_getnum(ctx_val); 526 + if (!ctx) return js_mkundef(); 527 + 528 + if (js_type(args[0]) == JS_STR) { 529 + ctx->redirect_location = js_getstr(js, args[0], NULL); 530 + } 531 + 532 + ctx->status = 302; 533 + if (nargs >= 2 && js_type(args[1]) == JS_NUM) { 534 + ctx->status = (int)js_getnum(args[1]); 535 + } 536 + 537 + ctx->body = ""; 538 + ctx->body_len = 0; 539 + ctx->content_type = "text/plain"; 540 + ctx->sent = 1; 541 + 542 + return js_mkundef(); 543 + } 544 + 361 545 static char* gzip_compress(const char *data, size_t data_len, size_t *compressed_len) { 362 546 z_stream stream; 363 547 memset(&stream, 0, sizeof(stream)); ··· 420 604 } 421 605 } 422 606 423 - char header[4096]; 607 + char header[8192]; 424 608 int header_len = snprintf(header, sizeof(header), 425 609 "HTTP/1.1 %d %s\r\n" 426 610 "Content-Type: %s\r\n" 427 611 "Content-Length: %zu\r\n" 428 612 "%s" 429 - "Connection: close\r\n" 430 - "\r\n", 613 + "%s", 431 614 res_ctx->status, 432 615 get_status_text(res_ctx->status), 433 616 res_ctx->content_type ? res_ctx->content_type : "text/plain", 434 617 body_len_to_send, 435 - use_gzip ? "Content-Encoding: gzip\r\n" : "" 618 + use_gzip ? "Content-Encoding: gzip\r\n" : "", 619 + res_ctx->redirect_location ? "Location: " : "" 436 620 ); 621 + 622 + if (res_ctx->redirect_location) { 623 + header_len += snprintf(header + header_len, sizeof(header) - header_len, 624 + "%s\r\n", res_ctx->redirect_location); 625 + } 626 + 627 + if (res_ctx->custom_headers) { 628 + custom_header_t *custom_header = NULL; 629 + while ((custom_header = (custom_header_t*)utarray_next(res_ctx->custom_headers, custom_header))) { 630 + if (custom_header->name && custom_header->value) { 631 + header_len += snprintf(header + header_len, sizeof(header) - header_len, 632 + "%s: %s\r\n", custom_header->name, custom_header->value); 633 + } 634 + } 635 + } 636 + 637 + header_len += snprintf(header + header_len, sizeof(header) - header_len, 638 + "Connection: close\r\n\r\n"); 437 639 438 640 size_t total_len = header_len + body_len_to_send; 439 641 ··· 476 678 res_ctx->content_type = "text/plain"; 477 679 res_ctx->sent = 0; 478 680 res_ctx->supports_gzip = http_req->accepts_gzip; 681 + utarray_new(res_ctx->custom_headers, &custom_header_icd); 682 + res_ctx->redirect_location = NULL; 479 683 res_ctx->client_handle = &client->handle; 480 684 res_ctx->next = NULL; 481 685 ··· 484 688 server->pending_responses = res_ctx; 485 689 486 690 if (server->handler != 0 && js_type(server->handler) != JS_UNDEF) { 691 + jsval_t ctx = js_mkobj(server->js); 692 + 487 693 jsval_t req = js_mkobj(server->js); 488 694 js_set(server->js, req, "method", js_mkstr(server->js, http_req->method, strlen(http_req->method))); 489 695 js_set(server->js, req, "uri", js_mkstr(server->js, http_req->uri, strlen(http_req->uri))); 490 696 js_set(server->js, req, "query", js_mkstr(server->js, http_req->query, strlen(http_req->query))); 491 697 js_set(server->js, req, "body", js_mkstr(server->js, http_req->body ? http_req->body : "", http_req->body ? http_req->body_len : 0)); 698 + js_set(server->js, req, "__headers", js_mknum((unsigned long)http_req->headers)); 699 + js_set(server->js, req, "header", js_mkfun(req_header)); 492 700 493 701 jsval_t res_obj = js_mkobj(server->js); 494 702 js_set(server->js, res_obj, "__response_ctx", js_mknum((unsigned long)res_ctx)); 703 + js_set(server->js, res_obj, "header", js_mkfun(res_header)); 495 704 js_set(server->js, res_obj, "status", js_mkfun(res_status)); 496 705 js_set(server->js, res_obj, "body", js_mkfun(res_body)); 497 706 js_set(server->js, res_obj, "html", js_mkfun(res_html)); 498 707 js_set(server->js, res_obj, "json", js_mkfun(res_json)); 708 + js_set(server->js, res_obj, "notFound", js_mkfun(res_notFound)); 709 + js_set(server->js, res_obj, "redirect", js_mkfun(res_redirect)); 710 + 711 + js_set(server->js, ctx, "req", req); 712 + js_set(server->js, ctx, "res", res_obj); 713 + js_set(server->js, ctx, "var", js_mkobj(server->js)); 714 + js_set(server->js, ctx, "set", js_mkfun(js_set_prop)); 715 + js_set(server->js, ctx, "get", js_mkfun(js_get_prop)); 499 716 500 - jsval_t args[2] = {req, res_obj}; 501 - result = js_call(server->js, server->handler, args, 2); 502 - if (js_type(result) == JS_PROMISE) return; 717 + jsval_t args[1] = {ctx}; 718 + result = js_call(server->js, server->handler, args, 1); 719 + if (js_type(result) == JS_PROMISE) { 720 + return; 721 + } 503 722 504 723 if (js_type(result) == JS_ERR) { 505 724 fprintf(stderr, "Handler error: %s\n", js_str(server->js, result)); ··· 547 766 if (!uv_is_closing((uv_handle_t *)ctx->client_handle)) { 548 767 send_response((uv_stream_t *)ctx->client_handle, ctx); 549 768 uv_close((uv_handle_t *)ctx->client_handle, on_close); 769 + } 770 + 771 + if (ctx->custom_headers) { 772 + utarray_free(ctx->custom_headers); 550 773 } 551 774 552 775 free(ctx); ··· 611 834 res_ctx->content_type = "text/plain"; 612 835 res_ctx->sent = 1; 613 836 res_ctx->supports_gzip = 0; 837 + utarray_new(res_ctx->custom_headers, &custom_header_icd); 838 + res_ctx->redirect_location = NULL; 614 839 res_ctx->client_handle = &client->handle; 615 840 res_ctx->next = client->server->pending_responses; 616 841 client->server->pending_responses = res_ctx;
+4 -10
tests/multi_server.cjs
··· 1 1 // Example: Running multiple HTTP servers on different ports 2 2 3 - function handler8000(req, res) { 4 - return { 5 - status: 200, 6 - body: "Server on port 8000\nURI: " + req.uri 7 - }; 3 + function handler8000(c) { 4 + c.res.body("Server on port 8000\nURI: " + c.req.uri); 8 5 } 9 6 10 - function handler8001(req, res) { 11 - return { 12 - status: 200, 13 - body: "Server on port 8001\nURI: " + req.uri 14 - }; 7 + function handler8001(c) { 8 + c.res.body("Server on port 8001\nURI: " + c.req.uri); 15 9 } 16 10 17 11 console.log("Starting multiple HTTP servers...");
+9 -9
tests/server_example.cjs
··· 1 1 // Example HTTP server using Ant.serve 2 2 3 3 // Define a request handler 4 - function handleRequest(req, res) { 5 - console.log("Received request:", req.method, req.uri); 6 - 7 - // Return a response object 8 - return { 9 - status: 200, 10 - body: "Hello from Ant HTTP Server!\nMethod: " + req.method + "\nURI: " + req.uri 11 - }; 4 + function handleRequest(c) { 5 + console.log('Received request:', c.req.method, c.req.uri); 6 + 7 + const userAgent = c.req.header('User-Agent'); 8 + c.res.header('X-Message', 'My custom message'); 9 + 10 + // Return a response 11 + c.res.body('Hello from Ant HTTP Server!\nMethod: ' + c.req.method + '\nURI: ' + c.req.uri + '\nUser-Agent: ' + userAgent); 12 12 } 13 13 14 14 // Start the server on port 8000 15 - console.log("Starting HTTP server..."); 15 + console.log('Starting HTTP server...'); 16 16 Ant.serve(8000, handleRequest);
+17 -31
tests/server_routes.cjs
··· 1 1 // Example HTTP server with basic routing 2 2 3 - function handleRequest(req, res) { 4 - console.log("Request:", req.method, req.uri); 3 + function handleRequest(c) { 4 + console.log("Request:", c.req.method, c.req.uri); 5 5 6 6 // Simple routing based on URI 7 - if (req.uri === "/") { 8 - return { 9 - status: 200, 10 - body: "Welcome to Ant HTTP Server!\n\nAvailable routes:\n GET /\n GET /hello\n GET /status\n GET /echo" 11 - }; 7 + if (c.req.uri === "/") { 8 + c.res.body("Welcome to Ant HTTP Server!\n\nAvailable routes:\n GET /\n GET /hello\n GET /status\n GET /echo"); 12 9 } 13 - 14 - if (req.uri === "/hello") { 15 - return { 16 - status: 200, 17 - body: "Hello, World!" 18 - }; 10 + else if (c.req.uri === "/hello") { 11 + c.res.body("Hello, World!"); 19 12 } 20 - 21 - if (req.uri === "/status") { 22 - return { 23 - status: 200, 24 - body: "Server is running!" 25 - }; 13 + else if (c.req.uri === "/status") { 14 + c.res.header('X-Server', 'Ant'); 15 + c.res.body("Server is running!"); 26 16 } 27 - 28 - if (req.uri === "/echo") { 29 - return { 30 - status: 200, 31 - body: "Method: " + req.method + "\nURI: " + req.uri + "\nQuery: " + req.query + "\nBody: " + req.body 32 - }; 17 + else if (c.req.uri === "/echo") { 18 + const userAgent = c.req.header('User-Agent') || 'Unknown'; 19 + c.res.body("Method: " + c.req.method + "\nURI: " + c.req.uri + "\nQuery: " + c.req.query + "\nBody: " + c.req.body + "\nUser-Agent: " + userAgent); 33 20 } 34 - 35 - // 404 for unknown routes 36 - return { 37 - status: 404, 38 - body: "Not Found: " + req.uri 39 - }; 21 + else { 22 + // 404 for unknown routes 23 + c.res.status(404); 24 + c.res.body("Not Found: " + c.req.uri); 25 + } 40 26 } 41 27 42 28 console.log("Starting HTTP server on port 8000...");