mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 03:18:53 +00:00
1205 lines
47 KiB
Zig
1205 lines
47 KiB
Zig
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");
|
|
const Fallback = Runtime.Fallback;
|
|
const MimeType = HTTP.MimeType;
|
|
const Blob = JSC.WebCore.Blob;
|
|
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,
|
|
};
|
|
const linux = std.os.linux;
|
|
|
|
pub const ServerConfig = struct {
|
|
port: u16 = 0,
|
|
hostname: [*:0]const u8 = "0.0.0.0",
|
|
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: [*:0]const u8 = "",
|
|
|
|
key_file_name: [*:0]const u8 = "",
|
|
cert_file_name: [*:0]const u8 = "",
|
|
|
|
ca_file_name: [*:0]const u8 = "",
|
|
dh_params_file_name: [*:0]const u8 = "",
|
|
|
|
passphrase: [*:0]const u8 = "",
|
|
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);
|
|
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("Invalid 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);
|
|
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("Invalid 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);
|
|
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);
|
|
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);
|
|
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);
|
|
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 = VirtualMachine.vm.bundler.env;
|
|
|
|
var args = ServerConfig{
|
|
.port = 3000,
|
|
.hostname = "0.0.0.0",
|
|
.development = true,
|
|
};
|
|
|
|
if (strings.eqlComptime(env.get("NODE_ENV") orelse "", "production")) {
|
|
args.development = false;
|
|
}
|
|
|
|
if (VirtualMachine.vm.bundler.options.production) {
|
|
args.development = false;
|
|
}
|
|
|
|
const PORT_ENV = .{ "PORT", "BUN_PORT" };
|
|
|
|
inline for (PORT_ENV) |PORT| {
|
|
if (env.get(PORT)) |port| {
|
|
if (std.fmt.parseInt(u16, port, 10)) |_port| {
|
|
args.port = _port;
|
|
} else |_| {}
|
|
}
|
|
}
|
|
|
|
if (VirtualMachine.vm.bundler.options.transform_options.port) |port| {
|
|
args.port = port;
|
|
}
|
|
|
|
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, "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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return args;
|
|
}
|
|
};
|
|
|
|
// 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);
|
|
|
|
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.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) = .{},
|
|
fallback_buf: std.ArrayListUnmanaged(u8) = .{},
|
|
|
|
pub const RequestContextStackAllocator = std.heap.StackFallbackAllocator(@sizeOf(RequestContext) * 2048 + 4096);
|
|
|
|
// TODO: support builtin compression
|
|
const can_sendfile = !ssl_enabled;
|
|
|
|
pub threadlocal var pool: *RequestContextStackAllocator = undefined;
|
|
|
|
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.finalize();
|
|
return;
|
|
}
|
|
|
|
if (arguments.len == 0 or arguments[0].isEmptyOrUndefinedOrNull()) {
|
|
ctx.renderMissing();
|
|
return;
|
|
}
|
|
|
|
var response = arguments[0].as(JSC.WebCore.Response) orelse {
|
|
Output.prettyErrorln("Expected a Response object", .{});
|
|
Output.flush();
|
|
ctx.renderMissing();
|
|
return;
|
|
};
|
|
ctx.render(response);
|
|
}
|
|
|
|
pub fn onReject(
|
|
ctx: *RequestContext,
|
|
_: *JSC.JSGlobalObject,
|
|
arguments: []const JSC.JSValue,
|
|
) void {
|
|
if (ctx.aborted) {
|
|
ctx.finalize();
|
|
return;
|
|
}
|
|
|
|
ctx.runErrorHandler(
|
|
if (arguments.len > 0) arguments[0] else JSC.JSValue.jsUndefined(),
|
|
);
|
|
|
|
if (ctx.aborted) {
|
|
ctx.finalize();
|
|
return;
|
|
}
|
|
|
|
if (!ctx.resp.hasResponded()) {
|
|
ctx.renderMissing();
|
|
}
|
|
}
|
|
|
|
pub fn renderMissing(ctx: *RequestContext) void {
|
|
if (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);
|
|
|
|
var allocator = bun.default_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.fallback_buf = std.ArrayListUnmanaged(u8){ .items = bb.items, .capacity = bb.capacity };
|
|
this.resp.onWritable(*RequestContext, onWritableFallback, this);
|
|
}
|
|
|
|
pub fn onWritableFallback(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
|
|
if (this.aborted) {
|
|
return false;
|
|
}
|
|
return this.sendWritableBytes(this.fallback_buf.items, write_offset, resp);
|
|
}
|
|
|
|
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.finalizeWithoutDeinit();
|
|
}
|
|
|
|
// 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();
|
|
this.request_body_buf.clearAndFree(bun.default_allocator);
|
|
|
|
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) {
|
|
// User called .blob(), .json(), text(), or .arrayBuffer() on the Request object
|
|
// but we received nothing
|
|
if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
|
|
if (req.body == .Locked and req.body.Locked.action != .none and req.body.Locked.promise != null) {
|
|
var old_body = req.body;
|
|
req.body = JSC.WebCore.Body.Value.empty;
|
|
old_body.Locked.callback = null;
|
|
old_body.resolve(&req.body, this.server.globalThis);
|
|
VirtualMachine.vm.tick();
|
|
}
|
|
req.uws_request = null;
|
|
JSC.C.JSValueUnprotect(this.server.globalThis.ref(), this.request_js_object);
|
|
this.request_js_object = null;
|
|
}
|
|
}
|
|
|
|
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.fallback_buf.clearAndFree(bun.default_allocator);
|
|
}
|
|
pub fn finalize(this: *RequestContext) void {
|
|
this.finalizeWithoutDeinit();
|
|
|
|
this.server.request_pool_allocator.destroy(this);
|
|
}
|
|
|
|
fn writeHeaders(
|
|
this: *RequestContext,
|
|
headers: *JSC.FetchHeaders,
|
|
) void {
|
|
headers.remove(&ZigString.init("content-length"));
|
|
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 cleanupAfterSendfile(this: *RequestContext) void {
|
|
this.resp.setWriteOffset(this.sendfile.offset);
|
|
this.resp.endWithoutBody();
|
|
// use node syscall so that we don't segfault on BADF
|
|
_ = 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 {
|
|
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 != .SUCCESS) {
|
|
Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
|
|
Output.flush();
|
|
}
|
|
this.cleanupAfterSendfile();
|
|
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) {
|
|
Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
|
|
Output.flush();
|
|
}
|
|
this.cleanupAfterSendfile();
|
|
return errcode == .SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (!this.sendfile.has_set_on_writable) {
|
|
this.sendfile.has_set_on_writable = true;
|
|
this.resp.onWritable(*RequestContext, onWritableSendfile, this);
|
|
}
|
|
|
|
this.resp.markNeedsMore();
|
|
|
|
return true;
|
|
}
|
|
|
|
pub fn onWritableBytes(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
|
|
if (this.aborted)
|
|
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 {
|
|
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();
|
|
}
|
|
|
|
pub fn onPrepareSendfileWrap(this: *anyopaque, fd: i32, size: anyerror!Blob.SizeType, _: *JSGlobalObject) void {
|
|
onPrepareSendfile(bun.cast(*RequestContext, this), fd, size);
|
|
}
|
|
|
|
fn onPrepareSendfile(this: *RequestContext, fd: i32, size: anyerror!Blob.SizeType) void {
|
|
this.setAbortHandler();
|
|
if (this.aborted) return;
|
|
const size_ = size catch {
|
|
this.req.setYield(true);
|
|
this.finalize();
|
|
return;
|
|
};
|
|
this.blob.size = size_;
|
|
this.needs_content_length = true;
|
|
this.sendfile = .{
|
|
.fd = fd,
|
|
.remain = size_,
|
|
.socket_fd = this.resp.getNativeHandle(),
|
|
};
|
|
|
|
this.resp.runCorked(*RequestContext, renderMetadata, this);
|
|
|
|
if (size_ == 0) {
|
|
this.cleanupAfterSendfile();
|
|
this.finalize();
|
|
|
|
return;
|
|
}
|
|
|
|
// TODO: fix this to be MSGHDR
|
|
_ = std.os.write(this.sendfile.socket_fd, "\r\n") catch 0;
|
|
|
|
_ = this.onSendfile();
|
|
}
|
|
|
|
pub fn renderSendFile(this: *RequestContext, blob: JSC.WebCore.Blob) void {
|
|
JSC.WebCore.Blob.doOpenAndStatFile(
|
|
&this.blob,
|
|
*RequestContext,
|
|
this,
|
|
onPrepareSendfileWrap,
|
|
blob.globalThis,
|
|
);
|
|
}
|
|
|
|
pub fn doSendfile(this: *RequestContext, blob: Blob) void {
|
|
if (this.has_sendfile_ctx) return;
|
|
this.has_sendfile_ctx = true;
|
|
this.setAbortHandler();
|
|
|
|
if (comptime can_sendfile) {
|
|
return this.renderSendFile(blob);
|
|
}
|
|
|
|
this.blob.doReadFileInternal(*RequestContext, this, onReadFile, this.server.globalThis);
|
|
}
|
|
|
|
pub fn onReadFile(this: *RequestContext, result: Blob.Store.ReadFile.ResultType) void {
|
|
if (result == .err) {
|
|
this.runErrorHandler(result.err.toErrorInstance(this.server.globalThis));
|
|
return;
|
|
}
|
|
|
|
this.blob.resolveSize();
|
|
this.doRenderBlob();
|
|
}
|
|
|
|
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 {
|
|
if (this.aborted) {
|
|
this.finalize();
|
|
return;
|
|
}
|
|
|
|
switch (value.*) {
|
|
.Error => {
|
|
const err = value.Error;
|
|
_ = value.use();
|
|
this.runErrorHandler(err);
|
|
return;
|
|
},
|
|
.Blob => {
|
|
this.blob = value.use();
|
|
|
|
if (this.blob.needsToReadFile()) {
|
|
this.req.setYield(false);
|
|
this.setAbortHandler();
|
|
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) {
|
|
return;
|
|
}
|
|
var response = this.response_ptr.?;
|
|
this.doRenderWithBody(&response.body.value);
|
|
}
|
|
|
|
pub fn renderProductionError(this: *RequestContext) void {
|
|
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 {
|
|
if (this.resp.hasResponded()) return;
|
|
|
|
var exception_list: std.ArrayList(Api.JsException) = std.ArrayList(Api.JsException).init(bun.default_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(), null, 1, &args);
|
|
|
|
if (!result.isEmptyOrUndefinedOrNull()) {
|
|
JSC.C.JSValueProtect(this.server.globalThis.ref(), result.asObjectRef());
|
|
if (result.isError() or result.isAggregateError(this.server.globalThis)) {
|
|
this.runErrorHandler(result);
|
|
JSC.C.JSValueUnprotect(this.server.globalThis.ref(), result.asObjectRef());
|
|
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 {
|
|
JSC.VirtualMachine.vm.defaultErrorHandler(value, &exception_list);
|
|
this.renderProductionError();
|
|
}
|
|
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)
|
|
return;
|
|
if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
|
|
var bytes = this.request_body_buf.toOwnedSlice(bun.default_allocator);
|
|
var old = req.body;
|
|
req.body = .{
|
|
.Blob = if (bytes.len > 0)
|
|
Blob.init(bytes, bun.default_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, _: *App.Response, chunk: []const u8, last: bool) void {
|
|
if (this.aborted) return;
|
|
this.request_body_buf.appendSlice(bun.default_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(bun.default_allocator);
|
|
this.request_body_buf = .{};
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn onRequestData(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(bun.default_allocator, len) catch @panic("Out of memory while allocating request body buffer");
|
|
}
|
|
this.setAbortHandler();
|
|
|
|
this.resp.onData(*RequestContext, onBodyChunk, this);
|
|
}
|
|
|
|
pub fn onRequestDataCallback(this: *anyopaque) void {
|
|
onRequestData(bun.cast(*RequestContext, this));
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
|
return struct {
|
|
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,
|
|
|
|
app: *App = undefined,
|
|
globalThis: *JSGlobalObject,
|
|
|
|
response_objects_pool: JSC.WebCore.Response.Pool = JSC.WebCore.Response.Pool{},
|
|
config: ServerConfig = ServerConfig{},
|
|
request_pool_allocator: std.mem.Allocator = undefined,
|
|
|
|
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,
|
|
};
|
|
RequestContext.pool = bun.default_allocator.create(RequestContext.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) {
|
|
JSC.VirtualMachine.vm.defaultErrorHandler(ZigString.init("Bun.serve failed to start").toErrorInstance(this.globalThis), null);
|
|
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 fn onBunInfoRequest(_: *ThisServer, req: *uws.Request, resp: *App.Response) void {
|
|
if (comptime JSC.is_bindgen) return undefined;
|
|
req.setYield(false);
|
|
var stack_fallback = std.heap.stackFallback(8096, bun.default_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(_: *ThisServer, req: *uws.Request, resp: *App.Response) void {
|
|
if (comptime JSC.is_bindgen) return undefined;
|
|
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, bun.default_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 {
|
|
if (comptime JSC.is_bindgen) return undefined;
|
|
|
|
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,
|
|
.uws_request = req,
|
|
.body = .{
|
|
.Locked = .{
|
|
.task = ctx,
|
|
.global = this.globalThis,
|
|
.onRequestData = RequestContext.onRequestDataCallback,
|
|
},
|
|
},
|
|
};
|
|
// 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]);
|
|
ctx.response_jsvalue = JSC.C.JSObjectCallAsFunctionReturnValue(this.globalThis.ref(), this.config.onRequest.asObjectRef(), null, 1, &args);
|
|
defer JSC.VirtualMachine.vm.tick();
|
|
if (ctx.aborted) {
|
|
ctx.finalize();
|
|
|
|
return;
|
|
}
|
|
|
|
if (ctx.response_jsvalue.isEmptyOrUndefinedOrNull() and !ctx.resp.hasResponded()) {
|
|
ctx.renderMissing();
|
|
return;
|
|
}
|
|
|
|
if (ctx.response_jsvalue.isError() or ctx.response_jsvalue.isAggregateError(this.globalThis) or ctx.response_jsvalue.isException(this.globalThis.vm())) {
|
|
ctx.runErrorHandler(ctx.response_jsvalue);
|
|
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) {
|
|
ctx.setAbortHandler();
|
|
JSC.VirtualMachine.vm.tick();
|
|
|
|
ctx.response_jsvalue.then(
|
|
this.globalThis,
|
|
RequestContext,
|
|
ctx,
|
|
RequestContext.onResolve,
|
|
RequestContext.onReject,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// switch (ctx.response_jsvalue.jsTypeLoose()) {
|
|
// .JSPromise => {
|
|
// JSPromise.
|
|
// },
|
|
// }
|
|
}
|
|
|
|
pub fn listen(this: *ThisServer) void {
|
|
if (ssl_enabled) {
|
|
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 (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, true);
|
|
pub const SSLServer = NewServer(true, true);
|
|
|
|
pub const DebugServer = NewServer(false, true);
|
|
pub const DebugSSLServer = NewServer(true, true);
|