diff options
Diffstat (limited to 'src/bun.js/api')
-rw-r--r-- | src/bun.js/api/JSTranspiler.zig | 6 | ||||
-rw-r--r-- | src/bun.js/api/bun.zig | 18 | ||||
-rw-r--r-- | src/bun.js/api/bun/dns_resolver.zig | 335 | ||||
-rw-r--r-- | src/bun.js/api/bun/socket.zig | 37 | ||||
-rw-r--r-- | src/bun.js/api/bun/subprocess.zig | 62 | ||||
-rw-r--r-- | src/bun.js/api/bun/x509.zig | 6 | ||||
-rw-r--r-- | src/bun.js/api/ffi.zig | 6 | ||||
-rw-r--r-- | src/bun.js/api/html_rewriter.zig | 28 | ||||
-rw-r--r-- | src/bun.js/api/server.classes.ts | 8 | ||||
-rw-r--r-- | src/bun.js/api/server.zig | 272 |
10 files changed, 646 insertions, 132 deletions
diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index 8cec025eb..d41458acb 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -72,6 +72,7 @@ const TranspilerOptions = struct { trim_unused_imports: ?bool = null, inlining: bool = false, + dead_code_elimination: bool = true, minify_whitespace: bool = false, minify_identifiers: bool = false, minify_syntax: bool = false, @@ -541,6 +542,10 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std transpiler.minify_whitespace = flag.toBoolean(); } + if (object.get(globalThis, "deadCodeElimination")) |flag| { + transpiler.dead_code_elimination = flag.toBoolean(); + } + if (object.getTruthy(globalThis, "minify")) |hot| { if (hot.isBoolean()) { transpiler.minify_whitespace = hot.coerce(bool, globalThis); @@ -800,6 +805,7 @@ pub fn constructor( bundler.options.macro_remap = transpiler_options.macro_map; } + bundler.options.dead_code_elimination = transpiler_options.dead_code_elimination; bundler.options.minify_whitespace = transpiler_options.minify_whitespace; // Keep defaults for these diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 966c82d38..21c2ecd0e 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -782,11 +782,17 @@ fn doResolveWithArgs( var errorable: ErrorableString = undefined; var query_string = ZigString.Empty; + const specifier_decoded = if (specifier.hasPrefixComptime("file://")) + bun.JSC.URL.pathFromFileURL(specifier) + else + specifier.dupeRef(); + defer specifier_decoded.deref(); + if (comptime is_file_path) { VirtualMachine.resolveFilePathForAPI( &errorable, ctx.ptr(), - specifier, + specifier_decoded, from, &query_string, is_esm, @@ -795,7 +801,7 @@ fn doResolveWithArgs( VirtualMachine.resolveForAPI( &errorable, ctx.ptr(), - specifier, + specifier_decoded, from, &query_string, is_esm, @@ -1098,10 +1104,12 @@ pub const Crypto = struct { } pub fn reset(this: *EVP, engine: *BoringSSL.ENGINE) void { + BoringSSL.ERR_clear_error(); _ = BoringSSL.EVP_DigestInit_ex(&this.ctx, this.md, engine); } pub fn hash(this: *EVP, engine: *BoringSSL.ENGINE, input: []const u8, output: []u8) ?u32 { + BoringSSL.ERR_clear_error(); var outsize: c_uint = @min(@as(u16, @truncate(output.len)), this.size()); if (BoringSSL.EVP_Digest(input.ptr, input.len, output.ptr, &outsize, this.md, engine) != 1) { return null; @@ -1111,6 +1119,7 @@ pub const Crypto = struct { } pub fn final(this: *EVP, engine: *BoringSSL.ENGINE, output: []u8) []const u8 { + BoringSSL.ERR_clear_error(); var outsize: u32 = @min(@as(u16, @truncate(output.len)), this.size()); if (BoringSSL.EVP_DigestFinal_ex( &this.ctx, @@ -1126,6 +1135,7 @@ pub const Crypto = struct { } pub fn update(this: *EVP, input: []const u8) void { + BoringSSL.ERR_clear_error(); _ = BoringSSL.EVP_DigestUpdate(&this.ctx, input.ptr, input.len); } @@ -1134,6 +1144,7 @@ pub const Crypto = struct { } pub fn copy(this: *const EVP, engine: *BoringSSL.ENGINE) error{OutOfMemory}!EVP { + BoringSSL.ERR_clear_error(); var new = init(this.algorithm, this.md, engine); if (BoringSSL.EVP_MD_CTX_copy_ex(&new.ctx, &this.ctx) == 0) { return error.OutOfMemory; @@ -2004,7 +2015,6 @@ pub const Crypto = struct { pub const digest = JSC.wrapInstanceMethod(CryptoHasher, "digest_", false); pub const hash = JSC.wrapStaticMethod(CryptoHasher, "hash_", false); - pub fn getByteLength( this: *CryptoHasher, _: *JSC.JSGlobalObject, @@ -3594,7 +3604,7 @@ pub const Timer = struct { this.poll_ref.unref(vm); - this.timer.deinit(); + this.timer.deinit(false); // balance double unreffing in doUnref vm.event_loop_handle.?.num_polls += @as(i32, @intFromBool(this.did_unref_timer)); diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig index 8232318a2..3c20f4df7 100644 --- a/src/bun.js/api/bun/dns_resolver.zig +++ b/src/bun.js/api/bun/dns_resolver.zig @@ -102,11 +102,12 @@ const LibInfo = struct { ) catch unreachable; const promise_value = request.head.promise.value(); + const hints = query.options.toLibC(); const errno = getaddrinfo_async_start_( &request.backend.libinfo.machport, name_z.ptr, null, - null, + if (hints != null) &hints.? else null, GetAddrInfoRequest.getAddrInfoAsyncCallback, request, ); @@ -812,6 +813,170 @@ pub const GetHostByAddrInfoRequest = struct { } }; +pub const CAresNameInfo = struct { + const log = Output.scoped(@This(), true); + + globalThis: *JSC.JSGlobalObject = undefined, + promise: JSC.JSPromise.Strong, + poll_ref: JSC.PollRef, + allocated: bool = false, + next: ?*@This() = null, + name: []const u8, + + pub fn init(globalThis: *JSC.JSGlobalObject, allocator: std.mem.Allocator, name: []const u8) !*@This() { + var this = try allocator.create(@This()); + var poll_ref = JSC.PollRef.init(); + poll_ref.ref(globalThis.bunVM()); + this.* = .{ .globalThis = globalThis, .promise = JSC.JSPromise.Strong.init(globalThis), .poll_ref = poll_ref, .allocated = true, .name = name }; + return this; + } + + pub fn processResolve(this: *@This(), err_: ?c_ares.Error, _: i32, result: ?c_ares.struct_nameinfo) void { + if (err_) |err| { + var promise = this.promise; + var globalThis = this.globalThis; + const error_value = globalThis.createErrorInstance("lookupService failed: {s}", .{err.label()}); + error_value.put( + globalThis, + JSC.ZigString.static("code"), + JSC.ZigString.init(err.code()).toValueGC(globalThis), + ); + + promise.reject(globalThis, error_value); + this.deinit(); + return; + } + if (result == null) { + var promise = this.promise; + var globalThis = this.globalThis; + const error_value = globalThis.createErrorInstance("lookupService failed: No results", .{}); + error_value.put( + globalThis, + JSC.ZigString.static("code"), + JSC.ZigString.init("EUNREACHABLE").toValueGC(globalThis), + ); + + promise.reject(globalThis, error_value); + this.deinit(); + return; + } + var name_info = result.?; + const array = name_info.toJSResponse(this.globalThis.allocator(), this.globalThis); + this.onComplete(array); + return; + } + + pub fn onComplete(this: *@This(), result: JSC.JSValue) void { + var promise = this.promise; + var globalThis = this.globalThis; + this.promise = .{}; + promise.resolve(globalThis, result); + this.deinit(); + } + + pub fn deinit(this: *@This()) void { + this.poll_ref.unrefOnNextTick(this.globalThis.bunVM()); + // freed + bun.default_allocator.free(this.name); + + if (this.allocated) + this.globalThis.allocator().destroy(this); + } +}; + +pub const GetNameInfoRequest = struct { + const request_type = @This(); + + const log = Output.scoped(@This(), false); + + resolver_for_caching: ?*DNSResolver = null, + hash: u64 = 0, + cache: @This().CacheConfig = @This().CacheConfig{}, + head: CAresNameInfo, + tail: *CAresNameInfo = undefined, + + pub fn init( + cache: DNSResolver.LookupCacheHit(@This()), + resolver: ?*DNSResolver, + name: []const u8, + globalThis: *JSC.JSGlobalObject, + comptime cache_field: []const u8, + ) !*@This() { + var request = try globalThis.allocator().create(@This()); + var hasher = std.hash.Wyhash.init(0); + hasher.update(name); + const hash = hasher.final(); + var poll_ref = JSC.PollRef.init(); + poll_ref.ref(globalThis.bunVM()); + request.* = .{ + .resolver_for_caching = resolver, + .hash = hash, + .head = .{ .poll_ref = poll_ref, .globalThis = globalThis, .promise = JSC.JSPromise.Strong.init(globalThis), .allocated = false, .name = name }, + }; + request.tail = &request.head; + if (cache == .new) { + request.resolver_for_caching = resolver; + request.cache = @This().CacheConfig{ + .pending_cache = true, + .entry_cache = false, + .pos_in_pending = @as(u5, @truncate(@field(resolver.?, cache_field).indexOf(cache.new).?)), + .name_len = @as(u9, @truncate(name.len)), + }; + cache.new.lookup = request; + } + return request; + } + + pub const CacheConfig = packed struct(u16) { + pending_cache: bool = false, + entry_cache: bool = false, + pos_in_pending: u5 = 0, + name_len: u9 = 0, + }; + + pub const PendingCacheKey = struct { + hash: u64, + len: u16, + lookup: *request_type = undefined, + + pub fn append(this: *PendingCacheKey, cares_lookup: *CAresNameInfo) void { + var tail = this.lookup.tail; + tail.next = cares_lookup; + this.lookup.tail = cares_lookup; + } + + pub fn init(name: []const u8) PendingCacheKey { + var hasher = std.hash.Wyhash.init(0); + hasher.update(name); + const hash = hasher.final(); + return PendingCacheKey{ + .hash = hash, + .len = @as(u16, @truncate(name.len)), + .lookup = undefined, + }; + } + }; + + pub fn onCaresComplete(this: *@This(), err_: ?c_ares.Error, timeout: i32, result: ?c_ares.struct_nameinfo) void { + if (this.resolver_for_caching) |resolver| { + if (this.cache.pending_cache) { + resolver.drainPendingNameInfoCares( + this.cache.pos_in_pending, + err_, + timeout, + result, + ); + return; + } + } + + var head = this.head; + bun.default_allocator.destroy(this); + + head.processResolve(err_, timeout, result); + } +}; + pub const GetAddrInfoRequest = struct { const log = Output.scoped(.GetAddrInfoRequest, false); @@ -1086,7 +1251,7 @@ pub const CAresReverse = struct { return; } var node = result.?; - const array = node.toJSReponse(this.globalThis.allocator(), this.globalThis, ""); + const array = node.toJSResponse(this.globalThis.allocator(), this.globalThis, ""); this.onComplete(array); return; } @@ -1157,7 +1322,7 @@ pub fn CAresLookup(comptime cares_type: type, comptime type_name: []const u8) ty return; } var node = result.?; - const array = node.toJSReponse(this.globalThis.allocator(), this.globalThis, type_name); + const array = node.toJSResponse(this.globalThis.allocator(), this.globalThis, type_name); this.onComplete(array); return; } @@ -1325,6 +1490,7 @@ pub const DNSResolver = struct { pending_ptr_cache_cares: PtrPendingCache = PtrPendingCache.init(), pending_cname_cache_cares: CnamePendingCache = CnamePendingCache.init(), pending_addr_cache_crares: AddrPendingCache = AddrPendingCache.init(), + pending_nameinfo_cache_cares: NameInfoPendingCache = NameInfoPendingCache.init(), const PendingCache = bun.HiveArray(GetAddrInfoRequest.PendingCacheKey, 32); const SrvPendingCache = bun.HiveArray(ResolveInfoRequest(c_ares.struct_ares_srv_reply, "srv").PendingCacheKey, 32); @@ -1337,6 +1503,7 @@ pub const DNSResolver = struct { const PtrPendingCache = bun.HiveArray(ResolveInfoRequest(c_ares.struct_hostent, "ptr").PendingCacheKey, 32); const CnamePendingCache = bun.HiveArray(ResolveInfoRequest(c_ares.struct_hostent, "cname").PendingCacheKey, 32); const AddrPendingCache = bun.HiveArray(GetHostByAddrInfoRequest.PendingCacheKey, 32); + const NameInfoPendingCache = bun.HiveArray(GetNameInfoRequest.PendingCacheKey, 32); fn getKey(this: *DNSResolver, index: u8, comptime cache_name: []const u8, comptime request_type: type) request_type.PendingCacheKey { var cache = &@field(this, cache_name); @@ -1370,7 +1537,7 @@ pub const DNSResolver = struct { var pending: ?*CAresLookup(cares_type, lookup_name) = key.lookup.head.next; var prev_global = key.lookup.head.globalThis; - var array = addr.toJSReponse(this.vm.allocator, prev_global, lookup_name); + var array = addr.toJSResponse(this.vm.allocator, prev_global, lookup_name); defer addr.deinit(); array.ensureStillAlive(); key.lookup.head.onComplete(array); @@ -1381,7 +1548,7 @@ pub const DNSResolver = struct { while (pending) |value| { var new_global = value.globalThis; if (prev_global != new_global) { - array = addr.toJSReponse(this.vm.allocator, new_global, lookup_name); + array = addr.toJSResponse(this.vm.allocator, new_global, lookup_name); prev_global = new_global; } pending = value.next; @@ -1500,7 +1667,48 @@ pub const DNSResolver = struct { // The callback need not and should not attempt to free the memory // pointed to by hostent; the ares library will free it when the // callback returns. - var array = addr.toJSReponse(this.vm.allocator, prev_global, ""); + var array = addr.toJSResponse(this.vm.allocator, prev_global, ""); + array.ensureStillAlive(); + key.lookup.head.onComplete(array); + bun.default_allocator.destroy(key.lookup); + + array.ensureStillAlive(); + + while (pending) |value| { + var new_global = value.globalThis; + if (prev_global != new_global) { + array = addr.toJSResponse(this.vm.allocator, new_global, ""); + prev_global = new_global; + } + pending = value.next; + + { + array.ensureStillAlive(); + value.onComplete(array); + array.ensureStillAlive(); + } + } + } + + pub fn drainPendingNameInfoCares(this: *DNSResolver, index: u8, err: ?c_ares.Error, timeout: i32, result: ?c_ares.struct_nameinfo) void { + const key = this.getKey(index, "pending_nameinfo_cache_cares", GetNameInfoRequest); + + var name_info = result orelse { + var pending: ?*CAresNameInfo = key.lookup.head.next; + key.lookup.head.processResolve(err, timeout, null); + bun.default_allocator.destroy(key.lookup); + + while (pending) |value| { + pending = value.next; + value.processResolve(err, timeout, null); + } + return; + }; + + var pending: ?*CAresNameInfo = key.lookup.head.next; + var prev_global = key.lookup.head.globalThis; + + var array = name_info.toJSResponse(this.vm.allocator, prev_global); array.ensureStillAlive(); key.lookup.head.onComplete(array); bun.default_allocator.destroy(key.lookup); @@ -1510,7 +1718,7 @@ pub const DNSResolver = struct { while (pending) |value| { var new_global = value.globalThis; if (prev_global != new_global) { - array = addr.toJSReponse(this.vm.allocator, new_global, ""); + array = name_info.toJSResponse(this.vm.allocator, new_global); prev_global = new_global; } pending = value.next; @@ -1977,11 +2185,6 @@ pub const DNSResolver = struct { return .zero; }; - if (name_str.length() == 0) { - globalThis.throwInvalidArgumentType("resolveSoa", "hostname", "non-empty string"); - return .zero; - } - const name = name_str.toSliceClone(globalThis, bun.default_allocator); var vm = globalThis.bunVM(); @@ -2039,11 +2242,6 @@ pub const DNSResolver = struct { return .zero; }; - if (name_str.length() == 0) { - globalThis.throwInvalidArgumentType("resolveNs", "hostname", "non-empty string"); - return .zero; - } - const name = name_str.toSliceClone(globalThis, bun.default_allocator); var vm = globalThis.bunVM(); @@ -2278,7 +2476,7 @@ pub const DNSResolver = struct { return dns_lookup.promise.value(); } - // var hints_buf = &[_]c_ares.AddrInfo_hints{query.toCAres()}; + var hints_buf = &[_]c_ares.AddrInfo_hints{query.toCAres()}; var request = GetAddrInfoRequest.init( cache, .{ @@ -2294,7 +2492,7 @@ pub const DNSResolver = struct { channel.getAddrInfo( query.name, query.port, - &.{}, + hints_buf, GetAddrInfoRequest, request, GetAddrInfoRequest.onCaresComplete, @@ -2386,6 +2584,95 @@ pub const DNSResolver = struct { return values; } + // Resolves the given address and port into a host name and service using the operating system's underlying getnameinfo implementation. + // If address is not a valid IP address, a TypeError will be thrown. The port will be coerced to a number. + // If it is not a legal port, a TypeError will be thrown. + pub fn lookupService(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { + const arguments = callframe.arguments(3); + if (arguments.len < 2) { + globalThis.throwNotEnoughArguments("lookupService", 3, arguments.len); + return .zero; + } + + const addr_value = arguments.ptr[0]; + const port_value = arguments.ptr[1]; + if (addr_value.isEmptyOrUndefinedOrNull() or !addr_value.isString()) { + globalThis.throwInvalidArgumentType("lookupService", "address", "string"); + return .zero; + } + const addr_str = addr_value.toStringOrNull(globalThis) orelse { + return .zero; + }; + if (addr_str.length() == 0) { + globalThis.throwInvalidArgumentType("lookupService", "address", "non-empty string"); + return .zero; + } + + const addr_s = addr_str.getZigString(globalThis).slice(); + const port: u16 = if (port_value.isNumber()) blk: { + break :blk port_value.to(u16); + } else { + globalThis.throwInvalidArgumentType("lookupService", "port", "invalid port"); + return .zero; + }; + + var sa: std.os.sockaddr.storage = std.mem.zeroes(std.os.sockaddr.storage); + if (c_ares.getSockaddr(addr_s, port, @as(*std.os.sockaddr, @ptrCast(&sa))) != 0) { + globalThis.throwInvalidArgumentType("lookupService", "address", "invalid address"); + return .zero; + } + + var vm = globalThis.bunVM(); + var resolver = vm.rareData().globalDNSResolver(vm); + var channel: *c_ares.Channel = switch (resolver.getChannel()) { + .result => |res| res, + .err => |err| { + const system_error = JSC.SystemError{ + .errno = -1, + .code = bun.String.static(err.code()), + .message = bun.String.static(err.label()), + }; + + globalThis.throwValue(system_error.toErrorInstance(globalThis)); + return .zero; + }, + }; + + // This string will be freed in `CAresNameInfo.deinit` + const cache_name = std.fmt.allocPrint(bun.default_allocator, "{s}|{d}", .{ addr_s, port }) catch unreachable; + + const key = GetNameInfoRequest.PendingCacheKey.init(cache_name); + var cache = resolver.getOrPutIntoResolvePendingCache( + GetNameInfoRequest, + key, + "pending_nameinfo_cache_cares", + ); + + if (cache == .inflight) { + var info = CAresNameInfo.init(globalThis, globalThis.allocator(), cache_name) catch unreachable; + cache.inflight.append(info); + return info.promise.value(); + } + + var request = GetNameInfoRequest.init( + cache, + resolver, + cache_name, // transfer ownership here + globalThis, + "pending_nameinfo_cache_cares", + ) catch unreachable; + + const promise = request.tail.promise.value(); + channel.getNameInfo( + @as(*std.os.sockaddr, @ptrCast(&sa)), + GetNameInfoRequest, + request, + GetNameInfoRequest.onCaresComplete, + ); + + return promise; + } + comptime { @export( resolve, @@ -2465,11 +2752,13 @@ pub const DNSResolver = struct { .name = "Bun__DNSResolver__reverse", }, ); + @export( + lookupService, + .{ + .name = "Bun__DNSResolver__lookupService", + }, + ); } - // pub fn lookupService(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - // const arguments = callframe.arguments(3); - - // } // pub fn cancel(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { // const arguments = callframe.arguments(3); diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 2ad44ffb0..e89ee5aa1 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -771,7 +771,7 @@ pub const Listener = struct { Socket.dataSetCached(this_socket.getThisValue(globalObject), globalObject, default_data); } socket.ext(**anyopaque).?.* = bun.cast(**anyopaque, this_socket); - socket.timeout(120000); + socket.setTimeout(120000); } // pub fn addServerName(this: *Listener, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { @@ -996,7 +996,12 @@ pub const Listener = struct { handlers.vm.allocator.destroy(handlers_ptr); handlers.promise.deinit(); bun.default_allocator.destroy(tls); - exception.* = ZigString.static("Failed to connect").toErrorInstance(globalObject).asObjectRef(); + const err = JSC.SystemError{ + .message = bun.String.static("Failed to connect"), + .syscall = bun.String.static("connect"), + .code = if (port == null) bun.String.static("ENOENT") else bun.String.static("ECONNREFUSED"), + }; + exception.* = err.toErrorInstance(globalObject).asObjectRef(); return .zero; }; tls.poll_ref.ref(handlers.vm); @@ -1022,7 +1027,12 @@ pub const Listener = struct { handlers.vm.allocator.destroy(handlers_ptr); handlers.promise.deinit(); bun.default_allocator.destroy(tcp); - exception.* = ZigString.static("Failed to connect").toErrorInstance(globalObject).asObjectRef(); + const err = JSC.SystemError{ + .message = bun.String.static("Failed to connect"), + .syscall = bun.String.static("connect"), + .code = if (port == null) bun.String.static("ENOENT") else bun.String.static("ECONNREFUSED"), + }; + exception.* = err.toErrorInstance(globalObject).asObjectRef(); return .zero; }; tcp.poll_ref.ref(handlers.vm); @@ -1205,6 +1215,12 @@ fn NewSocket(comptime ssl: bool) type { .errno = errno, .message = bun.String.static("Failed to connect"), .syscall = bun.String.static("connect"), + + // For some reason errno is 0 which causes this to be success. + // Unix socket case wont hit this callback because it instantly errors. + .code = bun.String.static("ECONNREFUSED"), + // .code = bun.String.static(@tagName(bun.sys.getErrno(errno))), + // .code = bun.String.static(@tagName(@as(bun.C.E, @enumFromInt(errno)))), }; if (callback == .zero) { @@ -1583,7 +1599,7 @@ fn NewSocket(comptime ssl: bool) type { return .zero; } - this.socket.timeout(@as(c_uint, @intCast(t))); + this.socket.setTimeout(@as(c_uint, @intCast(t))); return JSValue.jsUndefined(); } @@ -1913,7 +1929,7 @@ fn NewSocket(comptime ssl: bool) type { } pub fn finalize(this: *This) callconv(.C) void { - log("finalize()", .{}); + log("finalize() {d}", .{@intFromPtr(this)}); if (!this.detached) { this.detached = true; if (!this.socket.isClosed()) { @@ -2946,6 +2962,17 @@ pub fn NewWrappedHandler(comptime tls: bool) type { } } + pub fn onLongTimeout( + this: WrappedSocket, + socket: Socket, + ) void { + if (comptime tls) { + TLSSocket.onTimeout(this.tls, socket); + } else { + TLSSocket.onTimeout(this.tcp, socket); + } + } + pub fn onConnectError( this: WrappedSocket, socket: Socket, diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index 5695c15ad..f6d86d91a 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -62,12 +62,11 @@ pub const Subprocess = struct { is_sync: bool = false, this_jsvalue: JSC.JSValue = .zero, - ipc: IPCMode, - // this is only ever accessed when `ipc` is not `none` - ipc_socket: IPC.Socket = undefined, + ipc_mode: IPCMode, ipc_callback: JSC.Strong = .{}, - ipc_buffer: bun.ByteList, + ipc: IPC.IPCData, + has_pending_unref: bool = false, pub const SignalCode = bun.SignalCode; pub const IPCMode = enum { @@ -82,7 +81,7 @@ pub const Subprocess = struct { pub fn updateHasPendingActivityFlag(this: *Subprocess) void { @fence(.SeqCst); - this.has_pending_activity.store(this.waitpid_err == null and this.exit_code == null and this.ipc == .none, .SeqCst); + this.has_pending_activity.store(this.waitpid_err == null and this.exit_code == null and this.ipc_mode == .none and this.has_pending_unref, .SeqCst); } pub fn hasPendingActivity(this: *Subprocess) callconv(.C) bool { @@ -92,7 +91,7 @@ pub const Subprocess = struct { pub fn updateHasPendingActivity(this: *Subprocess) void { @fence(.Release); - this.has_pending_activity.store(this.waitpid_err == null and this.exit_code == null and this.ipc == .none, .Release); + this.has_pending_activity.store(this.waitpid_err == null and this.exit_code == null and this.ipc_mode == .none and this.has_pending_unref, .Release); } pub fn ref(this: *Subprocess) void { @@ -111,8 +110,10 @@ pub const Subprocess = struct { } } + /// This disables the keeping process alive flag on the poll and also in the stdin, stdout, and stderr pub fn unref(this: *Subprocess) void { var vm = this.globalThis.bunVM(); + if (this.poll_ref) |poll| poll.disableKeepingProcessAlive(vm); if (!this.hasCalledGetter(.stdin)) { this.stdin.unref(); @@ -425,7 +426,7 @@ pub const Subprocess = struct { } pub fn doSend(this: *Subprocess, global: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { - if (this.ipc == .none) { + if (this.ipc_mode == .none) { global.throw("Subprocess.send() can only be used if an IPC channel is open.", .{}); return .zero; } @@ -437,20 +438,16 @@ pub const Subprocess = struct { const value = callFrame.argument(0); - const success = IPC.serializeJSValueForSubprocess( - global, - value, - this.ipc_socket.fd(), - ); + const success = this.ipc.serializeAndSend(global, value); if (!success) return .zero; return JSC.JSValue.jsUndefined(); } pub fn disconnect(this: *Subprocess) void { - if (this.ipc == .none) return; - this.ipc_socket.close(0, null); - this.ipc = .none; + if (this.ipc_mode == .none) return; + this.ipc.socket.close(0, null); + this.ipc_mode = .none; } pub fn getPid( @@ -1538,15 +1535,15 @@ pub const Subprocess = struct { .stderr = Readable.init(stdio[bun.STDERR_FD], stderr_pipe[0], jsc_vm.allocator, default_max_buffer_size), .on_exit_callback = if (on_exit_callback != .zero) JSC.Strong.create(on_exit_callback, globalThis) else .{}, .is_sync = is_sync, - .ipc = ipc_mode, + .ipc_mode = ipc_mode, // will be assigned in the block below - .ipc_socket = socket, - .ipc_buffer = bun.ByteList{}, + .ipc = .{ .socket = socket }, .ipc_callback = if (ipc_callback != .zero) JSC.Strong.create(ipc_callback, globalThis) else undefined, }; if (ipc_mode != .none) { var ptr = socket.ext(*Subprocess); ptr.?.* = subprocess; + subprocess.ipc.writeVersionPacket(); } if (subprocess.stdin == .pipe) { @@ -1805,8 +1802,29 @@ pub const Subprocess = struct { } } - if (this.hasExited()) - this.unref(); + if (this.hasExited()) { + const Holder = struct { + process: *Subprocess, + task: JSC.AnyTask, + + pub fn unref(self: *@This()) void { + // this calls disableKeepingProcessAlive on pool_ref and stdin, stdout, stderr + self.process.unref(); + self.process.has_pending_unref = false; + self.process.updateHasPendingActivity(); + bun.default_allocator.destroy(self); + } + }; + + var holder = bun.default_allocator.create(Holder) catch @panic("OOM"); + this.has_pending_unref = true; + holder.* = .{ + .process = this, + .task = JSC.AnyTask.New(Holder, Holder.unref).init(holder), + }; + + this.globalThis.bunVM().enqueueTask(JSC.Task.init(&holder.task)); + } } const os = std.os; @@ -2036,7 +2054,7 @@ pub const Subprocess = struct { const result = cb.callWithThis( this.globalThis, this.this_jsvalue, - &[_]JSValue{data}, + &[_]JSValue{ data, this.this_jsvalue }, ); data.ensureStillAlive(); if (result.isAnyError()) { @@ -2049,7 +2067,7 @@ pub const Subprocess = struct { pub fn handleIPCClose(this: *Subprocess, _: IPC.Socket) void { // uSocket is already freed so calling .close() on the socket can segfault - this.ipc = .none; + this.ipc_mode = .none; this.updateHasPendingActivity(); } diff --git a/src/bun.js/api/bun/x509.zig b/src/bun.js/api/bun/x509.zig index 9c902b39c..a94d47c45 100644 --- a/src/bun.js/api/bun/x509.zig +++ b/src/bun.js/api/bun/x509.zig @@ -273,7 +273,8 @@ fn x509PrintGeneralName(out: *BoringSSL.BIO, name: *BoringSSL.GENERAL_NAME) bool // instead always print its numeric representation. var oline: [256]u8 = undefined; _ = BoringSSL.OBJ_obj2txt(&oline, @sizeOf(@TypeOf(oline)), name.d.rid, 1); - _ = BoringSSL.BIO_printf(out, "Registered ID:%s", &oline); + // Workaround for https://github.com/ziglang/zig/issues/16197 + _ = BoringSSL.BIO_printf(out, "Registered ID:%s", @as([*]const u8, &oline)); } else if (name.name_type == .GEN_X400) { _ = BoringSSL.BIO_printf(out, "X400Name:<unsupported>"); } else if (name.name_type == .GEN_EDIPARTY) { @@ -301,7 +302,8 @@ fn x509InfoAccessPrint(out: *BoringSSL.BIO, ext: *BoringSSL.X509_EXTENSION) bool } var tmp: [80]u8 = undefined; _ = BoringSSL.i2t_ASN1_OBJECT(&tmp, @sizeOf(@TypeOf(tmp)), desc.method); - _ = BoringSSL.BIO_printf(out, "%s - ", &tmp); + // Workaround for https://github.com/ziglang/zig/issues/16197 + _ = BoringSSL.BIO_printf(out, "%s - ", @as([*]const u8, &tmp)); if (!x509PrintGeneralName(out, desc.location)) { return false; diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index 097b66d35..a7a03e784 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -318,7 +318,11 @@ pub const FFI = struct { }; }; - var obj = JSC.JSValue.createEmptyObject(global, symbols.values().len); + var size = symbols.values().len; + if (size >= 63) { + size = 0; + } + var obj = JSC.JSValue.createEmptyObject(global, size); obj.protect(); defer obj.unprotect(); for (symbols.values()) |*function| { diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index 1bda47512..1f2366ad9 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -384,10 +384,10 @@ pub const HTMLRewriter = struct { result.* = Response{ .allocator = bun.default_allocator, + .init = .{ + .status_code = 200, + }, .body = .{ - .init = .{ - .status_code = 200, - }, .value = .{ .Locked = .{ .global = global, @@ -397,16 +397,16 @@ pub const HTMLRewriter = struct { }, }; - result.body.init.method = original.body.init.method; - result.body.init.status_code = original.body.init.status_code; + result.init.method = original.init.method; + result.init.status_code = original.init.status_code; + result.init.status_text = original.init.status_text.clone(); // https://github.com/oven-sh/bun/issues/3334 - if (original.body.init.headers) |headers| { - result.body.init.headers = headers.cloneThis(global); + if (original.init.headers) |headers| { + result.init.headers = headers.cloneThis(global); } result.url = original.url.clone(); - result.status_text = original.status_text.clone(); var value = original.getBodyValue(); sink.bodyValueBufferer = JSC.WebCore.BodyValueBufferer.init(sink, onFinishedBuffering, sink.global, bun.default_allocator); sink.bodyValueBufferer.?.run(value) catch |buffering_error| { @@ -606,10 +606,10 @@ pub const HTMLRewriter = struct { // result.* = Response{ // .allocator = bun.default_allocator, + // .init = .{ + // .status_code = 200, + // }, // .body = .{ - // .init = .{ - // .status_code = 200, - // }, // .value = .{ // .Locked = .{ // .global = global, @@ -619,9 +619,9 @@ pub const HTMLRewriter = struct { // }, // }; - // result.body.init.headers = original.body.init.headers; - // result.body.init.method = original.body.init.method; - // result.body.init.status_code = original.body.init.status_code; + // result.init.headers = original.init.headers; + // result.init.method = original.init.method; + // result.init.status_code = original.init.status_code; // result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable; // result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable; diff --git a/src/bun.js/api/server.classes.ts b/src/bun.js/api/server.classes.ts index 544f37ce6..81ec30988 100644 --- a/src/bun.js/api/server.classes.ts +++ b/src/bun.js/api/server.classes.ts @@ -24,6 +24,10 @@ function generate(name) { fn: "doStop", length: 1, }, + requestIP: { + fn: "doRequestIP", + length: 1, + }, port: { getter: "getPort", }, @@ -41,6 +45,10 @@ function generate(name) { getter: "getHostname", cache: true, }, + address: { + getter: "getAddress", + cache: true, + }, protocol: { getter: "getProtocol", }, diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 85d4dadb5..edf1d6d69 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -1125,6 +1125,92 @@ fn NewFlags(comptime debug_mode: bool) type { }; } +/// A generic wrapper for the HTTP(s) Server`RequestContext`s. +/// Only really exists because of `NewServer()` and `NewRequestContext()` generics. +pub const AnyRequestContext = struct { + pub const Pointer = bun.TaggedPointerUnion(.{ + HTTPServer.RequestContext, + HTTPSServer.RequestContext, + DebugHTTPServer.RequestContext, + DebugHTTPSServer.RequestContext, + }); + + tagged_pointer: Pointer, + + pub const Null = .{ .tagged_pointer = Pointer.Null }; + + pub fn init(request_ctx: anytype) AnyRequestContext { + return .{ .tagged_pointer = Pointer.init(request_ctx) }; + } + + pub fn getRemoteSocketInfo(self: AnyRequestContext) ?uws.SocketAddress { + if (self.tagged_pointer.isNull()) { + return null; + } + + switch (self.tagged_pointer.tag()) { + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => { + return self.tagged_pointer.as(HTTPServer.RequestContext).getRemoteSocketInfo(); + }, + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => { + return self.tagged_pointer.as(HTTPSServer.RequestContext).getRemoteSocketInfo(); + }, + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => { + return self.tagged_pointer.as(DebugHTTPServer.RequestContext).getRemoteSocketInfo(); + }, + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => { + return self.tagged_pointer.as(DebugHTTPSServer.RequestContext).getRemoteSocketInfo(); + }, + else => @panic("Unexpected AnyRequestContext tag"), + } + } + + /// Wont actually set anything if `self` is `.none` + pub fn setRequest(self: AnyRequestContext, req: *uws.Request) void { + if (self.tagged_pointer.isNull()) { + return; + } + + switch (self.tagged_pointer.tag()) { + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => { + self.tagged_pointer.as(HTTPServer.RequestContext).req = req; + }, + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => { + self.tagged_pointer.as(HTTPSServer.RequestContext).req = req; + }, + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => { + self.tagged_pointer.as(DebugHTTPServer.RequestContext).req = req; + }, + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => { + self.tagged_pointer.as(DebugHTTPSServer.RequestContext).req = req; + }, + else => @panic("Unexpected AnyRequestContext tag"), + } + } + + pub fn getRequest(self: AnyRequestContext) ?*uws.Request { + if (self.tagged_pointer.isNull()) { + return null; + } + + switch (self.tagged_pointer.tag()) { + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => { + return self.tagged_pointer.as(HTTPServer.RequestContext).req; + }, + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => { + return self.tagged_pointer.as(HTTPSServer.RequestContext).req; + }, + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => { + return self.tagged_pointer.as(DebugHTTPServer.RequestContext).req; + }, + @field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => { + return self.tagged_pointer.as(DebugHTTPSServer.RequestContext).req; + }, + else => @panic("Unexpected AnyRequestContext tag"), + } + } +}; + // 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 { @@ -1443,6 +1529,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } pub fn endStream(this: *RequestContext, closeConnection: bool) void { + ctxLog("endStream", .{}); if (this.resp) |resp| { if (this.flags.is_waiting_for_request_body) { this.flags.is_waiting_for_request_body = false; @@ -1537,8 +1624,17 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn onAbort(this: *RequestContext, resp: *App.Response) void { std.debug.assert(this.resp == resp); std.debug.assert(!this.flags.aborted); - //mark request as aborted + // mark request as aborted this.flags.aborted = true; + var any_js_calls = false; + var vm = this.server.vm; + defer { + // This is a task in the event loop. + // If we called into JavaScript, we must drain the microtask queue + if (any_js_calls) { + vm.drainMicrotasks(); + } + } // if signal is not aborted, abort the signal if (this.signal) |signal| { @@ -1547,6 +1643,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const reason = JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, this.server.globalThis); reason.ensureStillAlive(); _ = signal.signal(reason); + any_js_calls = true; } _ = signal.unref(); } @@ -1578,6 +1675,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } else if (body.value.Locked.readable != null) { body.value.Locked.readable.?.abort(this.server.globalThis); body.value.Locked.readable = null; + any_js_calls = true; } body.value.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis); } @@ -1588,6 +1686,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (response.body.value.Locked.readable) |*readable| { response.body.value.Locked.readable = null; readable.abort(this.server.globalThis); + any_js_calls = true; } } } @@ -1597,10 +1696,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.pending_promises_for_abort += 1; this.promise = null; promise.asAnyPromise().?.reject(this.server.globalThis, JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis)); - } - - if (this.pending_promises_for_abort > 0) { - this.server.vm.tick(); + any_js_calls = true; } } } @@ -1720,6 +1816,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this: *RequestContext, headers: *JSC.FetchHeaders, ) void { + ctxLog("writeHeaders", .{}); headers.fastRemove(.ContentLength); headers.fastRemove(.TransferEncoding); if (!ssl_enabled) headers.fastRemove(.StrictTransportSecurity); @@ -2091,6 +2188,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } fn doRenderStream(pair: *StreamPair) void { + ctxLog("doRenderStream", .{}); var this = pair.this; var stream = pair.stream; if (this.resp == null or this.flags.aborted) { @@ -2214,6 +2312,14 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }, } return; + } else { + // if is not a promise we treat it as Error + streamLog("returned an error", .{}); + if (!this.flags.aborted) resp.clearAborted(); + response_stream.detach(); + this.sink = null; + response_stream.sink.destroy(); + return this.handleReject(assignment_result); } } @@ -2223,6 +2329,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp defer stream.value.unprotect(); response_stream.sink.markDone(); this.finalizeForAbort(); + response_stream.sink.onFirstWrite = null; response_stream.sink.finalize(); return; @@ -2246,7 +2353,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.setAbortHandler(); streamLog("is in progress, but did not return a Promise. Finalizing request context", .{}); - this.finalize(); + response_stream.sink.onFirstWrite = null; + response_stream.sink.ctx = null; + response_stream.detach(); + stream.cancel(globalThis); + response_stream.sink.markDone(); + this.renderMissing(); } const streamLog = Output.scoped(.ReadableStream, false); @@ -2256,7 +2368,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } fn toAsyncWithoutAbortHandler(ctx: *RequestContext, req: *uws.Request, request_object: *Request) void { - request_object.uws_request = req; + request_object.request_context.setRequest(req); request_object.ensureURL() catch { request_object.url = bun.String.empty; @@ -2269,7 +2381,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // This object dies after the stack frame is popped // so we have to clear it in here too - request_object.uws_request = null; + request_object.request_context = JSC.API.AnyRequestContext.Null; } fn toAsync( @@ -2446,7 +2558,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } streamLog("onResolve({any})", .{wrote_anything}); - //aborted so call finalizeForAbort if (req.flags.aborted or req.resp == null) { req.finalizeForAbort(); @@ -2723,7 +2834,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } pub fn doRender(this: *RequestContext) void { - ctxLog("render", .{}); + ctxLog("doRender", .{}); if (this.flags.aborted) { this.finalizeForAbort(); @@ -2877,7 +2988,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp var needs_content_type = true; const content_type: MimeType = brk: { - if (response.body.init.headers) |headers_| { + if (response.init.headers) |headers_| { if (headers_.fastGet(.ContentType)) |content| { needs_content_type = false; break :brk MimeType.byName(content.slice()); @@ -2897,7 +3008,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }; var has_content_disposition = false; - if (response.body.init.headers) |headers_| { + if (response.init.headers) |headers_| { has_content_disposition = headers_.fastHas(.ContentDisposition); needs_content_range = needs_content_range and headers_.fastHas(.ContentRange); if (needs_content_range) { @@ -2907,7 +3018,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.writeStatus(status); this.writeHeaders(headers_); - response.body.init.headers = null; + response.init.headers = null; headers_.deref(); } else if (needs_content_range) { status = 206; @@ -3039,7 +3150,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (last) { var bytes = this.request_body_buf; - defer this.request_body_buf = .{}; + var old = body.value; const total = bytes.items.len + chunk.len; @@ -3070,6 +3181,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }; // } } + this.request_body_buf = .{}; if (old == .Locked) { var vm = this.server.vm; @@ -3142,6 +3254,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return onStartStreamingRequestBody(bun.cast(*RequestContext, this)); } + pub fn getRemoteSocketInfo(this: *RequestContext) ?uws.SocketAddress { + return (this.resp orelse return null).getRemoteSocketInfo(); + } + pub const Export = shim.exportFunctions(.{ .onResolve = onResolve, .onReject = onReject, @@ -4667,17 +4783,6 @@ pub const ServerWebSocket = struct { return JSValue.jsBoolean(this.websocket.isSubscribed(topic.slice())); } - // pub fn getTopics( - // this: *ServerWebSocket, - // globalThis: *JSC.JSGlobalObject, - // ) callconv(.C) JSValue { - // if (this.closed) { - // return JSValue.createStringArray(globalThis, bun.default_allocator, null, 0, false); - // } - - // this - // } - pub fn getRemoteAddress( this: *ServerWebSocket, globalThis: *JSC.JSGlobalObject, @@ -4704,7 +4809,7 @@ pub const ServerWebSocket = struct { pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { return struct { pub const ssl_enabled = ssl_enabled_; - const debug_mode = debug_mode_; + pub const debug_mode = debug_mode_; const ThisServer = @This(); pub const RequestContext = NewRequestContext(ssl_enabled, debug_mode, @This()); @@ -4742,6 +4847,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp pub const doPublish = JSC.wrapInstanceMethod(ThisServer, "publish", false); pub const doReload = onReload; pub const doFetch = onFetch; + pub const doRequestIP = JSC.wrapInstanceMethod(ThisServer, "requestIP", false); pub usingnamespace NamespaceType; @@ -4749,6 +4855,24 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp globalThis.throw("Server() is not a constructor", .{}); return null; } + + extern fn JSSocketAddress__create(global: *JSC.JSGlobalObject, ip: JSValue, port: i32, is_ipv6: bool) JSValue; + + pub fn requestIP(this: *ThisServer, request: *JSC.WebCore.Request) JSC.JSValue { + if (this.config.address == .unix) { + return JSValue.jsNull(); + } + return if (request.request_context.getRemoteSocketInfo()) |info| + JSSocketAddress__create( + this.globalThis, + bun.String.static(info.ip).toJSConst(this.globalThis), + info.port, + info.is_ipv6, + ) + else + JSValue.jsNull(); + } + pub fn publish(this: *ThisServer, globalThis: *JSC.JSGlobalObject, topic: ZigString, message_value: JSValue, compress_value: ?JSValue, exception: JSC.C.ExceptionRef) JSValue { if (this.config.websocket == null) return JSValue.jsNumber(0); @@ -5092,7 +5216,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp return JSPromise.rejectedPromiseValue(ctx, err); } - var request = ctx.bunVM().allocator.create(Request) catch unreachable; + var request = bun.default_allocator.create(Request) catch unreachable; request.* = existing_request; const response_value = this.config.onRequest.callWithThis( @@ -5173,6 +5297,37 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp return JSC.JSValue.jsNumber(@as(i32, @intCast(@as(u31, @truncate(this.activeSocketsCount()))))); } + pub fn getAddress(this: *ThisServer, globalThis: *JSGlobalObject) callconv(.C) JSC.JSValue { + switch (this.config.address) { + .unix => |unix| { + var value = bun.String.create(bun.sliceTo(@constCast(unix), 0)); + defer value.deref(); + return value.toJS(globalThis); + }, + .tcp => { + var port: u16 = this.config.address.tcp.port; + + if (this.listener) |listener| { + port = @intCast(listener.getLocalPort()); + + var buf: [64]u8 = [_]u8{0} ** 64; + var is_ipv6: bool = false; + + if (listener.socket().localAddressText(&buf, &is_ipv6)) |slice| { + var ip = bun.String.create(slice); + return JSSocketAddress__create( + this.globalThis, + ip.toJS(this.globalThis), + port, + is_ipv6, + ); + } + } + return JSValue.jsNull(); + }, + } + } + pub fn getHostname(this: *ThisServer, globalThis: *JSGlobalObject) callconv(.C) JSC.JSValue { if (this.cached_hostname.isEmpty()) { if (this.listener) |listener| { @@ -5254,6 +5409,10 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp var listener = this.listener orelse return; this.listener = null; this.unref(); + + if (!ssl_enabled_) + this.vm.removeListeningSocketForWatchMode(@intCast(listener.socket().fd())); + if (!abrupt) { listener.close(); } else if (!this.flags.terminated) { @@ -5388,24 +5547,18 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp if (error_instance == .zero) { switch (this.config.address) { .tcp => |tcp| { - error_instance = ZigString.init( - std.fmt.bufPrint(&output_buf, "Failed to start server. Is port {d} in use?", .{tcp.port}) catch "Failed to start server", - ).toErrorInstance( - this.globalThis, - ); + error_instance = (JSC.SystemError{ + .message = bun.String.init(std.fmt.bufPrint(&output_buf, "Failed to start server. Is port {d} in use?", .{tcp.port}) catch "Failed to start server"), + .code = bun.String.static("EADDRINUSE"), + .syscall = bun.String.static("listen"), + }).toErrorInstance(this.globalThis); }, .unix => |unix| { - error_instance = ZigString.init( - std.fmt.bufPrint( - &output_buf, - "Failed to listen on unix socket {}", - .{ - strings.QuotedFormatter{ .text = bun.sliceTo(unix, 0) }, - }, - ) catch "Failed to start server", - ).toErrorInstance( - this.globalThis, - ); + error_instance = (JSC.SystemError{ + .message = bun.String.init(std.fmt.bufPrint(&output_buf, "Failed to listen on unix socket {}", .{strings.QuotedFormatter{ .text = bun.sliceTo(unix, 0) }}) catch "Failed to start server"), + .code = bun.String.static("EADDRINUSE"), + .syscall = bun.String.static("listen"), + }).toErrorInstance(this.globalThis); }, } } @@ -5428,6 +5581,8 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp this.listener = socket; this.vm.event_loop_handle = uws.Loop.get(); + if (!ssl_enabled_) + this.vm.addListeningSocketForWatchMode(@intCast(socket.?.socket().fd())); } pub fn ref(this: *ThisServer) void { @@ -5512,21 +5667,19 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp req.setYield(false); var ctx = this.request_pool_allocator.tryGet() catch @panic("ran out of memory"); ctx.create(this, req, resp); + this.vm.jsc.reportExtraMemory(@sizeOf(RequestContext)); var request_object = this.allocator.create(JSC.WebCore.Request) catch unreachable; var body = JSC.WebCore.InitRequestBodyValue(.{ .Null = {} }) catch unreachable; ctx.request_body = body; - const js_signal = JSC.WebCore.AbortSignal.create(this.globalThis); - js_signal.ensureStillAlive(); - if (JSC.WebCore.AbortSignal.fromJS(js_signal)) |signal| { - ctx.signal = signal.ref().ref(); // +2 refs 1 for the request and 1 for the request context - } + var signal = JSC.WebCore.AbortSignal.new(this.globalThis); + ctx.signal = signal; request_object.* = .{ .method = ctx.method, - .uws_request = req, + .request_context = AnyRequestContext.init(ctx), .https = ssl_enabled, - .signal = ctx.signal, + .signal = signal.ref(), .body = body.ref(), }; @@ -5593,7 +5746,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp const response_value = this.config.onRequest.callWithThis(this.globalThis, this.thisObject, &args); defer { // uWS request will not live longer than this function - request_object.uws_request = null; + request_object.request_context = JSC.API.AnyRequestContext.Null; } var should_deinit_context = false; @@ -5608,7 +5761,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp ctx.defer_deinit_until_callback_completes = null; if (should_deinit_context) { - request_object.uws_request = null; + request_object.request_context = JSC.API.AnyRequestContext.Null; ctx.deinit(); return; } @@ -5637,18 +5790,15 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp var body = JSC.WebCore.InitRequestBodyValue(.{ .Null = {} }) catch unreachable; ctx.request_body = body; - const js_signal = JSC.WebCore.AbortSignal.create(this.globalThis); - js_signal.ensureStillAlive(); - if (JSC.WebCore.AbortSignal.fromJS(js_signal)) |signal| { - ctx.signal = signal.ref().ref(); // +2 refs 1 for the request and 1 for the request context - } + var signal = JSC.WebCore.AbortSignal.new(this.globalThis); + ctx.signal = signal; request_object.* = .{ .method = ctx.method, - .uws_request = req, + .request_context = AnyRequestContext.init(ctx), .upgrader = ctx, .https = ssl_enabled, - .signal = ctx.signal, + .signal = signal.ref(), .body = body.ref(), }; ctx.upgrade_context = upgrade_ctx; @@ -5663,7 +5813,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp const response_value = this.config.onRequest.callWithThis(this.globalThis, this.thisObject, &args); defer { // uWS request will not live longer than this function - request_object.uws_request = null; + request_object.request_context = JSC.API.AnyRequestContext.Null; } var should_deinit_context = false; @@ -5678,7 +5828,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp ctx.defer_deinit_until_callback_completes = null; if (should_deinit_context) { - request_object.uws_request = null; + request_object.request_context = JSC.API.AnyRequestContext.Null; ctx.deinit(); return; } |