const Bun = @This(); const default_allocator = @import("../../../global.zig").default_allocator; const bun = @import("../../../global.zig"); const Environment = bun.Environment; const NetworkThread = @import("http").NetworkThread; const Global = bun.Global; const strings = bun.strings; const string = bun.string; const Output = @import("../../../global.zig").Output; const MutableString = @import("../../../global.zig").MutableString; const std = @import("std"); const Allocator = std.mem.Allocator; const IdentityContext = @import("../../../identity_context.zig").IdentityContext; const Fs = @import("../../../fs.zig"); const Resolver = @import("../../../resolver/resolver.zig"); const ast = @import("../../../import_record.zig"); const NodeModuleBundle = @import("../../../node_module_bundle.zig").NodeModuleBundle; const MacroEntryPoint = @import("../../../bundler.zig").MacroEntryPoint; const logger = @import("../../../logger.zig"); const Api = @import("../../../api/schema.zig").Api; const options = @import("../../../options.zig"); const Bundler = @import("../../../bundler.zig").Bundler; const ServerEntryPoint = @import("../../../bundler.zig").ServerEntryPoint; const js_printer = @import("../../../js_printer.zig"); const js_parser = @import("../../../js_parser.zig"); const js_ast = @import("../../../js_ast.zig"); const hash_map = @import("../../../hash_map.zig"); const http = @import("../../../http.zig"); const NodeFallbackModules = @import("../../../node_fallbacks.zig"); const ImportKind = ast.ImportKind; const Analytics = @import("../../../analytics/analytics_thread.zig"); const ZigString = @import("../../../jsc.zig").ZigString; const Runtime = @import("../../../runtime.zig"); const Router = @import("./router.zig"); const ImportRecord = ast.ImportRecord; const DotEnv = @import("../../../env_loader.zig"); const ParseResult = @import("../../../bundler.zig").ParseResult; const PackageJSON = @import("../../../resolver/package_json.zig").PackageJSON; const MacroRemap = @import("../../../resolver/package_json.zig").MacroMap; const WebCore = @import("../../../jsc.zig").WebCore; const Request = WebCore.Request; const Response = WebCore.Response; const Headers = WebCore.Headers; const Fetch = WebCore.Fetch; const HTTP = @import("http"); const FetchEvent = WebCore.FetchEvent; const js = @import("../../../jsc.zig").C; const JSC = @import("../../../jsc.zig"); const JSError = @import("../base.zig").JSError; const d = @import("../base.zig").d; const MarkedArrayBuffer = @import("../base.zig").MarkedArrayBuffer; const getAllocator = @import("../base.zig").getAllocator; const JSValue = @import("../../../jsc.zig").JSValue; const NewClass = @import("../base.zig").NewClass; const Microtask = @import("../../../jsc.zig").Microtask; const JSGlobalObject = @import("../../../jsc.zig").JSGlobalObject; const ExceptionValueRef = @import("../../../jsc.zig").ExceptionValueRef; const JSPrivateDataPtr = @import("../../../jsc.zig").JSPrivateDataPtr; const ZigConsoleClient = @import("../../../jsc.zig").ZigConsoleClient; const Node = @import("../../../jsc.zig").Node; const ZigException = @import("../../../jsc.zig").ZigException; const ZigStackTrace = @import("../../../jsc.zig").ZigStackTrace; const ErrorableResolvedSource = @import("../../../jsc.zig").ErrorableResolvedSource; const ResolvedSource = @import("../../../jsc.zig").ResolvedSource; const JSPromise = @import("../../../jsc.zig").JSPromise; const JSInternalPromise = @import("../../../jsc.zig").JSInternalPromise; const JSModuleLoader = @import("../../../jsc.zig").JSModuleLoader; const JSPromiseRejectionOperation = @import("../../../jsc.zig").JSPromiseRejectionOperation; const Exception = @import("../../../jsc.zig").Exception; const ErrorableZigString = @import("../../../jsc.zig").ErrorableZigString; const ZigGlobalObject = @import("../../../jsc.zig").ZigGlobalObject; const VM = @import("../../../jsc.zig").VM; const JSFunction = @import("../../../jsc.zig").JSFunction; const Config = @import("../config.zig"); const URL = @import("../../../url.zig").URL; const Transpiler = @import("./transpiler.zig"); const VirtualMachine = @import("../javascript.zig").VirtualMachine; const IOTask = JSC.IOTask; const is_bindgen = JSC.is_bindgen; const uws = @import("uws"); pub fn NewServer(comptime ssl_enabled: bool) type { return struct { const ThisServer = @This(); const RequestContextStackAllocator = std.heap.StackFallbackAllocator(@sizeOf(RequestContext) * 2048 + 4096); pub const App = uws.NewApp(ssl_enabled); listener: ?*App.ListenSocket = null, callback: JSC.JSValue = JSC.JSValue.zero, port: u16 = 3000, app: *App = undefined, globalThis: *JSGlobalObject, default_server: URL = URL{ .host = "localhost", .port = "3000" }, response_objects_pool: JSC.WebCore.Response.Pool = JSC.WebCore.Response.Pool{}, request_pool_allocator: std.mem.Allocator = undefined, pub fn init(port: u16, callback: JSC.JSValue, globalThis: *JSGlobalObject) *ThisServer { var server = bun.default_allocator.create(ThisServer) catch @panic("Out of memory!"); server.* = .{ .port = port, .callback = callback, .globalThis = globalThis, }; RequestContext.pool = bun.default_allocator.create(RequestContextStackAllocator) catch @panic("Out of memory!"); server.request_pool_allocator = RequestContext.pool.get(); return server; } pub fn onListen(this: *ThisServer, socket: ?*App.ListenSocket, _: uws.uws_app_listen_config_t) void { if (socket == null) { Output.prettyErrorln("Failed to start socket", .{}); Output.flush(); return; } this.listener = socket; VirtualMachine.vm.uws_event_loop = uws.Loop.get(); VirtualMachine.vm.response_objects_pool = &this.response_objects_pool; this.app.run(); } pub const RequestContext = struct { server: *ThisServer, resp: *App.Response, req: *uws.Request, url: string, method: HTTP.Method, aborted: bool = false, response_jsvalue: JSC.JSValue = JSC.JSValue.zero, response_ptr: ?*JSC.WebCore.Response = null, blob: JSC.WebCore.Blob = JSC.WebCore.Blob{}, promise: ?*JSC.JSValue = null, response_headers: ?*JSC.WebCore.Headers.RefCountedHeaders = null, pub threadlocal var pool: *RequestContextStackAllocator = undefined; pub fn onResolve( ctx: *RequestContext, _: *JSC.JSGlobalObject, arguments: []const JSC.JSValue, ) void { if (ctx.aborted) { ctx.finalize(); return; } if (arguments.len == 0) { ctx.req.setYield(true); ctx.finalize(); return; } var response = arguments[0].as(JSC.WebCore.Response) orelse { Output.prettyErrorln("Expected serverless to return a Response", .{}); ctx.req.setYield(true); ctx.finalize(); return; }; ctx.render(response); } pub fn onReject( ctx: *RequestContext, _: *JSC.JSGlobalObject, arguments: []const JSC.JSValue, ) void { if (ctx.aborted) { ctx.finalize(); return; } JSC.VirtualMachine.vm.defaultErrorHandler(arguments[0], null); ctx.req.setYield(true); ctx.finalize(); } pub fn create(this: *RequestContext, server: *ThisServer, req: *uws.Request, resp: *App.Response) void { this.* = .{ .resp = resp, .req = req, .url = req.url(), .method = HTTP.Method.which(req.method()) orelse .GET, .server = server, }; } pub fn onAbort(this: *RequestContext, _: *App.Response) void { this.aborted = true; this.req = undefined; if (!this.response_jsvalue.isEmpty()) { this.server.response_objects_pool.push(this.server.globalThis, this.response_jsvalue); this.response_jsvalue = JSC.JSValue.zero; } } pub fn finalize(this: *RequestContext) void { this.blob.detach(); if (!this.response_jsvalue.isEmpty()) { this.server.response_objects_pool.push(this.server.globalThis, this.response_jsvalue); this.response_jsvalue = JSC.JSValue.zero; } if (this.promise != null) { JSC.C.JSValueUnprotect(this.server.globalThis.ref(), this.promise.?.asObjectRef()); this.promise = null; } if (this.response_headers != null) { this.response_headers.?.deref(); this.response_headers = null; } this.server.request_pool_allocator.destroy(this); } pub fn doRender(this: *RequestContext) void { if (this.aborted) { return; } var response = this.response_ptr.?; this.blob = response.body.use(); const status = response.statusCode(); if (response.body.init.headers) |headers_| { var headers: *JSC.WebCore.Headers = headers_.get(); defer headers_.deref(); var entries = headers.entries.slice(); const names = entries.items(.name); const values = entries.items(.value); var status_text_buf: [48]u8 = undefined; if (status == 302) { this.resp.writeStatus("302 Found"); } else { this.resp.writeStatus(std.fmt.bufPrint(&status_text_buf, "{d} HM", .{response.body.init.status_code}) catch unreachable); } this.resp.writeHeaders(names, values, headers.buf.items); } if (status == 302 or status == 202 or this.blob.size == 0) { this.resp.endWithoutBody(); this.finalize(); return; } this.resp.end(this.blob.sharedView(), false); this.finalize(); } pub fn render(this: *RequestContext, response: *JSC.WebCore.Response) void { this.response_ptr = response; this.resp.runCorked(*RequestContext, doRender, this); this.response_ptr = null; } }; pub fn onRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void { req.setYield(false); var ctx = this.request_pool_allocator.create(RequestContext) catch @panic("ran out of memory"); ctx.create(this, req, resp); var request_object = bun.default_allocator.create(JSC.WebCore.Request) catch unreachable; request_object.* = .{ .url = JSC.ZigString.init(ctx.url), .method = ctx.method, }; var args = [_]JSC.C.JSValueRef{JSC.WebCore.Request.Class.make(this.globalThis.ref(), request_object)}; ctx.response_jsvalue = JSC.C.JSObjectCallAsFunctionReturnValue(this.globalThis.ref(), this.callback.asObjectRef(), null, 1, &args); defer JSC.VirtualMachine.vm.tick(); if (ctx.aborted) { ctx.finalize(); return; } if (ctx.response_jsvalue.isUndefinedOrNull()) { req.setYield(true); ctx.finalize(); return; } JSC.C.JSValueProtect(this.globalThis.ref(), ctx.response_jsvalue.asObjectRef()); if (ctx.response_jsvalue.as(JSC.WebCore.Response)) |response| { ctx.render(response); return; } if (ctx.response_jsvalue.jsTypeLoose() == .JSPromise) { resp.onAborted(*RequestContext, RequestContext.onAbort, ctx); JSC.VirtualMachine.vm.tick(); ctx.response_jsvalue.then( this.globalThis, RequestContext, ctx, RequestContext.onResolve, RequestContext.onReject, ); } // switch (ctx.response_jsvalue.jsTypeLoose()) { // .JSPromise => { // JSPromise. // }, // } } pub fn listen(this: *ThisServer) void { this.app = App.create(.{}); this.app.any("/*", *ThisServer, this, onRequest); this.app.listenWithConfig(*ThisServer, this, onListen, .{ .port = this.default_server.getPort().?, .host = bun.default_allocator.dupeZ(u8, this.default_server.displayHostname()) catch unreachable, .options = 0, }); } }; } pub const Server = NewServer(false); pub const SSLServer = NewServer(true);