aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/api')
-rw-r--r--src/bun.js/api/JSTranspiler.zig6
-rw-r--r--src/bun.js/api/bun.zig18
-rw-r--r--src/bun.js/api/bun/dns_resolver.zig335
-rw-r--r--src/bun.js/api/bun/socket.zig37
-rw-r--r--src/bun.js/api/bun/subprocess.zig62
-rw-r--r--src/bun.js/api/bun/x509.zig6
-rw-r--r--src/bun.js/api/ffi.zig6
-rw-r--r--src/bun.js/api/html_rewriter.zig28
-rw-r--r--src/bun.js/api/server.classes.ts8
-rw-r--r--src/bun.js/api/server.zig272
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;
}