aboutsummaryrefslogtreecommitdiff
path: root/src/javascript/jsc/api/server.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/javascript/jsc/api/server.zig')
-rw-r--r--src/javascript/jsc/api/server.zig1844
1 files changed, 0 insertions, 1844 deletions
diff --git a/src/javascript/jsc/api/server.zig b/src/javascript/jsc/api/server.zig
deleted file mode 100644
index 711329a95..000000000
--- a/src/javascript/jsc/api/server.zig
+++ /dev/null
@@ -1,1844 +0,0 @@
-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 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");
-const Fallback = Runtime.Fallback;
-const MimeType = HTTP.MimeType;
-const Blob = JSC.WebCore.Blob;
-const BoringSSL = @import("boringssl");
-const Arena = @import("../../../mimalloc_arena.zig").Arena;
-const SendfileContext = struct {
- fd: i32,
- socket_fd: i32 = 0,
- remain: Blob.SizeType = 0,
- offset: Blob.SizeType = 0,
- has_listener: bool = false,
- has_set_on_writable: bool = false,
- auto_close: bool = false,
-};
-const DateTime = @import("datetime");
-const linux = std.os.linux;
-
-pub const ServerConfig = struct {
- port: u16 = 0,
- hostname: [*:0]const u8 = "0.0.0.0",
-
- // TODO: use webkit URL parser instead of bun's
- base_url: URL = URL{},
- base_uri: string = "",
-
- ssl_config: ?SSLConfig = null,
- max_request_body_size: usize = 1024 * 1024 * 128,
- development: bool = false,
-
- onError: JSC.JSValue = JSC.JSValue.zero,
- onRequest: JSC.JSValue = JSC.JSValue.zero,
-
- pub const SSLConfig = struct {
- server_name: [*c]const u8 = null,
-
- key_file_name: [*c]const u8 = null,
- cert_file_name: [*c]const u8 = null,
-
- ca_file_name: [*c]const u8 = null,
- dh_params_file_name: [*c]const u8 = null,
-
- passphrase: [*c]const u8 = null,
- low_memory_mode: bool = false,
-
- pub fn deinit(this: *SSLConfig) void {
- const fields = .{
- "server_name",
- "key_file_name",
- "cert_file_name",
- "ca_file_name",
- "dh_params_file_name",
- "passphrase",
- };
-
- inline for (fields) |field| {
- const slice = std.mem.span(@field(this, field));
- if (slice.len > 0) {
- bun.default_allocator.free(slice);
- }
- }
- }
-
- const zero = SSLConfig{};
-
- pub fn inJS(global: *JSC.JSGlobalObject, obj: JSC.JSValue, exception: JSC.C.ExceptionRef) ?SSLConfig {
- var result = zero;
- var any = false;
-
- // Required
- if (obj.getTruthy(global, "keyFile")) |key_file_name| {
- var sliced = key_file_name.toSlice(global, bun.default_allocator);
- defer sliced.deinit();
- if (sliced.len > 0) {
- result.key_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
- if (std.os.system.access(result.key_file_name, std.os.F_OK) != 0) {
- JSC.throwInvalidArguments("Unable to access keyFile path", .{}, global.ref(), exception);
- result.deinit();
-
- return null;
- }
- any = true;
- }
- }
- if (obj.getTruthy(global, "certFile")) |cert_file_name| {
- var sliced = cert_file_name.toSlice(global, bun.default_allocator);
- defer sliced.deinit();
- if (sliced.len > 0) {
- result.cert_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
- if (std.os.system.access(result.cert_file_name, std.os.F_OK) != 0) {
- JSC.throwInvalidArguments("Unable to access certFile path", .{}, global.ref(), exception);
- result.deinit();
- return null;
- }
- any = true;
- }
- }
-
- // Optional
- if (any) {
- if (obj.getTruthy(global, "serverName")) |key_file_name| {
- var sliced = key_file_name.toSlice(global, bun.default_allocator);
- defer sliced.deinit();
- if (sliced.len > 0) {
- result.server_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
- }
- }
-
- if (obj.getTruthy(global, "caFile")) |ca_file_name| {
- var sliced = ca_file_name.toSlice(global, bun.default_allocator);
- defer sliced.deinit();
- if (sliced.len > 0) {
- result.ca_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
- if (std.os.system.access(result.ca_file_name, std.os.F_OK) != 0) {
- JSC.throwInvalidArguments("Invalid caFile path", .{}, global.ref(), exception);
- result.deinit();
- return null;
- }
- }
- }
- if (obj.getTruthy(global, "dhParamsFile")) |dh_params_file_name| {
- var sliced = dh_params_file_name.toSlice(global, bun.default_allocator);
- defer sliced.deinit();
- if (sliced.len > 0) {
- result.dh_params_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
- if (std.os.system.access(result.dh_params_file_name, std.os.F_OK) != 0) {
- JSC.throwInvalidArguments("Invalid dhParamsFile path", .{}, global.ref(), exception);
- result.deinit();
- return null;
- }
- }
- }
-
- if (obj.getTruthy(global, "passphrase")) |passphrase| {
- var sliced = passphrase.toSlice(global, bun.default_allocator);
- defer sliced.deinit();
- if (sliced.len > 0) {
- result.passphrase = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable;
- }
- }
-
- if (obj.get(global, "lowMemoryMode")) |low_memory_mode| {
- result.low_memory_mode = low_memory_mode.toBoolean();
- any = true;
- }
- }
-
- if (!any)
- return null;
- return result;
- }
-
- pub fn fromJS(global: *JSC.JSGlobalObject, arguments: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) ?SSLConfig {
- if (arguments.next()) |arg| {
- return SSLConfig.inJS(global, arg, exception);
- }
-
- return null;
- }
- };
-
- pub fn fromJS(global: *JSC.JSGlobalObject, arguments: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) ServerConfig {
- var env = arguments.vm.bundler.env;
-
- var args = ServerConfig{
- .port = 3000,
- .hostname = "0.0.0.0",
- .development = true,
- };
- var has_hostname = false;
- if (strings.eqlComptime(env.get("NODE_ENV") orelse "", "production")) {
- args.development = false;
- }
-
- if (arguments.vm.bundler.options.production) {
- args.development = false;
- }
-
- const PORT_ENV = .{ "PORT", "BUN_PORT", "NODE_PORT" };
-
- inline for (PORT_ENV) |PORT| {
- if (env.get(PORT)) |port| {
- if (std.fmt.parseInt(u16, port, 10)) |_port| {
- args.port = _port;
- } else |_| {}
- }
- }
-
- if (arguments.vm.bundler.options.transform_options.port) |port| {
- args.port = port;
- }
-
- if (arguments.vm.bundler.options.transform_options.origin) |origin| {
- args.base_uri = origin;
- }
-
- if (arguments.next()) |arg| {
- if (arg.isUndefinedOrNull() or !arg.isObject()) {
- JSC.throwInvalidArguments("Bun.serve expects an object", .{}, global.ref(), exception);
- return args;
- }
-
- if (arg.getTruthy(global, "port")) |port_| {
- args.port = @intCast(u16, @minimum(@maximum(0, port_.toInt32()), std.math.maxInt(u16)));
- }
-
- if (arg.getTruthy(global, "baseURI")) |baseURI| {
- var sliced = baseURI.toSlice(global, bun.default_allocator);
-
- if (sliced.len > 0) {
- defer sliced.deinit();
- args.base_uri = bun.default_allocator.dupe(u8, sliced.slice()) catch unreachable;
- }
- }
-
- if (arg.getTruthy(global, "hostname") orelse arg.getTruthy(global, "host")) |host| {
- const host_str = host.toSlice(
- global,
- bun.default_allocator,
- );
- if (host_str.len > 0) {
- args.hostname = bun.default_allocator.dupeZ(u8, host_str.slice()) catch unreachable;
- has_hostname = true;
- }
- }
-
- if (arg.get(global, "development")) |dev| {
- args.development = dev.toBoolean();
- }
-
- if (SSLConfig.fromJS(global, arguments, exception)) |ssl_config| {
- args.ssl_config = ssl_config;
- }
-
- if (exception.* != null) {
- return args;
- }
-
- if (arg.getTruthy(global, "maxRequestBodySize")) |max_request_body_size| {
- args.max_request_body_size = @intCast(u64, @maximum(0, max_request_body_size.toInt64()));
- }
-
- if (arg.getTruthy(global, "error")) |onError| {
- if (!onError.isCallable(global.vm())) {
- JSC.throwInvalidArguments("Expected error to be a function", .{}, global.ref(), exception);
- if (args.ssl_config) |*conf| {
- conf.deinit();
- }
- return args;
- }
- JSC.C.JSValueProtect(global.ref(), onError.asObjectRef());
- args.onError = onError;
- }
-
- if (arg.getTruthy(global, "fetch")) |onRequest| {
- if (!onRequest.isCallable(global.vm())) {
- JSC.throwInvalidArguments("Expected fetch() to be a function", .{}, global.ref(), exception);
- return args;
- }
- JSC.C.JSValueProtect(global.ref(), onRequest.asObjectRef());
- args.onRequest = onRequest;
- } else {
- JSC.throwInvalidArguments("Expected fetch() to be a function", .{}, global.ref(), exception);
- if (args.ssl_config) |*conf| {
- conf.deinit();
- }
- return args;
- }
- }
-
- if (args.port == 0) {
- JSC.throwInvalidArguments("Invalid port: must be > 0", .{}, global.ref(), exception);
- }
-
- if (args.base_uri.len > 0) {
- args.base_url = URL.parse(args.base_uri);
- if (args.base_url.hostname.len == 0) {
- JSC.throwInvalidArguments("baseURI must have a hostname", .{}, global.ref(), exception);
- bun.default_allocator.free(bun.constStrToU8(args.base_uri));
- args.base_uri = "";
- return args;
- }
-
- if (!strings.isAllASCII(args.base_uri)) {
- JSC.throwInvalidArguments("Unicode baseURI must already be encoded for now.\nnew URL(baseuRI).toString() should do the trick.", .{}, global.ref(), exception);
- bun.default_allocator.free(bun.constStrToU8(args.base_uri));
- args.base_uri = "";
- return args;
- }
-
- if (args.base_url.protocol.len == 0) {
- const protocol: string = if (args.ssl_config != null) "https" else "http";
-
- args.base_uri = (if ((args.port == 80 and args.ssl_config == null) or (args.port == 443 and args.ssl_config != null))
- std.fmt.allocPrint(bun.default_allocator, "{s}://{s}/{s}", .{
- protocol,
- args.base_url.hostname,
- strings.trimLeadingChar(args.base_url.pathname, '/'),
- })
- else
- std.fmt.allocPrint(bun.default_allocator, "{s}://{s}:{d}/{s}", .{
- protocol,
- args.base_url.hostname,
- args.port,
- strings.trimLeadingChar(args.base_url.pathname, '/'),
- })) catch unreachable;
-
- args.base_url = URL.parse(args.base_uri);
- }
- } else {
- const hostname: string =
- if (has_hostname and std.mem.span(args.hostname).len > 0) std.mem.span(args.hostname) else "localhost";
- const protocol: string = if (args.ssl_config != null) "https" else "http";
-
- args.base_uri = (if ((args.port == 80 and args.ssl_config == null) or (args.port == 443 and args.ssl_config != null))
- std.fmt.allocPrint(bun.default_allocator, "{s}://{s}/", .{
- protocol,
- hostname,
- })
- else
- std.fmt.allocPrint(bun.default_allocator, "{s}://{s}:{d}/", .{ protocol, hostname, args.port })) catch unreachable;
-
- if (!strings.isAllASCII(hostname)) {
- JSC.throwInvalidArguments("Unicode hostnames must already be encoded for now.\nnew URL(input).hostname should do the trick.", .{}, global.ref(), exception);
- bun.default_allocator.free(bun.constStrToU8(args.base_uri));
- args.base_uri = "";
- return args;
- }
-
- args.base_url = URL.parse(args.base_uri);
- }
-
- // I don't think there's a case where this can happen
- // but let's check anyway, just in case
- if (args.base_url.hostname.len == 0) {
- JSC.throwInvalidArguments("baseURI must have a hostname", .{}, global.ref(), exception);
- bun.default_allocator.free(bun.constStrToU8(args.base_uri));
- args.base_uri = "";
- return args;
- }
-
- if (args.base_url.username.len > 0 or args.base_url.password.len > 0) {
- JSC.throwInvalidArguments("baseURI can't have a username or password", .{}, global.ref(), exception);
- bun.default_allocator.free(bun.constStrToU8(args.base_uri));
- args.base_uri = "";
- return args;
- }
-
- return args;
- }
-};
-
-pub fn NewRequestContextStackAllocator(comptime RequestContext: type, comptime count: usize) type {
- // Pre-allocate up to 2048 requests
- // use a bitset to track which ones are used
- return struct {
- buf: [count]RequestContext = undefined,
- unused: Set = undefined,
- fallback_allocator: std.mem.Allocator = undefined,
-
- pub const Set = std.bit_set.ArrayBitSet(usize, count);
-
- pub fn get(this: *@This()) std.mem.Allocator {
- this.unused = Set.initFull();
- return std.mem.Allocator.init(this, alloc, resize, free);
- }
-
- fn alloc(self: *@This(), a: usize, b: u29, c: u29, d: usize) ![]u8 {
- if (self.unused.findFirstSet()) |i| {
- self.unused.unset(i);
- return std.mem.asBytes(&self.buf[i]);
- }
-
- return try self.fallback_allocator.rawAlloc(a, b, c, d);
- }
-
- fn resize(
- _: *@This(),
- _: []u8,
- _: u29,
- _: usize,
- _: u29,
- _: usize,
- ) ?usize {
- unreachable;
- }
-
- fn sliceContainsSlice(container: []u8, slice: []u8) bool {
- return @ptrToInt(slice.ptr) >= @ptrToInt(container.ptr) and
- (@ptrToInt(slice.ptr) + slice.len) <= (@ptrToInt(container.ptr) + container.len);
- }
-
- fn free(
- self: *@This(),
- buf: []u8,
- buf_align: u29,
- return_address: usize,
- ) void {
- _ = buf_align;
- _ = return_address;
- const bytes = std.mem.asBytes(&self.buf);
- if (sliceContainsSlice(bytes, buf)) {
- const index = if (bytes[0..buf.len].ptr != buf.ptr)
- (@ptrToInt(buf.ptr) - @ptrToInt(bytes)) / @sizeOf(RequestContext)
- else
- @as(usize, 0);
-
- if (comptime Environment.allow_assert) {
- std.debug.assert(@intToPtr(*RequestContext, @ptrToInt(buf.ptr)) == &self.buf[index]);
- std.debug.assert(!self.unused.isSet(index));
- }
-
- self.unused.set(index);
- } else {
- self.fallback_allocator.rawFree(buf, buf_align, return_address);
- }
- }
- };
-}
-
-// This is defined separately partially to work-around an LLVM debugger bug.
-fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comptime ThisServer: type) type {
- return struct {
- const RequestContext = @This();
- const App = uws.NewApp(ssl_enabled);
- pub threadlocal var pool: ?*RequestContext.RequestContextStackAllocator = null;
- pub threadlocal var pool_allocator: std.mem.Allocator = undefined;
-
- pub const RequestContextStackAllocator = NewRequestContextStackAllocator(RequestContext, 2048);
- pub const name = "HTTPRequestContext" ++ (if (debug_mode) "Debug" else "") ++ (if (ThisServer.ssl_enabled) "TLS" else "");
- pub const shim = JSC.Shimmer("Bun", name, @This());
-
- server: *ThisServer,
- resp: *App.Response,
- /// thread-local default heap allocator
- /// this prevents an extra pthread_getspecific() call which shows up in profiling
- allocator: std.mem.Allocator,
- req: *uws.Request,
- url: string,
- method: HTTP.Method,
- aborted: bool = false,
- finalized: bun.DebugOnly(bool) = bun.DebugOnlyDefault(false),
-
- /// We can only safely free once the request body promise is finalized
- /// and the response is rejected
- pending_promises_for_abort: u8 = 0,
-
- has_marked_complete: 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.FetchHeaders = null,
- has_abort_handler: bool = false,
- has_sendfile_ctx: bool = false,
- has_called_error_handler: bool = false,
- needs_content_length: bool = false,
- sendfile: SendfileContext = undefined,
- request_js_object: JSC.C.JSObjectRef = null,
- request_body_buf: std.ArrayListUnmanaged(u8) = .{},
-
- /// Used either for temporary blob data or fallback
- /// When the response body is a temporary value
- response_buf_owned: std.ArrayListUnmanaged(u8) = .{},
-
- // TODO: support builtin compression
- const can_sendfile = !ssl_enabled;
-
- pub const thenables = shim.thenables(.{
- PromiseHandler,
- });
-
- pub const lazy_static_functions = thenables;
- pub const Export = lazy_static_functions;
-
- const PromiseHandler = JSC.Thenable(RequestContext, onResolve, onReject);
-
- pub fn setAbortHandler(this: *RequestContext) void {
- if (this.has_abort_handler) return;
- this.has_abort_handler = true;
- this.resp.onAborted(*RequestContext, RequestContext.onAbort, this);
- }
-
- pub fn onResolve(
- ctx: *RequestContext,
- _: *JSC.JSGlobalObject,
- arguments: []const JSC.JSValue,
- ) void {
- if (ctx.aborted) {
- ctx.finalizeForAbort();
- return;
- }
-
- if (arguments.len == 0) {
- ctx.renderMissing();
- return;
- }
-
- handleResolve(ctx, arguments[0]);
- }
-
- fn handleResolve(ctx: *RequestContext, value: JSC.JSValue) void {
- if (value.isEmptyOrUndefinedOrNull()) {
- ctx.renderMissing();
- return;
- }
-
- var response = value.as(JSC.WebCore.Response) orelse {
- Output.prettyErrorln("Expected a Response object", .{});
- Output.flush();
- ctx.renderMissing();
- return;
- };
- ctx.response_jsvalue = value;
- JSC.C.JSValueProtect(ctx.server.globalThis.ref(), value.asObjectRef());
-
- ctx.render(response);
- }
-
- pub fn finalizeForAbort(this: *RequestContext) void {
- this.pending_promises_for_abort -|= 1;
- if (this.pending_promises_for_abort == 0) this.finalize();
- }
-
- pub fn onReject(
- ctx: *RequestContext,
- _: *JSC.JSGlobalObject,
- arguments: []const JSC.JSValue,
- ) void {
- if (ctx.aborted) {
- ctx.finalizeForAbort();
- return;
- }
- handleReject(ctx, if (arguments.len > 0) arguments[0] else JSC.JSValue.jsUndefined());
- }
-
- fn handleReject(ctx: *RequestContext, value: JSC.JSValue) void {
- ctx.runErrorHandler(
- value,
- );
-
- if (ctx.aborted) {
- ctx.finalizeForAbort();
- return;
- }
- if (!ctx.resp.hasResponded()) {
- ctx.renderMissing();
- }
- }
-
- pub fn renderMissing(ctx: *RequestContext) void {
- if (comptime !debug_mode) {
- ctx.resp.writeStatus("204 No Content");
- ctx.resp.endWithoutBody();
- ctx.finalize();
- } else {
- ctx.resp.writeStatus("200 OK");
- ctx.resp.end("Welcome to Bun! To get started, return a Response object.", false);
- ctx.finalize();
- }
- }
-
- pub fn renderDefaultError(
- this: *RequestContext,
- log: *logger.Log,
- err: anyerror,
- exceptions: []Api.JsException,
- comptime fmt: string,
- args: anytype,
- ) void {
- this.resp.writeStatus("500 Internal Server Error");
- this.resp.writeHeader("content-type", MimeType.html.value);
-
- const allocator = this.allocator;
-
- var fallback_container = allocator.create(Api.FallbackMessageContainer) catch unreachable;
- defer allocator.destroy(fallback_container);
- fallback_container.* = Api.FallbackMessageContainer{
- .message = std.fmt.allocPrint(allocator, comptime Output.prettyFmt(fmt, false), args) catch unreachable,
- .router = null,
- .reason = .fetch_event_handler,
- .cwd = VirtualMachine.vm.bundler.fs.top_level_dir,
- .problems = Api.Problems{
- .code = @truncate(u16, @errorToInt(err)),
- .name = @errorName(err),
- .exceptions = exceptions,
- .build = log.toAPI(allocator) catch unreachable,
- },
- };
-
- if (comptime fmt.len > 0) Output.prettyErrorln(fmt, args);
- Output.flush();
-
- var bb = std.ArrayList(u8).init(allocator);
- var bb_writer = bb.writer();
-
- Fallback.renderBackend(
- allocator,
- fallback_container,
- @TypeOf(bb_writer),
- bb_writer,
- ) catch unreachable;
- if (this.resp.tryEnd(bb.items, bb.items.len)) {
- bb.clearAndFree();
- this.finalizeWithoutDeinit();
- return;
- }
-
- this.response_buf_owned = std.ArrayListUnmanaged(u8){ .items = bb.items, .capacity = bb.capacity };
- this.renderResponseBuffer();
- }
-
- pub fn renderResponseBuffer(this: *RequestContext) void {
- this.resp.onWritable(*RequestContext, onWritableResponseBuffer, this);
- }
-
- pub fn onWritableResponseBuffer(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
- std.debug.assert(this.resp == resp);
- if (this.aborted) {
- this.finalizeForAbort();
- return false;
- }
- return this.sendWritableBytes(this.response_buf_owned.items, write_offset, resp);
- }
-
- pub fn create(this: *RequestContext, server: *ThisServer, req: *uws.Request, resp: *App.Response) void {
- this.* = .{
- .allocator = server.allocator,
- .resp = resp,
- .req = req,
- // this memory is owned by the Request object
- .url = strings.append(this.allocator, server.base_url_string_for_joining, req.url()) catch
- @panic("Out of memory while joining the URL path?"),
- .method = HTTP.Method.which(req.method()) orelse .GET,
- .server = server,
- };
- }
-
- pub fn isDeadRequest(this: *RequestContext) bool {
- if (this.pending_promises_for_abort > 0) return false;
-
- if (this.promise != null) {
- return false;
- }
-
- if (this.request_js_object) |obj| {
- if (obj.value().as(Request)) |req| {
- if (req.body == .Locked) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- pub fn onAbort(this: *RequestContext, resp: *App.Response) void {
- std.debug.assert(this.resp == resp);
- std.debug.assert(!this.aborted);
- this.aborted = true;
-
- // if we can, free the request now.
- if (this.isDeadRequest()) {
- this.finalizeWithoutDeinit();
- this.markComplete();
- this.deinit();
- } else {
- this.pending_promises_for_abort = 0;
-
- // if we cannot, we have to reject pending promises
- // first, we reject the request body promise
- if (this.request_js_object != null) {
- var request_js = this.request_js_object.?.value();
- request_js.ensureStillAlive();
-
- this.request_js_object = null;
- defer request_js.ensureStillAlive();
- defer JSC.C.JSValueUnprotect(this.server.globalThis.ref(), request_js.asObjectRef());
- // User called .blob(), .json(), text(), or .arrayBuffer() on the Request object
- // but we received nothing or the connection was aborted
- if (request_js.as(Request)) |req| {
- // the promise is pending
- if (req.body == .Locked and (req.body.Locked.action != .none or req.body.Locked.promise != null)) {
- this.pending_promises_for_abort += 1;
- req.body.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
- }
- req.uws_request = null;
- }
- }
-
- // then, we reject the response promise
- if (this.promise) |promise| {
- this.pending_promises_for_abort += 1;
- this.promise = null;
- promise.asPromise().?.reject(this.server.globalThis, JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis));
- }
-
- if (this.pending_promises_for_abort > 0) {
- this.server.vm.tick();
- }
- }
- }
-
- pub fn markComplete(this: *RequestContext) void {
- if (!this.has_marked_complete) this.server.onRequestComplete();
- this.has_marked_complete = true;
- }
-
- // This function may be called multiple times
- // so it's important that we can safely do that
- pub fn finalizeWithoutDeinit(this: *RequestContext) void {
- this.blob.detach();
-
- if (comptime Environment.allow_assert) {
- std.debug.assert(!this.finalized);
- this.finalized = true;
- }
-
- 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.request_js_object != null) {
- var request_js = this.request_js_object.?.value();
- request_js.ensureStillAlive();
-
- this.request_js_object = null;
- defer request_js.ensureStillAlive();
- defer JSC.C.JSValueUnprotect(this.server.globalThis.ref(), request_js.asObjectRef());
- // User called .blob(), .json(), text(), or .arrayBuffer() on the Request object
- // but we received nothing or the connection was aborted
- if (request_js.as(Request)) |req| {
- // the promise is pending
- if (req.body == .Locked and req.body.Locked.action != .none and req.body.Locked.promise != null) {
- req.body.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
- }
- req.uws_request = null;
- }
- }
-
- if (this.promise) |promise| {
- this.promise = null;
-
- if (promise.asInternalPromise()) |prom| {
- prom.rejectAsHandled(this.server.globalThis, (JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis)));
- } else if (promise.asPromise()) |prom| {
- prom.rejectAsHandled(this.server.globalThis, (JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis)));
- }
- JSC.C.JSValueUnprotect(this.server.globalThis.ref(), promise.asObjectRef());
- }
-
- if (this.response_headers != null) {
- this.response_headers.?.deref();
- this.response_headers = null;
- }
- }
- pub fn finalize(this: *RequestContext) void {
- this.finalizeWithoutDeinit();
- this.markComplete();
- this.deinit();
- }
-
- pub fn deinit(this: *RequestContext) void {
- if (comptime Environment.allow_assert)
- std.debug.assert(this.finalized);
-
- if (comptime Environment.allow_assert)
- std.debug.assert(this.has_marked_complete);
-
- var server = this.server;
- this.request_body_buf.clearAndFree(this.allocator);
- this.response_buf_owned.clearAndFree(this.allocator);
-
- server.request_pool_allocator.destroy(this);
- }
-
- fn writeHeaders(
- this: *RequestContext,
- headers: *JSC.FetchHeaders,
- ) void {
- headers.remove(&ZigString.init("content-length"));
- headers.remove(&ZigString.init("transfer-encoding"));
- if (!ssl_enabled) headers.remove(&ZigString.init("strict-transport-security"));
- headers.toUWSResponse(ssl_enabled, this.resp);
- }
-
- pub fn writeStatus(this: *RequestContext, status: u16) void {
- 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", .{status}) catch unreachable);
- }
- }
-
- fn cleanupAndFinalizeAfterSendfile(this: *RequestContext) void {
- this.resp.setWriteOffset(this.sendfile.offset);
- this.resp.endWithoutBody();
- // use node syscall so that we don't segfault on BADF
- if (this.sendfile.auto_close)
- _ = JSC.Node.Syscall.close(this.sendfile.fd);
- this.sendfile = undefined;
- this.finalize();
- }
- const separator: string = "\r\n";
- const separator_iovec = [1]std.os.iovec_const{.{
- .iov_base = separator.ptr,
- .iov_len = separator.len,
- }};
-
- pub fn onSendfile(this: *RequestContext) bool {
- if (this.aborted) {
- this.cleanupAndFinalizeAfterSendfile();
- return false;
- }
-
- const adjusted_count_temporary = @minimum(@as(u64, this.sendfile.remain), @as(u63, std.math.maxInt(u63)));
- // TODO we should not need this int cast; improve the return type of `@minimum`
- const adjusted_count = @intCast(u63, adjusted_count_temporary);
-
- if (Environment.isLinux) {
- var signed_offset = @intCast(i64, this.sendfile.offset);
- const start = this.sendfile.offset;
- const val =
- // this does the syscall directly, without libc
- linux.sendfile(this.sendfile.socket_fd, this.sendfile.fd, &signed_offset, this.sendfile.remain);
- this.sendfile.offset = @intCast(Blob.SizeType, signed_offset);
-
- const errcode = linux.getErrno(val);
-
- this.sendfile.remain -= @intCast(Blob.SizeType, this.sendfile.offset - start);
-
- if (errcode != .SUCCESS or this.aborted or this.sendfile.remain == 0 or val == 0) {
- if (errcode != .AGAIN and errcode != .SUCCESS and errcode != .PIPE) {
- Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
- Output.flush();
- }
- this.cleanupAndFinalizeAfterSendfile();
- return errcode != .SUCCESS;
- }
- } else {
- var sbytes: std.os.off_t = adjusted_count;
- const signed_offset = @bitCast(i64, @as(u64, this.sendfile.offset));
-
- const errcode = std.c.getErrno(std.c.sendfile(
- this.sendfile.fd,
- this.sendfile.socket_fd,
-
- signed_offset,
- &sbytes,
- null,
- 0,
- ));
- const wrote = @intCast(Blob.SizeType, sbytes);
- this.sendfile.offset += wrote;
- this.sendfile.remain -= wrote;
- if (errcode != .AGAIN or this.aborted or this.sendfile.remain == 0 or sbytes == 0) {
- if (errcode != .AGAIN and errcode != .SUCCESS and errcode != .PIPE) {
- Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
- Output.flush();
- }
- this.cleanupAndFinalizeAfterSendfile();
- return errcode == .SUCCESS;
- }
- }
-
- if (!this.sendfile.has_set_on_writable) {
- this.sendfile.has_set_on_writable = true;
- this.resp.onWritable(*RequestContext, onWritableSendfile, this);
- }
-
- this.setAbortHandler();
- this.resp.markNeedsMore();
-
- return true;
- }
-
- pub fn onWritableBytes(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
- std.debug.assert(this.resp == resp);
- if (this.aborted) {
- this.finalizeForAbort();
- return false;
- }
-
- var bytes = this.blob.sharedView();
- return this.sendWritableBytes(bytes, write_offset, resp);
- }
-
- pub fn sendWritableBytes(this: *RequestContext, bytes_: []const u8, write_offset: c_ulong, resp: *App.Response) bool {
- std.debug.assert(this.resp == resp);
-
- var bytes = bytes_[@minimum(bytes_.len, @truncate(usize, write_offset))..];
- if (resp.tryEnd(bytes, bytes_.len)) {
- this.finalize();
- return true;
- } else {
- this.resp.onWritable(*RequestContext, onWritableBytes, this);
- return true;
- }
- }
-
- pub fn onWritableSendfile(this: *RequestContext, _: c_ulong, _: *App.Response) callconv(.C) bool {
- return this.onSendfile();
- }
-
- // We tried open() in another thread for this
- // it was not faster due to the mountain of syscalls
- pub fn renderSendFile(this: *RequestContext, blob: JSC.WebCore.Blob) void {
- this.blob = blob;
- const file = &this.blob.store.?.data.file;
- var file_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
- const auto_close = file.pathlike != .fd;
- const fd = if (!auto_close)
- file.pathlike.fd
- else switch (JSC.Node.Syscall.open(file.pathlike.path.sliceZ(&file_buf), std.os.O.RDONLY | std.os.O.NONBLOCK | std.os.O.CLOEXEC, 0)) {
- .result => |_fd| _fd,
- .err => |err| return this.runErrorHandler(err.withPath(file.pathlike.path.slice()).toSystemError().toErrorInstance(
- this.server.globalThis,
- )),
- };
-
- // stat only blocks if the target is a file descriptor
- const stat: std.os.Stat = switch (JSC.Node.Syscall.fstat(fd)) {
- .result => |result| result,
- .err => |err| {
- this.runErrorHandler(err.withPath(file.pathlike.path.slice()).toSystemError().toErrorInstance(
- this.server.globalThis,
- ));
- if (auto_close) {
- _ = JSC.Node.Syscall.close(fd);
- }
- return;
- },
- };
-
- if (Environment.isMac) {
- if (!std.os.S.ISREG(stat.mode)) {
- if (auto_close) {
- _ = JSC.Node.Syscall.close(fd);
- }
-
- var err = JSC.Node.Syscall.Error{
- .errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(std.os.E.INVAL)),
- .path = file.pathlike.path.slice(),
- .syscall = .sendfile,
- };
- var sys = err.toSystemError();
- sys.message = ZigString.init("MacOS does not support sending non-regular files");
- this.runErrorHandler(sys.toErrorInstance(
- this.server.globalThis,
- ));
- return;
- }
- }
-
- if (Environment.isLinux) {
- if (!(std.os.S.ISREG(stat.mode) or std.os.S.ISFIFO(stat.mode))) {
- if (auto_close) {
- _ = JSC.Node.Syscall.close(fd);
- }
-
- var err = JSC.Node.Syscall.Error{
- .errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(std.os.E.INVAL)),
- .path = file.pathlike.path.slice(),
- .syscall = .sendfile,
- };
- var sys = err.toSystemError();
- sys.message = ZigString.init("File must be regular or FIFO");
- this.runErrorHandler(sys.toErrorInstance(
- this.server.globalThis,
- ));
- return;
- }
- }
-
- this.blob.size = @intCast(Blob.SizeType, stat.size);
- this.needs_content_length = true;
-
- this.sendfile = .{
- .fd = fd,
- .remain = this.blob.size,
- .auto_close = auto_close,
- .socket_fd = if (!this.aborted) this.resp.getNativeHandle() else -999,
- };
-
- this.resp.runCorked(*RequestContext, renderMetadataAndNewline, this);
-
- if (this.blob.size == 0) {
- this.cleanupAndFinalizeAfterSendfile();
- return;
- }
-
- _ = this.onSendfile();
- }
-
- pub fn renderMetadataAndNewline(this: *RequestContext) void {
- this.renderMetadata();
- this.resp.prepareForSendfile();
- }
-
- pub fn doSendfile(this: *RequestContext, blob: Blob) void {
- if (this.aborted) {
- this.finalizeForAbort();
- return;
- }
-
- if (this.has_sendfile_ctx) return;
-
- this.has_sendfile_ctx = true;
-
- if (comptime can_sendfile) {
- return this.renderSendFile(blob);
- }
-
- this.setAbortHandler();
- this.blob.doReadFileInternal(*RequestContext, this, onReadFile, this.server.globalThis);
- }
-
- pub fn onReadFile(this: *RequestContext, result: Blob.Store.ReadFile.ResultType) void {
- if (this.aborted) {
- this.finalizeForAbort();
- return;
- }
-
- if (result == .err) {
- this.runErrorHandler(result.err.toErrorInstance(this.server.globalThis));
- return;
- }
-
- const is_temporary = result.result.is_temporary;
- if (!is_temporary) {
- this.blob.resolveSize();
- this.doRenderBlob();
- } else {
- this.blob.size = @truncate(Blob.SizeType, result.result.buf.len);
- this.response_buf_owned = .{ .items = result.result.buf, .capacity = result.result.buf.len };
- this.renderResponseBuffer();
- }
- }
-
- pub fn doRenderWithBodyLocked(this: *anyopaque, value: *JSC.WebCore.Body.Value) void {
- doRenderWithBody(bun.cast(*RequestContext, this), value);
- }
-
- pub fn doRenderWithBody(this: *RequestContext, value: *JSC.WebCore.Body.Value) void {
- switch (value.*) {
- .Error => {
- const err = value.Error;
- _ = value.use();
- if (this.aborted) {
- this.finalizeForAbort();
- return;
- }
- this.runErrorHandler(err);
- return;
- },
- .Blob => {
- this.blob = value.use();
-
- if (this.aborted) {
- this.finalizeForAbort();
- return;
- }
-
- if (this.blob.needsToReadFile()) {
- this.req.setYield(false);
- if (!this.has_sendfile_ctx)
- this.doSendfile(this.blob);
- return;
- }
- },
- // TODO: this needs to support streaming!
- .Locked => |*lock| {
- lock.callback = doRenderWithBodyLocked;
- lock.task = this;
- return;
- },
- else => {},
- }
-
- this.doRenderBlob();
- }
-
- pub fn doRenderBlob(this: *RequestContext) void {
- if (this.has_abort_handler)
- this.resp.runCorked(*RequestContext, renderMetadata, this)
- else
- this.renderMetadata();
-
- this.renderBytes();
- }
-
- pub fn doRender(this: *RequestContext) void {
- if (this.aborted) {
- this.finalizeForAbort();
- return;
- }
- var response = this.response_ptr.?;
- this.doRenderWithBody(&response.body.value);
- }
-
- pub fn renderProductionError(this: *RequestContext, status: u16) void {
- switch (status) {
- 404 => {
- this.resp.writeStatus("404 Not Found");
- this.resp.endWithoutBody();
- },
- else => {
- this.resp.writeStatus("500 Internal Server Error");
- this.resp.writeHeader("content-type", "text/plain");
- this.resp.end("Something went wrong!", true);
- },
- }
-
- this.finalize();
- }
-
- pub fn runErrorHandler(
- this: *RequestContext,
- value: JSC.JSValue,
- ) void {
- runErrorHandlerWithStatusCode(this, value, 500);
- }
-
- pub fn runErrorHandlerWithStatusCode(
- this: *RequestContext,
- value: JSC.JSValue,
- status: u16,
- ) void {
- JSC.markBinding();
- if (this.resp.hasResponded()) return;
-
- var exception_list: std.ArrayList(Api.JsException) = std.ArrayList(Api.JsException).init(this.allocator);
- defer exception_list.deinit();
- if (!this.server.config.onError.isEmpty() and !this.has_called_error_handler) {
- this.has_called_error_handler = true;
- var args = [_]JSC.C.JSValueRef{value.asObjectRef()};
- const result = JSC.C.JSObjectCallAsFunctionReturnValue(this.server.globalThis.ref(), this.server.config.onError.asObjectRef(), this.server.thisObject.asObjectRef(), 1, &args);
-
- if (!result.isEmptyOrUndefinedOrNull()) {
- if (result.isError() or result.isAggregateError(this.server.globalThis)) {
- this.runErrorHandler(result);
- return;
- } else if (result.as(Response)) |response| {
- this.render(response);
- return;
- }
- }
- }
-
- if (comptime debug_mode) {
- JSC.VirtualMachine.vm.defaultErrorHandler(value, &exception_list);
-
- this.renderDefaultError(
- JSC.VirtualMachine.vm.log,
- error.ExceptionOcurred,
- exception_list.toOwnedSlice(),
- "<r><red>{s}<r> - <b>{s}<r> failed",
- .{ std.mem.span(@tagName(this.method)), this.url },
- );
- } else {
- if (status != 404)
- JSC.VirtualMachine.vm.defaultErrorHandler(value, &exception_list);
- this.renderProductionError(status);
- }
- JSC.VirtualMachine.vm.log.reset();
- return;
- }
-
- pub fn renderMetadata(this: *RequestContext) void {
- var response: *JSC.WebCore.Response = this.response_ptr.?;
- var status = response.statusCode();
- const size = this.blob.size;
- status = if (status == 200 and size == 0)
- 204
- else
- status;
-
- this.writeStatus(status);
- var needs_content_type = true;
- const content_type: MimeType = brk: {
- if (response.body.init.headers) |headers_| {
- if (headers_.get("content-type")) |content| {
- needs_content_type = false;
- break :brk MimeType.init(content);
- }
- }
- break :brk if (this.blob.content_type.len > 0)
- MimeType.init(this.blob.content_type)
- else if (MimeType.sniff(this.blob.sharedView())) |content|
- content
- else if (this.blob.is_all_ascii orelse false)
- MimeType.text
- else
- MimeType.other;
- };
-
- var has_content_disposition = false;
-
- if (response.body.init.headers) |headers_| {
- this.writeHeaders(headers_);
- has_content_disposition = headers_.has(&ZigString.init("content-disposition"));
- response.body.init.headers = null;
- headers_.deref();
- }
-
- if (needs_content_type) {
- this.resp.writeHeader("content-type", content_type.value);
- }
-
- // automatically include the filename when:
- // 1. Bun.file("foo")
- // 2. The content-disposition header is not present
- if (!has_content_disposition and content_type.category.autosetFilename()) {
- if (this.blob.store) |store| {
- if (store.data == .file) {
- if (store.data.file.pathlike == .path) {
- const basename = std.fs.path.basename(store.data.file.pathlike.path.slice());
- if (basename.len > 0) {
- var filename_buf: [1024]u8 = undefined;
-
- this.resp.writeHeader(
- "content-disposition",
- std.fmt.bufPrint(&filename_buf, "filename=\"{s}\"", .{basename[0..@minimum(basename.len, 1024 - 32)]}) catch "",
- );
- }
- }
- }
- }
- }
-
- if (this.needs_content_length) {
- this.resp.writeHeaderInt("content-length", size);
- this.needs_content_length = false;
- }
- }
-
- pub fn renderBytes(this: *RequestContext) void {
- const bytes = this.blob.sharedView();
-
- if (!this.resp.tryEnd(
- bytes,
- bytes.len,
- )) {
- this.resp.onWritable(*RequestContext, onWritableBytes, this);
- return;
- }
-
- this.finalize();
- }
-
- pub fn render(this: *RequestContext, response: *JSC.WebCore.Response) void {
- this.response_ptr = response;
-
- this.doRender();
- }
-
- pub fn resolveRequestBody(this: *RequestContext) void {
- if (this.aborted) {
- this.finalizeForAbort();
- return;
- }
-
- if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
- var bytes = this.request_body_buf.toOwnedSlice(this.allocator);
- var old = req.body;
- req.body = .{
- .Blob = if (bytes.len > 0)
- Blob.init(bytes, this.allocator, this.server.globalThis)
- else
- Blob.initEmpty(this.server.globalThis),
- };
- old.resolve(&req.body, this.server.globalThis);
- VirtualMachine.vm.tick();
- return;
- }
- }
-
- pub fn onBodyChunk(this: *RequestContext, resp: *App.Response, chunk: []const u8, last: bool) void {
- std.debug.assert(this.resp == resp);
-
- if (this.aborted) return;
- this.request_body_buf.appendSlice(this.allocator, chunk) catch @panic("Out of memory while allocating request body");
- if (last) {
- if (JSC.JSValue.fromRef(this.request_js_object).as(Request) != null) {
- uws.Loop.get().?.nextTick(*RequestContext, this, resolveRequestBody);
- } else {
- this.request_body_buf.deinit(this.allocator);
- this.request_body_buf = .{};
- }
- }
- }
-
- pub fn onPull(this: *RequestContext) void {
- if (this.req.header("content-length")) |content_length| {
- const len = std.fmt.parseInt(usize, content_length, 10) catch 0;
- if (len == 0) {
- if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
- var old = req.body;
- old.Locked.callback = null;
- req.body = .{ .Empty = .{} };
- old.resolve(&req.body, this.server.globalThis);
- VirtualMachine.vm.tick();
- return;
- }
- }
-
- if (len >= this.server.config.max_request_body_size) {
- if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
- var old = req.body;
- old.Locked.callback = null;
- req.body = .{ .Empty = .{} };
- old.toError(error.RequestBodyTooLarge, this.server.globalThis);
- VirtualMachine.vm.tick();
- return;
- }
-
- this.resp.writeStatus("413 Request Entity Too Large");
- this.resp.endWithoutBody();
- this.finalize();
- return;
- }
-
- this.request_body_buf.ensureTotalCapacityPrecise(this.allocator, len) catch @panic("Out of memory while allocating request body buffer");
- }
- this.setAbortHandler();
-
- this.resp.onData(*RequestContext, onBodyChunk, this);
- }
-
- pub fn onPullCallback(this: *anyopaque) void {
- onPull(bun.cast(*RequestContext, this));
- }
-
- comptime {
- if (!JSC.is_bindgen) {
- @export(PromiseHandler.resolve, .{
- .name = Export[0].symbol_name,
- });
- @export(PromiseHandler.reject, .{
- .name = Export[1].symbol_name,
- });
- }
- }
- };
-}
-
-pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
- return struct {
- pub const ssl_enabled = ssl_enabled_;
- const debug_mode = debug_mode_;
-
- const ThisServer = @This();
- pub const RequestContext = NewRequestContext(ssl_enabled, debug_mode, @This());
-
- pub const App = uws.NewApp(ssl_enabled);
-
- listener: ?*App.ListenSocket = null,
- thisObject: JSC.JSValue = JSC.JSValue.zero,
- app: *App = undefined,
- vm: *JSC.VirtualMachine = undefined,
- globalThis: *JSGlobalObject,
- base_url_string_for_joining: string = "",
- response_objects_pool: JSC.WebCore.Response.Pool = JSC.WebCore.Response.Pool{},
- config: ServerConfig = ServerConfig{},
- pending_requests: usize = 0,
- request_pool_allocator: std.mem.Allocator = undefined,
- has_js_deinited: bool = false,
- listen_callback: JSC.AnyTask = undefined,
- allocator: std.mem.Allocator,
-
- pub const Class = JSC.NewClass(
- ThisServer,
- .{ .name = "Server" },
- .{
- .stop = .{
- .rfn = JSC.wrapSync(ThisServer, "stopFromJS"),
- },
- .finalize = .{
- .rfn = finalize,
- },
- },
- .{
- .port = .{
- .get = JSC.getterWrap(ThisServer, "getPort"),
- },
- .hostname = .{
- .get = JSC.getterWrap(ThisServer, "getHostname"),
- },
- .development = .{
- .get = JSC.getterWrap(ThisServer, "getDevelopment"),
- },
- .pendingRequests = .{
- .get = JSC.getterWrap(ThisServer, "getPendingRequests"),
- },
- },
- );
-
- pub fn stopFromJS(this: *ThisServer) JSC.JSValue {
- if (this.listener != null) {
- JSC.C.JSValueUnprotect(this.globalThis.ref(), this.thisObject.asObjectRef());
- this.thisObject = JSC.JSValue.jsUndefined();
- this.stop();
- }
-
- return JSC.JSValue.jsUndefined();
- }
-
- pub fn getPort(this: *ThisServer) JSC.JSValue {
- return JSC.JSValue.jsNumber(this.config.port);
- }
-
- pub fn getPendingRequests(this: *ThisServer) JSC.JSValue {
- return JSC.JSValue.jsNumber(@intCast(i32, @truncate(u31, this.pending_requests)));
- }
-
- pub fn getHostname(this: *ThisServer, globalThis: *JSGlobalObject) JSC.JSValue {
- return ZigString.init(this.config.base_uri).toValue(globalThis);
- }
-
- pub fn getDevelopment(
- _: *ThisServer,
- ) JSC.JSValue {
- return JSC.JSValue.jsBoolean(debug_mode);
- }
-
- pub fn onRequestComplete(this: *ThisServer) void {
- this.pending_requests -= 1;
- this.deinitIfWeCan();
- }
-
- pub fn finalize(this: *ThisServer) void {
- this.has_js_deinited = true;
- this.deinitIfWeCan();
- }
-
- pub fn deinitIfWeCan(this: *ThisServer) void {
- if (this.pending_requests == 0 and this.listener == null and this.has_js_deinited)
- this.deinit();
- }
-
- pub fn stop(this: *ThisServer) void {
- if (this.listener) |listener| {
- listener.close();
- this.listener = null;
- this.vm.disable_run_us_loop = false;
- }
-
- this.deinitIfWeCan();
- }
-
- pub fn deinit(this: *ThisServer) void {
- if (this.vm.response_objects_pool) |pool| {
- if (pool == &this.response_objects_pool) {
- this.vm.response_objects_pool = null;
- }
- }
-
- this.app.destroy();
- const allocator = this.allocator;
- allocator.destroy(this);
- }
-
- pub fn init(config: ServerConfig, globalThis: *JSGlobalObject) *ThisServer {
- var server = bun.default_allocator.create(ThisServer) catch @panic("Out of memory!");
- server.* = .{
- .globalThis = globalThis,
- .config = config,
- .base_url_string_for_joining = strings.trim(config.base_url.href, "/"),
- .vm = JSC.VirtualMachine.vm,
- .allocator = Arena.getThreadlocalDefault(),
- };
- if (RequestContext.pool == null) {
- RequestContext.pool = server.allocator.create(RequestContext.RequestContextStackAllocator) catch @panic("Out of memory!");
- RequestContext.pool.?.* = .{
- .fallback_allocator = server.allocator,
- };
- server.request_pool_allocator = RequestContext.pool.?.get();
- RequestContext.pool_allocator = server.request_pool_allocator;
- } else {
- server.request_pool_allocator = RequestContext.pool_allocator;
- }
-
- return server;
- }
-
- noinline fn onListenFailed(this: *ThisServer) void {
- var zig_str: ZigString = ZigString.init("Failed to start server");
- if (comptime ssl_enabled) {
- var output_buf: [4096]u8 = undefined;
- output_buf[0] = 0;
- var written: usize = 0;
- var ssl_error = BoringSSL.ERR_get_error();
- while (ssl_error != 0 and written < output_buf.len) : (ssl_error = BoringSSL.ERR_get_error()) {
- if (written > 0) {
- output_buf[written] = '\n';
- written += 1;
- }
-
- if (BoringSSL.ERR_reason_error_string(
- ssl_error,
- )) |reason_ptr| {
- const reason = std.mem.span(reason_ptr);
- if (reason.len == 0) {
- break;
- }
- @memcpy(output_buf[written..].ptr, reason.ptr, reason.len);
- written += reason.len;
- }
-
- if (BoringSSL.ERR_func_error_string(
- ssl_error,
- )) |reason_ptr| {
- const reason = std.mem.span(reason_ptr);
- if (reason.len > 0) {
- output_buf[written..][0.." via ".len].* = " via ".*;
- written += " via ".len;
- @memcpy(output_buf[written..].ptr, reason.ptr, reason.len);
- written += reason.len;
- }
- }
-
- if (BoringSSL.ERR_lib_error_string(
- ssl_error,
- )) |reason_ptr| {
- const reason = std.mem.span(reason_ptr);
- if (reason.len > 0) {
- output_buf[written..][0] = ' ';
- written += 1;
- @memcpy(output_buf[written..].ptr, reason.ptr, reason.len);
- written += reason.len;
- }
- }
- }
-
- if (written > 0) {
- var message = output_buf[0..written];
- zig_str = ZigString.init(std.fmt.allocPrint(bun.default_allocator, "OpenSSL {s}", .{message}) catch unreachable);
- zig_str.withEncoding().mark();
- }
- }
- // store the exception in here
- this.thisObject = zig_str.toErrorInstance(this.globalThis);
- return;
- }
-
- pub fn onListen(this: *ThisServer, socket: ?*App.ListenSocket, _: uws.uws_app_listen_config_t) void {
- if (socket == null) {
- return this.onListenFailed();
- }
-
- this.listener = socket;
- const needs_post_handler = this.vm.uws_event_loop == null;
- this.vm.uws_event_loop = uws.Loop.get();
- this.vm.response_objects_pool = &this.response_objects_pool;
- this.listen_callback = JSC.AnyTask.New(ThisServer, run).init(this);
- this.vm.eventLoop().enqueueTask(JSC.Task.init(&this.listen_callback));
- if (needs_post_handler) {
- _ = this.vm.uws_event_loop.?.addPostHandler(*JSC.EventLoop, this.vm.eventLoop(), JSC.EventLoop.tick);
- }
- }
-
- pub fn run(this: *ThisServer) void {
- // this.app.addServerName(hostname_pattern: [*:0]const u8)
-
- // we do not increment the reference count here
- // uWS manages running the loop, so it is unnecessary
- // this.vm.us_loop_reference_count +|= 1;
- this.vm.disable_run_us_loop = true;
-
- this.app.run();
- }
-
- pub fn onBunInfoRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
- JSC.markBinding();
- this.pending_requests += 1;
- defer this.pending_requests -= 1;
- req.setYield(false);
- var stack_fallback = std.heap.stackFallback(8096, this.allocator);
- var allocator = stack_fallback.get();
-
- var buffer_writer = js_printer.BufferWriter.init(allocator) catch unreachable;
- var writer = js_printer.BufferPrinter.init(buffer_writer);
- defer writer.ctx.buffer.deinit();
- var source = logger.Source.initEmptyFile("info.json");
- _ = js_printer.printJSON(
- *js_printer.BufferPrinter,
- &writer,
- bun.Global.BunInfo.generate(*Bundler, &JSC.VirtualMachine.vm.bundler, allocator) catch unreachable,
- &source,
- ) catch unreachable;
-
- resp.writeStatus("200 OK");
- resp.writeHeader("Content-Type", MimeType.json.value);
- resp.writeHeader("Cache-Control", "public, max-age=3600");
- resp.writeHeaderInt("Age", 0);
- const buffer = writer.ctx.written;
- resp.end(buffer, false);
- }
-
- pub fn onSrcRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
- JSC.markBinding();
- this.pending_requests += 1;
- defer this.pending_requests -= 1;
- req.setYield(false);
- if (req.header("open-in-editor") == null) {
- resp.writeStatus("501 Not Implemented");
- resp.end("Viewing source without opening in editor is not implemented yet!", false);
- return;
- }
-
- var ctx = &JSC.VirtualMachine.vm.rareData().editor_context;
- ctx.autoDetectEditor(JSC.VirtualMachine.vm.bundler.env);
- var line: ?string = req.header("editor-line");
- var column: ?string = req.header("editor-column");
-
- if (ctx.editor) |editor| {
- resp.writeStatus("200 Opened");
- resp.end("Opened in editor", false);
- var url = req.url()["/src:".len..];
- if (strings.indexOfChar(url, ':')) |colon| {
- url = url[0..colon];
- }
- editor.open(ctx.path, url, line, column, this.allocator) catch Output.prettyErrorln("Failed to open editor", .{});
- } else {
- resp.writeStatus("500 Missing Editor :(");
- resp.end("Please set your editor in bunfig.toml", false);
- }
- }
-
- pub fn onRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
- JSC.markBinding();
- this.pending_requests += 1;
- var vm = this.vm;
- 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 = this.allocator.create(JSC.WebCore.Request) catch unreachable;
- request_object.* = .{
- .url = JSC.ZigString.init(ctx.url),
- .method = ctx.method,
- .uws_request = req,
- .body = .{
- .Locked = .{
- .task = ctx,
- .global = this.globalThis,
- .onPull = RequestContext.onPullCallback,
- },
- },
- };
- request_object.url.mark();
- // We keep the Request object alive for the duration of the request so that we can remove the pointer to the UWS request object.
- var args = [_]JSC.C.JSValueRef{JSC.WebCore.Request.Class.make(this.globalThis.ref(), request_object)};
- ctx.request_js_object = args[0];
- JSC.C.JSValueProtect(this.globalThis.ref(), args[0]);
- const response_value = JSC.C.JSObjectCallAsFunctionReturnValue(this.globalThis.ref(), this.config.onRequest.asObjectRef(), this.thisObject.asObjectRef(), 1, &args);
-
- if (ctx.aborted) {
- ctx.finalizeForAbort();
- return;
- }
- if (response_value.isEmptyOrUndefinedOrNull() and !ctx.resp.hasResponded()) {
- ctx.renderMissing();
- return;
- }
-
- if (response_value.isError() or response_value.isAggregateError(this.globalThis) or response_value.isException(this.globalThis.vm())) {
- ctx.runErrorHandler(response_value);
- return;
- }
-
- if (response_value.as(JSC.WebCore.Response)) |response| {
- JSC.C.JSValueProtect(this.globalThis.ref(), response_value.asObjectRef());
- ctx.response_jsvalue = response_value;
-
- ctx.render(response);
- return;
- }
-
- var wait_for_promise = false;
-
- if (response_value.asPromise()) |promise| {
- // If we immediately have the value available, we can skip the extra event loop tick
- switch (promise.status(vm.global.vm())) {
- .Pending => {},
- .Fulfilled => {
- ctx.handleResolve(promise.result(vm.global.vm()));
- return;
- },
- .Rejected => {
- ctx.handleReject(promise.result(vm.global.vm()));
- return;
- },
- }
- wait_for_promise = true;
- // I don't think this case should happen
- // But I'm uncertain
- } else if (response_value.asInternalPromise()) |promise| {
- switch (promise.status(vm.global.vm())) {
- .Pending => {},
- .Fulfilled => {
- ctx.handleResolve(promise.result(vm.global.vm()));
- return;
- },
- .Rejected => {
- ctx.handleReject(promise.result(vm.global.vm()));
- return;
- },
- }
- wait_for_promise = true;
- }
-
- if (wait_for_promise) {
- ctx.setAbortHandler();
-
- RequestContext.PromiseHandler.then(ctx, response_value, this.globalThis);
- return;
- }
-
- // The user returned something that wasn't a promise or a promise with a response
- if (!ctx.resp.hasResponded()) ctx.renderMissing();
- }
-
- pub fn listen(this: *ThisServer) void {
- if (ssl_enabled) {
- BoringSSL.load();
- const ssl_config = this.config.ssl_config orelse @panic("Assertion failure: ssl_config");
- this.app = App.create(.{
- .key_file_name = ssl_config.key_file_name,
- .cert_file_name = ssl_config.cert_file_name,
- .passphrase = ssl_config.passphrase,
- .dh_params_file_name = ssl_config.dh_params_file_name,
- .ca_file_name = ssl_config.ca_file_name,
- .ssl_prefer_low_memory_usage = @as(c_int, @boolToInt(ssl_config.low_memory_mode)),
- });
-
- if (ssl_config.server_name != null and std.mem.span(ssl_config.server_name).len > 0) {
- this.app.addServerName(ssl_config.server_name);
- }
- } else {
- this.app = App.create(.{});
- }
-
- this.app.any("/*", *ThisServer, this, onRequest);
-
- if (comptime debug_mode) {
- this.app.get("/bun:info", *ThisServer, this, onBunInfoRequest);
- this.app.get("/src:/*", *ThisServer, this, onSrcRequest);
- }
-
- this.app.listenWithConfig(*ThisServer, this, onListen, .{
- .port = this.config.port,
- .host = this.config.hostname,
- .options = 0,
- });
- }
- };
-}
-
-pub const Server = NewServer(false, false);
-pub const SSLServer = NewServer(true, false);
-pub const DebugServer = NewServer(false, true);
-pub const DebugSSLServer = NewServer(true, true);