Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265#include <compat.h> // IWYU pragma: keep
#include <stdbool.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <strings.h>#include <signal.h>#include <uv.h>
#include "ant.h"#include "internal.h"#include "ptr.h"
#include "gc/modules.h"#include "net/listener.h"#include "streams/readable.h"
#include "http/http1_parser.h"#include "http/http1_writer.h"
#include "modules/assert.h"#include "modules/abort.h"#include "modules/blob.h"#include "modules/buffer.h"#include "modules/headers.h"#include "modules/request.h"#include "modules/response.h"#include "modules/server.h"#include "modules/timer.h"#include "modules/domexception.h"
typedef struct server_runtime_s server_runtime_t;typedef struct server_request_s server_request_t;typedef struct server_conn_state_s server_conn_state_t;
static server_runtime_t *g_server = NULL;
typedef struct stop_waiter_s { ant_value_t promise; struct stop_waiter_s *next;} stop_waiter_t;
typedef enum { SERVER_WRITE_NONE = 0, SERVER_WRITE_CLOSE_CLIENT, SERVER_WRITE_STREAM_READ, SERVER_WRITE_KEEP_ALIVE,} server_write_action_t;
typedef struct { server_request_t *request; ant_conn_t *conn; server_write_action_t action;} server_write_req_t;
struct server_request_s { server_runtime_t *server; server_conn_state_t *conn_state; ant_conn_t *conn; ant_value_t request_obj; ant_value_t response_obj; ant_value_t response_promise; ant_value_t response_reader; ant_value_t response_read_promise; struct server_request_s *next; size_t consumed_len; int refs; bool keep_alive; bool response_started;};
struct server_conn_state_s { server_runtime_t *server; ant_conn_t *conn; ant_http1_conn_parser_t parser; uv_timer_t drain_timer; server_request_t request; server_request_t *active_req; bool drain_timer_closed; bool drain_scheduled;};
struct server_runtime_s { ant_t *js; ant_value_t export_obj; ant_value_t fetch_fn; ant_value_t server_ctx; uv_loop_t *loop; ant_listener_t listener; uv_signal_t sigint_handle; uv_signal_t sigterm_handle; stop_waiter_t *stop_waiters; server_request_t *requests; char *hostname; char *unix_path; uint64_t request_timeout_ms; uint64_t idle_timeout_ms; int port; bool sigint_closed; bool sigterm_closed; bool stopping; bool force_stop;};
static inline void server_request_retain(server_request_t *req) { if (req) req->refs++;}
static inline server_request_t *server_current_request(ant_t *js) { return (server_request_t *)js_get_native_ptr(js->current_func);}
static inline server_runtime_t *server_current_runtime(ant_t *js) { return (server_runtime_t *)js_get_native_ptr(js->current_func);}
static ant_value_t server_mkreqfun( ant_t *js, ant_value_t (*fn)(ant_t *, ant_value_t *, int), server_request_t *req) { ant_value_t func = js_heavy_mkfun(js, fn, js_mkundef()); js_set_native_ptr(func, req); return func;}
static ant_value_t server_mkruntimefun( ant_t *js, ant_value_t (*fn)(ant_t *, ant_value_t *, int), server_runtime_t *server) { ant_value_t func = js_heavy_mkfun(js, fn, js_mkundef()); js_set_native_ptr(func, server); return func;}
static ant_value_t server_exception_reason(ant_t *js, ant_value_t value) { if (!is_err(value)) return value; if (!js->thrown_exists) return value;
value = js->thrown_value; js->thrown_exists = false; js->thrown_value = js_mkundef(); js->thrown_stack = js_mkundef(); return value;}
static void server_remove_request(server_runtime_t *server, server_request_t *req) { server_request_t **it = NULL; if (!server || !req) return;
for (it = &server->requests; *it; it = &(*it)->next) { if (*it == req) { *it = req->next; return; }}}
static void server_request_reset(server_request_t *req) { server_runtime_t *server = NULL; server_conn_state_t *conn_state = NULL;
if (!req) return; server = req->server; conn_state = req->conn_state; *req = (server_request_t){ .server = server, .conn_state = conn_state, .conn = conn_state ? conn_state->conn : NULL, .request_obj = js_mkundef(), .response_obj = js_mkundef(), .response_promise = js_mkundef(), .response_reader = js_mkundef(), .response_read_promise = js_mkundef(), };}
static void server_conn_state_maybe_free(server_conn_state_t *cs) { if (!cs) return; if (cs->conn) return; if (cs->active_req) return; if (cs->request.refs > 0) return; if (!cs->drain_timer_closed) return; free(cs);}
static void server_request_release(server_request_t *req) { if (!req) return; if (--req->refs > 0) return;
server_remove_request(req->server, req); server_request_reset(req); server_conn_state_maybe_free(req->conn_state);}
static server_request_t *server_find_request(server_runtime_t *server, ant_value_t request_obj) { server_request_t *req = NULL; if (!server || !is_object_type(request_obj)) return NULL;
for (req = server->requests; req; req = req->next) { if (req->request_obj == request_obj) return req; } return NULL;}static void stop_waiters_resolve(server_runtime_t *server) { stop_waiter_t *waiter = server->stop_waiters; server->stop_waiters = NULL;
while (waiter) { stop_waiter_t *next = waiter->next; js_resolve_promise(server->js, waiter->promise, js_mkundef()); free(waiter); waiter = next; }}
static void server_maybe_finish_stop(server_runtime_t *server) { if (!server || !server->stopping) return; if (ant_listener_has_connections(&server->listener)) return; if (!ant_listener_is_closed(&server->listener) || !server->sigint_closed || !server->sigterm_closed) return;
stop_waiters_resolve(server);}
static void server_signal_close_cb(uv_handle_t *handle) { server_runtime_t *server = (server_runtime_t *)handle->data; if (!server) return;
if (handle == (uv_handle_t *)&server->sigint_handle) server->sigint_closed = true; if (handle == (uv_handle_t *)&server->sigterm_handle) server->sigterm_closed = true; server_maybe_finish_stop(server);}
static void server_begin_stop(server_runtime_t *server, bool force) { if (!server) return; if (force) server->force_stop = true; server->stopping = true; ant_listener_stop(&server->listener, server->force_stop);
if (!uv_is_closing((uv_handle_t *)&server->sigint_handle)) uv_close((uv_handle_t *)&server->sigint_handle, server_signal_close_cb); else server->sigint_closed = true;
if (!uv_is_closing((uv_handle_t *)&server->sigterm_handle)) uv_close((uv_handle_t *)&server->sigterm_handle, server_signal_close_cb); else server->sigterm_closed = true;
server_maybe_finish_stop(server);}
static void server_signal_cb(uv_signal_t *handle, int signum) { server_runtime_t *server = (server_runtime_t *)handle->data; server_begin_stop(server, false);}
static ant_value_t server_headers_from_parsed(ant_t *js, const ant_http1_parsed_request_t *parsed) { ant_value_t headers = headers_create_empty(js); const ant_http_header_t *hdr = NULL;
if (is_err(headers)) return headers; for (hdr = parsed->headers; hdr; hdr = hdr->next) { ant_value_t step = headers_append_literal(js, headers, hdr->name, hdr->value); if (is_err(step)) return step; } return headers;}
static ant_value_t server_call_fetch(server_runtime_t *server, ant_value_t request_obj) { ant_t *js = server->js; ant_value_t args[2] = { request_obj, server->server_ctx }; ant_value_t saved_this = js->this_val; ant_value_t result = js_mkundef();
js->this_val = server->export_obj; if (vtype(server->fetch_fn) == T_CFUNC) result = js_as_cfunc(server->fetch_fn)(js, args, 2); else result = sv_vm_call(js->vm, js, server->fetch_fn, server->export_obj, args, 2, NULL, false); js->this_val = saved_this; return result;}
static ant_value_t server_abort_signal_task(ant_t *js, ant_value_t *args, int nargs) { ant_value_t payload = js_get_slot(js->current_func, SLOT_DATA); ant_value_t signal = 0; ant_value_t reason = 0; if (vtype(payload) != T_OBJ) return js_mkundef();
signal = js_get(js, payload, "signal"); reason = js_get(js, payload, "reason");
if (abort_signal_is_signal(signal) && !abort_signal_is_aborted(signal)) signal_do_abort(js, signal, reason);
return js_mkundef();}
static void server_queue_abort_signal(ant_t *js, ant_value_t signal, ant_value_t reason) { ant_value_t callback = 0; ant_value_t payload = 0;
if (!abort_signal_is_signal(signal) || abort_signal_is_aborted(signal)) return; payload = js_mkobj(js); if (is_err(payload)) return;
js_set(js, payload, "signal", signal); js_set(js, payload, "reason", reason); callback = js_heavy_mkfun(js, server_abort_signal_task, payload); queue_microtask(js, callback);}
static void server_abort_request(server_request_t *req, const char *message) { ant_t *js = NULL; ant_value_t signal = 0; ant_value_t reason = 0; ant_value_t abort_reason = 0; if (!req || !req->server || !is_object_type(req->request_obj)) return;
js = req->server->js; abort_reason = js_get_slot(req->request_obj, SLOT_REQUEST_ABORT_REASON); if (vtype(abort_reason) != T_UNDEF) return;
reason = make_dom_exception(js, message ? message : "The request was aborted", "AbortError" ); js_set_slot_wb(js, req->request_obj, SLOT_REQUEST_ABORT_REASON, reason); signal = js_get_slot(req->request_obj, SLOT_REQUEST_SIGNAL); if (!abort_signal_is_signal(signal) || abort_signal_is_aborted(signal)) return; server_queue_abort_signal(js, signal, reason);}
static bool server_response_chunk(server_request_t *req, ant_value_t value, const uint8_t **out, size_t *len) { blob_data_t *blob = NULL;
if (vtype(value) == T_STR) { *out = (const uint8_t *)js_getstr(req->server->js, value, len); if (!*out) *len = 0; return true; }
if (is_object_type(value) && blob_is_blob(req->server->js, value)) { blob = blob_get_data(value); *out = blob ? blob->data : NULL; *len = blob ? blob->size : 0; return true; }
return buffer_source_get_bytes(req->server->js, value, out, len);}
static void server_cancel_response_body(server_request_t *req, const char *message) { ant_t *js = NULL; ant_value_t stream = 0; ant_value_t reason = 0; ant_value_t result = 0;
if (!req || !req->server || !is_object_type(req->response_obj)) return;
js = req->server->js; stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); if (!rs_is_stream(stream)) return;
reason = make_dom_exception( js, message ? message : "The response body was canceled", "AbortError" ); result = readable_stream_cancel(js, stream, reason); if (vtype(result) == T_PROMISE) promise_mark_handled(result);}
static void server_start_stream_read(server_request_t *req);static void server_on_read(ant_conn_t *conn, ssize_t nread, void *user_data);static void server_write_cb(ant_conn_t *conn, int status, void *user_data);
static void server_on_deferred_drain(uv_timer_t *handle) { server_conn_state_t *cs = handle ? (server_conn_state_t *)handle->data : NULL;
if (!cs) return; cs->drain_scheduled = false;
if (!cs->conn || cs->active_req) return; if (ant_conn_is_closing(cs->conn)) return; if (ant_conn_buffer_len(cs->conn) == 0) return;
server_on_read(cs->conn, (ssize_t)ant_conn_buffer_len(cs->conn), cs->server);}
static void server_on_drain_timer_close(uv_handle_t *handle) { server_conn_state_t *cs = handle ? (server_conn_state_t *)handle->data : NULL;
if (!cs) return; cs->drain_timer_closed = true; cs->drain_scheduled = false; server_conn_state_maybe_free(cs);}
static void server_schedule_drain(server_conn_state_t *cs) { if (!cs || !cs->conn) return; if (cs->drain_scheduled) return;
cs->drain_scheduled = true; if (uv_timer_start(&cs->drain_timer, server_on_deferred_drain, 0, 0) != 0) cs->drain_scheduled = false;}
static bool server_queue_write(ant_conn_t *conn, server_request_t *req, char *data, size_t len, server_write_action_t action) { server_write_req_t *wr = calloc(1, sizeof(*wr)); int rc = 0;
if (!conn || ant_conn_is_closing(conn)) { free(data); return false; }
if (!wr) { free(data); return false; }
wr->request = req; wr->conn = conn; wr->action = action; if (req) server_request_retain(req);
rc = ant_conn_write(conn, data, len, server_write_cb, wr); if (rc != 0) { if (req) server_request_release(req); free(wr); ant_conn_close(conn); return false; }
return true;}
static bool server_queue_final_chunk(server_request_t *req, server_write_action_t action) { ant_http1_buffer_t buf; char *out = NULL; size_t out_len = 0;
if (!req || !req->conn || ant_conn_is_closing(req->conn)) return false;
ant_http1_buffer_init(&buf); if (!ant_http1_write_final_chunk(&buf)) { ant_http1_buffer_free(&buf); ant_conn_close(req->conn); return false; }
out = ant_http1_buffer_take(&buf, &out_len); return server_queue_write(req->conn, req, out, out_len, action);}
static void server_send_basic_response( ant_conn_t *conn, int status, const char *status_text, const char *content_type, const uint8_t *body, size_t body_len) { ant_http1_buffer_t buf; char *out = NULL; size_t out_len = 0;
ant_http1_buffer_init(&buf); if (!ant_http1_write_basic_response(&buf, status, status_text, content_type, body, body_len, false)) { ant_http1_buffer_free(&buf); ant_conn_close(conn); return; }
out = ant_http1_buffer_take(&buf, &out_len); server_queue_write(conn, NULL, out, out_len, SERVER_WRITE_CLOSE_CLIENT);}
static inline void server_send_text_response( ant_conn_t *conn, int status, const char *status_text, const char *body) { if (!body) body = ""; server_send_basic_response( conn, status, status_text, "text/plain;charset=UTF-8", (const uint8_t *)body, strlen(body) );}
static inline void server_send_internal_error(ant_conn_t *conn, const char *body) { server_send_text_response( conn, 500, "Internal Server Error", body ? body : "Internal Server Error" );}
static void server_finish_with_response(server_request_t *req, ant_value_t response_obj) { response_data_t *resp = response_get_data(response_obj); ant_value_t headers = response_get_headers(response_obj); ant_value_t stream = js_get_slot(response_obj, SLOT_RESPONSE_BODY_STREAM); bool body_is_stream = resp && resp->body_is_stream && rs_is_stream(stream); bool head_only = false; const char *status_text = NULL; ant_http1_buffer_t buf; char *out = NULL; size_t out_len = 0;
if (!req->conn || ant_conn_is_closing(req->conn)) return; if (!resp) { server_send_internal_error(req->conn, "Invalid Response"); return; }
req->response_obj = response_obj; req->response_started = true; head_only = strcasecmp(request_get_data(req->request_obj)->method, "HEAD") == 0; status_text = (resp->status_text && resp->status_text[0]) ? resp->status_text : ant_http1_default_status_text(resp->status);
ant_http1_buffer_init(&buf); if (!ant_http1_write_response_head(&buf, resp->status, status_text, headers, body_is_stream, resp->body_size, req->keep_alive)) { ant_http1_buffer_free(&buf); ant_conn_close(req->conn); return; } if (!body_is_stream && !head_only && resp->body_size > 0) ant_http1_buffer_append(&buf, resp->body_data, resp->body_size); if (buf.failed) { ant_http1_buffer_free(&buf); ant_conn_close(req->conn); return; }
out = ant_http1_buffer_take(&buf, &out_len); ant_conn_set_timeout_ms(req->conn, req->server->idle_timeout_ms); if (body_is_stream && head_only) server_cancel_response_body( req, "The response body was canceled for a HEAD request" ); server_queue_write( req->conn, req, out, out_len, body_is_stream && !head_only ? SERVER_WRITE_STREAM_READ : (req->keep_alive ? SERVER_WRITE_KEEP_ALIVE : SERVER_WRITE_CLOSE_CLIENT) );}
static ant_value_t server_on_response_reject(ant_t *js, ant_value_t *args, int nargs) { server_request_t *req = server_current_request(js); ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef(); const char *msg = NULL;
if (!req) return js_mkundef(); req->response_promise = js_mkundef();
if (req->conn && !ant_conn_is_closing(req->conn)) { msg = js_str(js, reason); server_send_internal_error(req->conn, msg); }
server_request_release(req); return js_mkundef();}
static ant_value_t server_on_response_fulfill(ant_t *js, ant_value_t *args, int nargs) { server_request_t *req = server_current_request(js); ant_value_t value = (nargs > 0) ? args[0] : js_mkundef();
if (!req) return js_mkundef(); req->response_promise = js_mkundef();
if (!response_get_data(value)) { if (req->conn && !ant_conn_is_closing(req->conn)) server_send_internal_error(req->conn, "fetch handler must return a Response"); } else if (req->conn && !ant_conn_is_closing(req->conn)) { server_finish_with_response(req, value); }
server_request_release(req); return js_mkundef();}
static void server_handle_fetch_result(server_request_t *req, ant_value_t result) { ant_t *js = req->server->js;
if (is_err(result)) { ant_value_t reason = server_exception_reason(js, result); const char *msg = js_str(js, reason); server_send_internal_error(req->conn, msg); return; }
if (vtype(result) == T_PROMISE) { ant_value_t fulfill = server_mkreqfun(js, server_on_response_fulfill, req); ant_value_t reject = server_mkreqfun(js, server_on_response_reject, req); ant_value_t then_result = 0;
req->response_promise = result; server_request_retain(req); then_result = js_promise_then(js, result, fulfill, reject); promise_mark_handled(then_result); return; }
if (!response_get_data(result)) { server_send_internal_error(req->conn, "fetch handler must return a Response"); return; }
server_finish_with_response(req, result);}
static ant_value_t server_stream_read_reject(ant_t *js, ant_value_t *args, int nargs) { server_request_t *req = server_current_request(js); if (!req) return js_mkundef(); req->response_read_promise = js_mkundef(); server_queue_final_chunk(req, SERVER_WRITE_CLOSE_CLIENT); server_request_release(req); return js_mkundef();}
static ant_value_t server_stream_read_fulfill(ant_t *js, ant_value_t *args, int nargs) { server_request_t *req = server_current_request(js); 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; ant_http1_buffer_t buf; char *out = NULL; size_t out_len = 0;
if (!req) return js_mkundef(); req->response_read_promise = js_mkundef();
if (!req->conn || ant_conn_is_closing(req->conn)) { server_request_release(req); return js_mkundef(); }
done = js_get(js, result, "done"); if (done == js_true) { ant_conn_set_timeout_ms(req->conn, req->server->idle_timeout_ms); server_queue_final_chunk( req, req->keep_alive ? SERVER_WRITE_KEEP_ALIVE : SERVER_WRITE_CLOSE_CLIENT ); server_request_release(req); return js_mkundef(); }
value = js_get(js, result, "value"); if (!server_response_chunk(req, value, &chunk, &chunk_len)) { fprintf(stderr, "Response body stream chunk must be a string, Blob, ArrayBuffer, DataView, or TypedArray\n"); server_cancel_response_body(req, "Invalid response body chunk"); server_queue_final_chunk(req, SERVER_WRITE_CLOSE_CLIENT); server_request_release(req); return js_mkundef(); }
ant_http1_buffer_init(&buf); if (!ant_http1_write_chunk(&buf, chunk, chunk_len)) { ant_http1_buffer_free(&buf); ant_conn_close(req->conn); server_request_release(req); return js_mkundef(); }
out = ant_http1_buffer_take(&buf, &out_len); ant_conn_set_timeout_ms(req->conn, req->server->idle_timeout_ms); server_queue_write(req->conn, req, out, out_len, SERVER_WRITE_STREAM_READ); server_request_release(req); return js_mkundef();}
static void server_start_stream_read(server_request_t *req) { ant_t *js = req->server->js; ant_value_t next_p = 0; ant_value_t fulfill = 0; ant_value_t reject = 0; ant_value_t then_result = 0;
if (!req->conn || ant_conn_is_closing(req->conn)) return; if (!is_object_type(req->response_reader)) return;
next_p = rs_default_reader_read(js, req->response_reader); req->response_read_promise = next_p; fulfill = server_mkreqfun(js, server_stream_read_fulfill, req); reject = server_mkreqfun(js, server_stream_read_reject, req);
server_request_retain(req); then_result = js_promise_then(js, next_p, fulfill, reject); promise_mark_handled(then_result);}
static bool server_request_ensure_reader(server_request_t *req) { ant_t *js; ant_value_t reader_args[1]; ant_value_t saved;
if (!req || !is_object_type(req->response_obj)) return false; if (vtype(req->response_reader) != T_UNDEF) return true;
js = req->server->js; reader_args[0] = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM);
saved = js->new_target; js->new_target = g_reader_proto; req->response_reader = js_rs_reader_ctor(js, reader_args, 1); js->new_target = saved;
return !is_err(req->response_reader);}
static void server_write_cb(ant_conn_t *conn, int status, void *user_data) { server_write_req_t *wr = (server_write_req_t *)user_data; server_request_t *req = wr->request; server_conn_state_t *cs = conn ? (server_conn_state_t *)ant_conn_get_user_data(conn) : NULL;
if (status < 0 && conn && !ant_conn_is_closing(conn)) ant_conn_close(conn);
if (status == 0 && conn && !ant_conn_is_closing(conn)) { switch (wr->action) { case SERVER_WRITE_STREAM_READ: if (!server_request_ensure_reader(req)) { ant_conn_close(conn); break; } server_start_stream_read(req); break;
case SERVER_WRITE_KEEP_ALIVE: if (cs && req) { ant_conn_consume(conn, req->consumed_len); ant_http1_conn_parser_reset(&cs->parser); cs->active_req = NULL; ant_conn_set_timeout_ms(conn, cs->server->idle_timeout_ms); ant_conn_resume_read(conn); server_request_release(req); if (ant_conn_buffer_len(conn) > 0) server_schedule_drain(cs); } break; case SERVER_WRITE_CLOSE_CLIENT: ant_conn_close(conn); break; default: break; }}
if (req) server_request_release(req); free(wr);}
static ant_value_t server_request_ip(ant_t *js, ant_value_t *args, int nargs) { server_runtime_t *server = server_current_runtime(js); server_request_t *req = NULL; ant_value_t out = 0;
if (!server || nargs < 1) return js_mknull(); req = server_find_request(server, args[0]); if (!req || !req->conn || !ant_conn_has_remote_addr(req->conn)) return js_mknull();
out = js_mkobj(js); js_set(js, out, "address", js_mkstr(js, ant_conn_remote_addr(req->conn), strlen(ant_conn_remote_addr(req->conn)))); js_set(js, out, "port", js_mknum(ant_conn_remote_port(req->conn))); return out;}
static ant_value_t server_timeout(ant_t *js, ant_value_t *args, int nargs) { server_runtime_t *server = server_current_runtime(js); server_request_t *req = NULL; int timeout = 0;
if (!server || nargs < 2) return js_mkundef(); req = server_find_request(server, args[0]); if (!req || !req->conn) return js_mkundef();
timeout = (int)js_getnum(args[1]); ant_conn_set_timeout_ms(req->conn, (uint64_t)timeout * 1000ULL); return js_mkundef();}
static ant_value_t server_stop(ant_t *js, ant_value_t *args, int nargs) { server_runtime_t *server = server_current_runtime(js); stop_waiter_t *waiter = NULL; ant_value_t promise = js_mkpromise(js); bool force = (nargs > 0 && js_truthy(js, args[0]));
if (!server) { js_resolve_promise(js, promise, js_mkundef()); return promise; }
waiter = calloc(1, sizeof(*waiter)); if (!waiter) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); return promise; }
waiter->promise = promise; waiter->next = server->stop_waiters; server->stop_waiters = waiter; server_begin_stop(server, force); return promise;}
static void server_process_client_request( ant_conn_t *conn, ant_http1_parsed_request_t *parsed, size_t consumed_len) { server_conn_state_t *cs = (server_conn_state_t *)ant_conn_get_user_data(conn); server_runtime_t *server = cs ? cs->server : NULL; server_request_t *req = NULL; ant_t *js = NULL; ant_value_t headers = 0; ant_value_t request_obj = 0; ant_value_t result = 0; bool keep_alive = false;
if (!server || !cs) { ant_http1_free_parsed_request(parsed); ant_conn_close(conn); return; }
js = server->js; req = &cs->request; keep_alive = parsed->keep_alive; headers = server_headers_from_parsed(js, parsed); if (is_err(headers)) { ant_http1_free_parsed_request(parsed); server_send_internal_error(conn, NULL); return; }
request_obj = request_create_server( js, parsed->method, parsed->target, parsed->absolute_target, parsed->host, server->hostname, server->port, headers, parsed->body, parsed->body_len, parsed->content_type ); ant_http1_free_parsed_request(parsed);
if (is_err(request_obj)) { server_send_internal_error(conn, NULL); return; }
server_request_reset(req); req->server = server; req->conn_state = cs; req->conn = conn; req->request_obj = request_obj; req->consumed_len = consumed_len; req->keep_alive = keep_alive; req->refs = 1; req->next = server->requests; server->requests = req; cs->active_req = req; ant_conn_pause_read(conn); ant_conn_set_timeout_ms(conn, server->request_timeout_ms);
result = server_call_fetch(server, request_obj); server_handle_fetch_result(req, result);}
static void server_on_read(ant_conn_t *conn, ssize_t nread, void *user_data) { server_conn_state_t *cs = (server_conn_state_t *)ant_conn_get_user_data(conn); ant_http1_parsed_request_t parsed = {0}; ant_http1_parse_result_t parse_result = ANT_HTTP1_PARSE_INCOMPLETE; size_t consumed = 0;
if (!conn || !cs) return; if (cs->active_req) return; if (ant_conn_buffer_len(conn) == 0) return;
ant_conn_set_timeout_ms(conn, cs->server->request_timeout_ms); parse_result = ant_http1_conn_parser_execute( &cs->parser, ant_conn_buffer(conn), ant_conn_buffer_len(conn), &parsed, &consumed ); if (parse_result == ANT_HTTP1_PARSE_ERROR) { ant_http1_free_parsed_request(&parsed); server_send_text_response(conn, 400, "Bad Request", "Bad Request"); return; }
if (parse_result != ANT_HTTP1_PARSE_OK) return; server_process_client_request(conn, &parsed, consumed);}
static void server_on_end(ant_conn_t *conn, void *user_data) { if (conn) ant_conn_close(conn);}
static void server_on_conn_close(ant_conn_t *conn, void *user_data) { server_runtime_t *server = (server_runtime_t *)user_data; server_conn_state_t *cs = (server_conn_state_t *)ant_conn_get_user_data(conn);
if (cs) { ant_conn_set_user_data(conn, NULL); cs->conn = NULL; ant_http1_conn_parser_free(&cs->parser); if (!uv_is_closing((uv_handle_t *)&cs->drain_timer)) uv_close((uv_handle_t *)&cs->drain_timer, server_on_drain_timer_close); if (cs->active_req) { server_abort_request(cs->active_req, "Client disconnected"); server_cancel_response_body(cs->active_req, "Client disconnected"); cs->active_req->conn = NULL; cs->active_req = NULL; server_request_release(&cs->request); cs = NULL; } if (cs) server_conn_state_maybe_free(cs); }
server_maybe_finish_stop(server);}
static void server_on_listener_close(ant_listener_t *listener, void *user_data) { server_maybe_finish_stop((server_runtime_t *)user_data);}
static void server_on_accept(ant_listener_t *listener, ant_conn_t *conn, void *user_data) { server_runtime_t *server = (server_runtime_t *)user_data; server_conn_state_t *cs = NULL;
(void)listener; if (!conn || !server) return;
cs = calloc(1, sizeof(*cs)); if (!cs) { ant_conn_close(conn); return; }
cs->server = server; cs->conn = conn; cs->drain_timer_closed = true; cs->request.server = server; cs->request.conn_state = cs; server_request_reset(&cs->request); if (uv_timer_init(server->loop, &cs->drain_timer) != 0) { free(cs); ant_conn_close(conn); return; } cs->drain_timer.data = cs; cs->drain_timer_closed = false; ant_http1_conn_parser_init(&cs->parser); ant_conn_set_user_data(conn, cs); ant_conn_set_no_delay(conn, true);}
static bool server_export_has_fetch_handler(ant_t *js, ant_value_t default_export, bool *looks_like_config) { ant_value_t fetch = 0; if (looks_like_config) *looks_like_config = false; if (!is_object_type(default_export)) return false;
fetch = js_get(js, default_export, "fetch"); if (is_callable(fetch)) return true;
if (looks_like_config) { ant_value_t port = js_get(js, default_export, "port"); ant_value_t hostname = js_get(js, default_export, "hostname"); ant_value_t idle_timeout = js_get(js, default_export, "idleTimeout"); ant_value_t request_timeout = js_get(js, default_export, "requestTimeout"); ant_value_t unix_path = js_get(js, default_export, "unix"); ant_value_t tls = js_get(js, default_export, "tls");
*looks_like_config = vtype(fetch) != T_UNDEF || vtype(port) != T_UNDEF || vtype(hostname) != T_UNDEF || vtype(idle_timeout) != T_UNDEF || vtype(request_timeout) != T_UNDEF || vtype(unix_path) != T_UNDEF || vtype(tls) != T_UNDEF; }
return false;}
int server_maybe_start_from_export(ant_t *js, ant_value_t default_export) { bool looks_like_server = false; ant_value_t server_result = 0; const char *error = NULL;
if (!server_export_has_fetch_handler(js, default_export, &looks_like_server)) { if (!looks_like_server) return EXIT_SUCCESS; error = "Module does not export a fetch handler"; goto fail; }
server_result = server_start_from_export(js, default_export); if (is_err(server_result)) { error = js_str(js, server_result); goto fail; }
return EXIT_SUCCESS;
fail: fprintf(stderr, "%s\n", error); return EXIT_FAILURE;}
ant_value_t server_start_from_export(ant_t *js, ant_value_t default_export) { server_runtime_t *server = NULL; ant_value_t port_v = 0; ant_value_t hostname_v = 0; ant_value_t idle_timeout_v = 0; ant_value_t request_timeout_v = 0; ant_value_t unix_v = 0; ant_value_t tls_v = 0; ant_listener_callbacks_t callbacks = {0}; int rc = 0;
if (g_server) return js_mkerr(js, "server is already running"); if (!is_object_type(default_export)) return js_mkerr_typed(js, JS_ERR_TYPE, "Module does not export a fetch handler");
server = malloc(sizeof(*server)); if (!server) return js_mkerr(js, "out of memory");
*server = (server_runtime_t){ .js = js, .export_obj = default_export, .fetch_fn = js_get(js, default_export, "fetch"), .hostname = strdup("0.0.0.0"), .unix_path = NULL, .port = 3000, .request_timeout_ms = 30000, .idle_timeout_ms = 30000, .loop = uv_default_loop(), };
if (!server->hostname) { free(server); return js_mkerr(js, "out of memory"); }
unix_v = js_get(js, default_export, "unix"); tls_v = js_get(js, default_export, "tls"); if (vtype(unix_v) != T_UNDEF && vtype(unix_v) != T_NULL) { if (vtype(unix_v) != T_STR) { free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_TYPE, "server unix must be a string"); }
server->unix_path = strdup(js_getstr(js, unix_v, NULL)); if (!server->unix_path) { free(server->hostname); free(server); return js_mkerr(js, "out of memory"); } } if (vtype(tls_v) != T_UNDEF && vtype(tls_v) != T_NULL) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_TYPE, "tls server config is not implemented yet"); }
port_v = js_get(js, default_export, "port"); hostname_v = js_get(js, default_export, "hostname"); idle_timeout_v = js_get(js, default_export, "idleTimeout"); request_timeout_v = js_get(js, default_export, "requestTimeout");
if (vtype(port_v) != T_UNDEF && vtype(port_v) != T_NULL) { if (vtype(port_v) != T_NUM) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_TYPE, "server port must be a number"); } server->port = (int)js_getnum(port_v); if (server->port < 0 || server->port > 65535) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_RANGE, "server port must be between 0 and 65535"); } }
if (vtype(hostname_v) != T_UNDEF && vtype(hostname_v) != T_NULL) { char *next_hostname = NULL; if (vtype(hostname_v) != T_STR) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_TYPE, "server hostname must be a string"); } next_hostname = strdup(js_getstr(js, hostname_v, NULL)); if (!next_hostname) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr(js, "out of memory"); } free(server->hostname); server->hostname = next_hostname; }
if (vtype(idle_timeout_v) != T_UNDEF && vtype(idle_timeout_v) != T_NULL) { double timeout = 0; if (vtype(idle_timeout_v) != T_NUM) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_TYPE, "server idleTimeout must be a number"); } timeout = js_getnum(idle_timeout_v); if (timeout < 0) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_RANGE, "server idleTimeout must be >= 0"); } server->idle_timeout_ms = (uint64_t)(timeout * 1000.0); }
if (vtype(request_timeout_v) != T_UNDEF && vtype(request_timeout_v) != T_NULL) { double timeout = 0; if (vtype(request_timeout_v) != T_NUM) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_TYPE, "server requestTimeout must be a number"); } timeout = js_getnum(request_timeout_v); if (timeout < 0) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_RANGE, "server requestTimeout must be >= 0"); } server->request_timeout_ms = (uint64_t)(timeout * 1000.0); }
uv_signal_init(server->loop, &server->sigint_handle); uv_signal_init(server->loop, &server->sigterm_handle); server->sigint_handle.data = server; server->sigterm_handle.data = server;
callbacks.on_accept = server_on_accept; callbacks.on_read = server_on_read; callbacks.on_end = server_on_end; callbacks.on_conn_close = server_on_conn_close; callbacks.on_listener_close = server_on_listener_close;
if (server->unix_path) { rc = ant_listener_listen_pipe( &server->listener, server->loop, server->unix_path, 128, server->idle_timeout_ms, &callbacks, server ); } else { rc = ant_listener_listen_tcp( &server->listener, server->loop, server->hostname, server->port, 128, server->idle_timeout_ms, &callbacks, server ); } if (rc != 0) { free(server->unix_path); free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); }
server->port = ant_listener_port(&server->listener); uv_signal_start(&server->sigint_handle, server_signal_cb, SIGINT); uv_signal_start(&server->sigterm_handle, server_signal_cb, SIGTERM);
server->server_ctx = js_mkobj(js); js_set(js, server->server_ctx, "requestIP", server_mkruntimefun(js, server_request_ip, server)); js_set(js, server->server_ctx, "timeout", server_mkruntimefun(js, server_timeout, server)); js_set(js, server->server_ctx, "stop", server_mkruntimefun(js, server_stop, server));
g_server = server; return js_mkundef();}
void gc_mark_server(ant_t *js, gc_mark_fn mark) { server_request_t *req = NULL; stop_waiter_t *waiter = NULL;
if (!g_server) return; mark(js, g_server->export_obj); mark(js, g_server->fetch_fn); mark(js, g_server->server_ctx);
for (req = g_server->requests; req; req = req->next) { mark(js, req->request_obj); mark(js, req->response_obj); mark(js, req->response_promise); mark(js, req->response_reader); mark(js, req->response_read_promise); }
for (waiter = g_server->stop_waiters; waiter; waiter = waiter->next) mark(js, waiter->promise);}