aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-08-24 22:49:58 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-24 22:49:58 -0700
commitf269432d90826ad3e5b66c7685a6e826e0fb05e2 (patch)
tree78a05d1ba0a9bb7079ba111db7f3ddbac9b021ed
parent73b3fb7b0fa7cc786e147ccf1247cd9883ad8e59 (diff)
downloadbun-f269432d90826ad3e5b66c7685a6e826e0fb05e2.tar.gz
bun-f269432d90826ad3e5b66c7685a6e826e0fb05e2.tar.zst
bun-f269432d90826ad3e5b66c7685a6e826e0fb05e2.zip
Listen on a unix domain socket with Bun.serve() (#4311)
* Update response.zig * Comment this out for now * Support unix domain socket in Bun.serve() * Add test * add types * Update JSFetchHeaders.cpp * comment this test out * tls unix web socket serve options --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: dave caruso <me@paperdave.net>
-rw-r--r--packages/bun-types/bun.d.ts199
-rw-r--r--packages/bun-types/tests/serve.test-d.ts57
-rw-r--r--src/bun.js/api/server.zig199
-rw-r--r--src/bun.js/base.zig3
-rw-r--r--src/bun.js/bindings/webcore/JSFetchHeaders.cpp4
-rw-r--r--src/bun.js/webcore/response.zig2
-rw-r--r--src/deps/uws.zig40
-rw-r--r--test/js/bun/http/serve.test.ts52
-rw-r--r--tsconfig.json1
9 files changed, 467 insertions, 90 deletions
diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts
index 7ad0f912e..76db42715 100644
--- a/packages/bun-types/bun.d.ts
+++ b/packages/bun-types/bun.d.ts
@@ -73,8 +73,12 @@ declare module "bun" {
export type Serve<WebSocketDataType = undefined> =
| ServeOptions
| TLSServeOptions
+ | UnixServeOptions
+ | UnixTLSServeOptions
| WebSocketServeOptions<WebSocketDataType>
- | TLSWebSocketServeOptions<WebSocketDataType>;
+ | TLSWebSocketServeOptions<WebSocketDataType>
+ | UnixWebSocketServeOptions<WebSocketDataType>
+ | UnixTLSWebSocketServeOptions<WebSocketDataType>;
/**
* Start a fast HTTP server.
@@ -112,13 +116,7 @@ declare module "bun" {
* });
* ```
*/
- export function serve<T>(
- options:
- | ServeOptions
- | TLSServeOptions
- | WebSocketServeOptions<T>
- | TLSWebSocketServeOptions<T>,
- ): Server;
+ export function serve<T>(options: Serve<T>): Server;
/**
* Synchronously resolve a `moduleId` as though it were imported from `parent`
@@ -1784,32 +1782,7 @@ declare module "bun" {
interface GenericServeOptions {
/**
- * What port should the server listen on?
- * @default process.env.PORT || "3000"
- */
- port?: string | number;
-
- /**
- * What hostname should the server listen on?
- *
- * @default
- * ```js
- * "0.0.0.0" // listen on all interfaces
- * ```
- * @example
- * ```js
- * "127.0.0.1" // Only listen locally
- * ```
- * @example
- * ```js
- * "remix.run" // Only listen on remix.run
- * ````
*
- * note: hostname should not include a {@link port}
- */
- hostname?: string;
-
- /**
* What URI should be used to make {@link Request.url} absolute?
*
* By default, looks at {@link hostname}, {@link port}, and whether or not SSL is enabled to generate one
@@ -1828,7 +1801,6 @@ declare module "bun" {
*
* @example
* "http://localhost:3000"
- *
*/
// baseURI?: string;
@@ -1853,6 +1825,38 @@ declare module "bun" {
export type AnyFunction = (..._: any[]) => any;
export interface ServeOptions extends GenericServeOptions {
/**
+ * What port should the server listen on?
+ * @default process.env.PORT || "3000"
+ */
+ port?: string | number;
+
+ /**
+ * What hostname should the server listen on?
+ *
+ * @default
+ * ```js
+ * "0.0.0.0" // listen on all interfaces
+ * ```
+ * @example
+ * ```js
+ * "127.0.0.1" // Only listen locally
+ * ```
+ * @example
+ * ```js
+ * "remix.run" // Only listen on remix.run
+ * ````
+ *
+ * note: hostname should not include a {@link port}
+ */
+ hostname?: string;
+
+ /**
+ * If set, the HTTP server will listen on a unix socket instead of a port.
+ * (Cannot be used with hostname+port)
+ */
+ unix?: never;
+
+ /**
* Handle HTTP requests
*
* Respond to {@link Request} objects with a {@link Response} object.
@@ -1865,9 +1869,114 @@ declare module "bun" {
): Response | Promise<Response>;
}
+ export interface UnixServeOptions extends GenericServeOptions {
+ /**
+ * If set, the HTTP server will listen on a unix socket instead of a port.
+ * (Cannot be used with hostname+port)
+ */
+ unix: string;
+ /**
+ * Handle HTTP requests
+ *
+ * Respond to {@link Request} objects with a {@link Response} object.
+ */
+ fetch(
+ this: Server,
+ request: Request,
+ server: Server,
+ ): Response | Promise<Response>;
+ }
+
export interface WebSocketServeOptions<WebSocketDataType = undefined>
extends GenericServeOptions {
/**
+ * What port should the server listen on?
+ * @default process.env.PORT || "3000"
+ */
+ port?: string | number;
+
+ /**
+ * What hostname should the server listen on?
+ *
+ * @default
+ * ```js
+ * "0.0.0.0" // listen on all interfaces
+ * ```
+ * @example
+ * ```js
+ * "127.0.0.1" // Only listen locally
+ * ```
+ * @example
+ * ```js
+ * "remix.run" // Only listen on remix.run
+ * ````
+ *
+ * note: hostname should not include a {@link port}
+ */
+ hostname?: string;
+
+ /**
+ * Enable websockets with {@link Bun.serve}
+ *
+ * For simpler type safety, see {@link Bun.websocket}
+ *
+ * @example
+ * ```js
+ *import { serve } from "bun";
+ *serve({
+ * websocket: {
+ * open: (ws) => {
+ * console.log("Client connected");
+ * },
+ * message: (ws, message) => {
+ * console.log("Client sent message", message);
+ * },
+ * close: (ws) => {
+ * console.log("Client disconnected");
+ * },
+ * },
+ * fetch(req, server) {
+ * const url = new URL(req.url);
+ * if (url.pathname === "/chat") {
+ * const upgraded = server.upgrade(req);
+ * if (!upgraded) {
+ * return new Response("Upgrade failed", { status: 400 });
+ * }
+ * }
+ * return new Response("Hello World");
+ * },
+ *});
+ *```
+ * Upgrade a {@link Request} to a {@link ServerWebSocket} via {@link Server.upgrade}
+ *
+ * Pass `data` in @{link Server.upgrade} to attach data to the {@link ServerWebSocket.data} property
+ *
+ *
+ */
+ websocket: WebSocketHandler<WebSocketDataType>;
+
+ /**
+ * Handle HTTP requests or upgrade them to a {@link ServerWebSocket}
+ *
+ * Respond to {@link Request} objects with a {@link Response} object.
+ *
+ */
+ fetch(
+ this: Server,
+ request: Request,
+ server: Server,
+ ): Response | undefined | Promise<Response | undefined>;
+ }
+
+ export interface UnixWebSocketServeOptions<WebSocketDataType = undefined>
+ extends GenericServeOptions {
+ /**
+ * If set, the HTTP server will listen on a unix socket instead of a port.
+ * (Cannot be used with hostname+port)
+ */
+ unix: string;
+
+ /**
* Enable websockets with {@link Bun.serve}
*
* For simpler type safety, see {@link Bun.websocket}
@@ -1923,6 +2032,17 @@ declare module "bun" {
export interface TLSWebSocketServeOptions<WebSocketDataType = undefined>
extends WebSocketServeOptions<WebSocketDataType>,
TLSOptions {
+ unix?: never;
+ tls?: TLSOptions;
+ }
+ export interface UnixTLSWebSocketServeOptions<WebSocketDataType = undefined>
+ extends UnixWebSocketServeOptions<WebSocketDataType>,
+ TLSOptions {
+ /**
+ * If set, the HTTP server will listen on a unix socket instead of a port.
+ * (Cannot be used with hostname+port)
+ */
+ unix: string;
tls?: TLSOptions;
}
export interface Errorlike extends Error {
@@ -2039,6 +2159,16 @@ declare module "bun" {
tls?: TLSOptions;
}
+ export interface UnixTLSServeOptions extends UnixServeOptions, TLSOptions {
+ /**
+ * The keys are [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) hostnames.
+ * The values are SSL options objects.
+ */
+ serverNames?: Record<string, TLSOptions>;
+
+ tls?: TLSOptions;
+ }
+
/**
* HTTP & HTTPS Server
*
@@ -2049,7 +2179,6 @@ declare module "bun" {
* avoid starting and stopping the server often (unless it's a new instance of bun).
*
* Powered by a fork of [uWebSockets](https://github.com/uNetworking/uWebSockets). Thank you @alexhultman.
- *
*/
export interface Server {
/**
diff --git a/packages/bun-types/tests/serve.test-d.ts b/packages/bun-types/tests/serve.test-d.ts
index 976466022..86d78fd8a 100644
--- a/packages/bun-types/tests/serve.test-d.ts
+++ b/packages/bun-types/tests/serve.test-d.ts
@@ -103,3 +103,60 @@ Bun.serve({
},
websocket: { message() {} },
});
+
+Bun.serve({
+ unix: "/tmp/bun.sock",
+ fetch() {
+ return new Response();
+ },
+});
+
+Bun.serve({
+ unix: "/tmp/bun.sock",
+ fetch(req, server) {
+ server.upgrade(req);
+ if (Math.random() > 0.5) return undefined;
+ return new Response();
+ },
+ websocket: { message() {} },
+});
+
+Bun.serve({
+ unix: "/tmp/bun.sock",
+ fetch() {
+ return new Response();
+ },
+ tls: {},
+});
+
+Bun.serve({
+ unix: "/tmp/bun.sock",
+ fetch(req, server) {
+ server.upgrade(req);
+ if (Math.random() > 0.5) return undefined;
+ return new Response();
+ },
+ websocket: { message() {} },
+ tls: {},
+});
+
+// Bun.serve({
+// unix: "/tmp/bun.sock",
+// // @ts-expect-error
+// port: 1234,
+// fetch() {
+// return new Response();
+// },
+// });
+
+// Bun.serve({
+// unix: "/tmp/bun.sock",
+// // @ts-expect-error
+// port: 1234,
+// fetch(req, server) {
+// server.upgrade(req);
+// if (Math.random() > 0.5) return undefined;
+// return new Response();
+// },
+// websocket: { message() {} },
+// });
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig
index ca803cef2..81a50a5a7 100644
--- a/src/bun.js/api/server.zig
+++ b/src/bun.js/api/server.zig
@@ -120,8 +120,28 @@ const BlobFileContentResult = struct {
};
pub const ServerConfig = struct {
- port: u16 = 0,
- hostname: ?[*:0]const u8 = null,
+ address: union(enum) {
+ tcp: struct {
+ port: u16 = 0,
+ hostname: ?[*:0]const u8 = null,
+ },
+ unix: [*:0]const u8,
+
+ pub fn deinit(this: *@This(), allocator: std.mem.Allocator) void {
+ switch (this.*) {
+ .tcp => |tcp| {
+ if (tcp.hostname) |host| {
+ allocator.free(bun.sliceTo(host, 0));
+ }
+ },
+ .unix => |addr| {
+ allocator.free(bun.sliceTo(addr, 0));
+ },
+ }
+ }
+ } = .{
+ .tcp = .{},
+ },
// TODO: use webkit URL parser instead of bun's
base_url: URL = URL{},
@@ -657,8 +677,12 @@ pub const ServerConfig = struct {
var env = arguments.vm.bundler.env;
var args = ServerConfig{
- .port = 3000,
- .hostname = null,
+ .address = .{
+ .tcp = .{
+ .port = 3000,
+ .hostname = null,
+ },
+ },
.development = true,
};
var has_hostname = false;
@@ -670,19 +694,24 @@ pub const ServerConfig = struct {
args.development = false;
}
- const PORT_ENV = .{ "BUN_PORT", "PORT", "NODE_PORT" };
+ args.address.tcp.port = brk: {
+ const PORT_ENV = .{ "BUN_PORT", "PORT", "NODE_PORT" };
- inline for (PORT_ENV) |PORT| {
- if (env.get(PORT)) |port| {
- if (std.fmt.parseInt(u16, port, 10)) |_port| {
- args.port = _port;
- } else |_| {}
+ inline for (PORT_ENV) |PORT| {
+ if (env.get(PORT)) |port| {
+ if (std.fmt.parseInt(u16, port, 10)) |_port| {
+ break :brk _port;
+ } else |_| {}
+ }
}
- }
- if (arguments.vm.bundler.options.transform_options.port) |port| {
- args.port = port;
- }
+ if (arguments.vm.bundler.options.transform_options.port) |port| {
+ break :brk port;
+ }
+
+ break :brk args.address.tcp.port;
+ };
+ var port = args.address.tcp.port;
if (arguments.vm.bundler.options.transform_options.origin) |origin| {
args.base_uri = origin;
@@ -714,13 +743,14 @@ pub const ServerConfig = struct {
}
if (arg.getTruthy(global, "port")) |port_| {
- args.port = @as(
+ args.address.tcp.port = @as(
u16,
@intCast(@min(
@max(0, port_.coerce(i32, global)),
std.math.maxInt(u16),
)),
);
+ port = args.address.tcp.port;
}
if (arg.getTruthy(global, "baseURI")) |baseURI| {
@@ -737,12 +767,33 @@ pub const ServerConfig = struct {
global,
bun.default_allocator,
);
+ defer host_str.deinit();
+
if (host_str.len > 0) {
- args.hostname = bun.default_allocator.dupeZ(u8, host_str.slice()) catch unreachable;
+ args.address.tcp.hostname = bun.default_allocator.dupeZ(u8, host_str.slice()) catch unreachable;
has_hostname = true;
}
}
+ if (arg.getTruthy(global, "unix")) |unix| {
+ const unix_str = unix.toSlice(
+ global,
+ bun.default_allocator,
+ );
+ defer unix_str.deinit();
+ if (unix_str.len > 0) {
+ if (has_hostname) {
+ JSC.throwInvalidArguments("Cannot specify both hostname and unix", .{}, global, exception);
+ if (args.ssl_config) |*conf| {
+ conf.deinit();
+ }
+ return args;
+ }
+
+ args.address = .{ .unix = bun.default_allocator.dupeZ(u8, unix_str.slice()) catch unreachable };
+ }
+ }
+
if (arg.get(global, "development")) |dev| {
args.development = dev.coerce(bool, global);
args.reuse_port = !args.development;
@@ -846,7 +897,7 @@ pub const ServerConfig = struct {
const hostname = args.base_url.hostname;
const needsBrackets: bool = strings.isIPV6Address(hostname) and hostname[0] != '[';
if (needsBrackets) {
- args.base_uri = (if ((args.port == 80 and args.ssl_config == null) or (args.port == 443 and args.ssl_config != null))
+ args.base_uri = (if ((port == 80 and args.ssl_config == null) or (port == 443 and args.ssl_config != null))
std.fmt.allocPrint(bun.default_allocator, "{s}://[{s}]/{s}", .{
protocol,
hostname,
@@ -856,11 +907,11 @@ pub const ServerConfig = struct {
std.fmt.allocPrint(bun.default_allocator, "{s}://[{s}]:{d}/{s}", .{
protocol,
hostname,
- args.port,
+ port,
strings.trimLeadingChar(args.base_url.pathname, '/'),
})) catch unreachable;
} else {
- args.base_uri = (if ((args.port == 80 and args.ssl_config == null) or (args.port == 443 and args.ssl_config != null))
+ args.base_uri = (if ((port == 80 and args.ssl_config == null) or (port == 443 and args.ssl_config != null))
std.fmt.allocPrint(bun.default_allocator, "{s}://{s}/{s}", .{
protocol,
hostname,
@@ -870,7 +921,7 @@ pub const ServerConfig = struct {
std.fmt.allocPrint(bun.default_allocator, "{s}://{s}:{d}/{s}", .{
protocol,
hostname,
- args.port,
+ port,
strings.trimLeadingChar(args.base_url.pathname, '/'),
})) catch unreachable;
}
@@ -879,27 +930,27 @@ pub const ServerConfig = struct {
}
} else {
const hostname: string =
- if (has_hostname and std.mem.span(args.hostname.?).len > 0) std.mem.span(args.hostname.?) else "0.0.0.0";
+ if (has_hostname) std.mem.span(args.address.tcp.hostname.?) else "0.0.0.0";
const needsBrackets: bool = strings.isIPV6Address(hostname) and hostname[0] != '[';
const protocol: string = if (args.ssl_config != null) "https" else "http";
if (needsBrackets) {
- args.base_uri = (if ((args.port == 80 and args.ssl_config == null) or (args.port == 443 and args.ssl_config != null))
+ args.base_uri = (if ((port == 80 and args.ssl_config == null) or (port == 443 and args.ssl_config != null))
std.fmt.allocPrint(bun.default_allocator, "{s}://[{s}]/", .{
protocol,
hostname,
})
else
- std.fmt.allocPrint(bun.default_allocator, "{s}://[{s}]:{d}/", .{ protocol, hostname, args.port })) catch unreachable;
+ std.fmt.allocPrint(bun.default_allocator, "{s}://[{s}]:{d}/", .{ protocol, hostname, port })) catch unreachable;
} else {
- args.base_uri = (if ((args.port == 80 and args.ssl_config == null) or (args.port == 443 and args.ssl_config != null))
+ args.base_uri = (if ((port == 80 and args.ssl_config == null) or (port == 443 and args.ssl_config != null))
std.fmt.allocPrint(bun.default_allocator, "{s}://{s}/", .{
protocol,
hostname,
})
else
- std.fmt.allocPrint(bun.default_allocator, "{s}://{s}:{d}/", .{ protocol, hostname, args.port })) catch unreachable;
+ std.fmt.allocPrint(bun.default_allocator, "{s}://{s}:{d}/", .{ protocol, hostname, port })) catch unreachable;
}
if (!strings.isAllASCII(hostname)) {
@@ -5007,7 +5058,11 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
this: *ThisServer,
_: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
- var listener = this.listener orelse return JSC.JSValue.jsNumber(this.config.port);
+ if (this.config.address != .tcp) {
+ return JSValue.undefined;
+ }
+
+ var listener = this.listener orelse return JSC.JSValue.jsNumber(this.config.address.tcp.port);
return JSC.JSValue.jsNumber(listener.getLocalPort());
}
@@ -5036,8 +5091,18 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
}
}
- if (this.cached_hostname.isEmpty())
- this.cached_hostname = bun.String.create(bun.span(this.config.hostname orelse "localhost"));
+ if (this.cached_hostname.isEmpty()) {
+ switch (this.config.address) {
+ .tcp => |tcp| {
+ if (tcp.hostname) |hostname| {
+ this.cached_hostname = bun.String.create(bun.sliceTo(hostname, 0));
+ } else {
+ this.cached_hostname = bun.String.createAtom("localhost");
+ }
+ },
+ else => {},
+ }
+ }
}
return this.cached_hostname.toJS(globalThis);
@@ -5130,9 +5195,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
this.cached_hostname.deref();
this.cached_protocol.deref();
- if (this.config.hostname) |host| {
- bun.default_allocator.free(host[0 .. std.mem.len(host) + 1]);
- }
+ this.config.address.deinit(bun.default_allocator);
if (this.config.base_url.href.len > 0) {
bun.default_allocator.free(this.config.base_url.href);
@@ -5224,7 +5287,28 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
}
if (error_instance == .zero) {
- error_instance = ZigString.init(std.fmt.bufPrint(&output_buf, "Failed to start server. Is port {d} in use?", .{this.config.port}) catch "Failed to start server").toErrorInstance(this.globalThis);
+ 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,
+ );
+ },
+ .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,
+ );
+ },
+ }
}
// store the exception in here
@@ -5545,19 +5629,6 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
this.app.get("/src:/*", *ThisServer, this, onSrcRequest);
}
- var host: ?[*:0]const u8 = null;
- var host_buff: [1024:0]u8 = undefined;
-
- if (this.config.hostname) |existing| {
- const hostname = bun.span(existing);
-
- if (hostname.len > 2 and hostname[0] == '[') {
- // remove "[" and "]" from hostname
- host = std.fmt.bufPrintZ(&host_buff, "{s}", .{hostname[1 .. hostname.len - 1]}) catch unreachable;
- } else {
- host = this.config.hostname;
- }
- }
this.ref();
@@ -5568,11 +5639,39 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
this.vm.eventLoop().performGC();
}
- this.app.listenWithConfig(*ThisServer, this, onListen, .{
- .port = this.config.port,
- .host = host,
- .options = if (this.config.reuse_port) 0 else 1,
- });
+ switch (this.config.address) {
+ .tcp => |tcp| {
+ var host: ?[*:0]const u8 = null;
+ var host_buff: [1024:0]u8 = undefined;
+
+ if (tcp.hostname) |existing| {
+ const hostname = bun.span(existing);
+
+ if (hostname.len > 2 and hostname[0] == '[') {
+ // remove "[" and "]" from hostname
+ host = std.fmt.bufPrintZ(&host_buff, "{s}", .{hostname[1 .. hostname.len - 1]}) catch unreachable;
+ } else {
+ host = tcp.hostname;
+ }
+ }
+
+ this.app.listenWithConfig(*ThisServer, this, onListen, .{
+ .port = tcp.port,
+ .host = host,
+ .options = if (this.config.reuse_port) 0 else 1,
+ });
+ },
+
+ .unix => |unix| {
+ this.app.listenOnUnixSocket(
+ *ThisServer,
+ this,
+ onListen,
+ unix,
+ if (this.config.reuse_port) 0 else 1,
+ );
+ },
+ }
}
};
}
diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig
index 0ed780a5d..66cc50f5d 100644
--- a/src/bun.js/base.zig
+++ b/src/bun.js/base.zig
@@ -2519,9 +2519,10 @@ pub const MemoryReportingAllocator = struct {
this.child_allocator.rawFree(buf, buf_align, ret_addr);
const prev = this.memory_cost.fetchSub(buf.len, .Monotonic);
+ _ = prev;
if (comptime Environment.allow_assert) {
// check for overflow, racily
- std.debug.assert(prev > this.memory_cost.load(.Monotonic));
+ // std.debug.assert(prev > this.memory_cost.load(.Monotonic));
log("free({d}) = {d}", .{ buf.len, this.memory_cost.loadUnchecked() });
}
}
diff --git a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
index eec128373..2ae8dd547 100644
--- a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
+++ b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
@@ -148,6 +148,10 @@ template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSFetchHeadersDOMConstructor:
RETURN_IF_EXCEPTION(throwScope, {});
setSubclassStructureIfNeeded<FetchHeaders>(lexicalGlobalObject, callFrame, asObject(jsValue));
RETURN_IF_EXCEPTION(throwScope, {});
+
+ if (argument0.value())
+ jsCast<JSFetchHeaders*>(jsValue)->computeMemoryCost();
+
return JSValue::encode(jsValue);
}
JSC_ANNOTATE_HOST_FUNCTION(JSFetchHeadersDOMConstructorConstruct, JSFetchHeadersDOMConstructor::construct);
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index 0838a4580..df92dff15 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -735,7 +735,7 @@ pub const Fetch = struct {
var reporter = this.memory_reporter;
if (this.http) |http| reporter.allocator().destroy(http);
reporter.allocator().destroy(this);
- reporter.assert();
+ // reporter.assert();
bun.default_allocator.destroy(reporter);
}
diff --git a/src/deps/uws.zig b/src/deps/uws.zig
index 2610b0720..3f1fb276c 100644
--- a/src/deps/uws.zig
+++ b/src/deps/uws.zig
@@ -1635,6 +1635,37 @@ pub fn NewApp(comptime ssl: bool) type {
};
return uws_app_listen_with_config(ssl_flag, @as(*uws_app_t, @ptrCast(app)), config.host, @as(u16, @intCast(config.port)), config.options, Wrapper.handle, user_data);
}
+
+ pub fn listenOnUnixSocket(
+ app: *ThisApp,
+ comptime UserData: type,
+ user_data: UserData,
+ comptime handler: fn (UserData, ?*ThisApp.ListenSocket) void,
+ domain: [*:0]const u8,
+ flags: i32,
+ ) void {
+ const Wrapper = struct {
+ pub fn handle(socket: ?*uws.ListenSocket, _: [*:0]const u8, _: i32, data: *anyopaque) callconv(.C) void {
+ if (comptime UserData == void) {
+ @call(.always_inline, handler, .{ {}, @as(?*ThisApp.ListenSocket, @ptrCast(socket)) });
+ } else {
+ @call(.always_inline, handler, .{
+ @as(UserData, @ptrCast(@alignCast(data))),
+ @as(?*ThisApp.ListenSocket, @ptrCast(socket)),
+ });
+ }
+ }
+ };
+ return uws_app_listen_domain_with_options(
+ ssl_flag,
+ @as(*uws_app_t, @ptrCast(app)),
+ domain,
+ flags,
+ Wrapper.handle,
+ user_data,
+ );
+ }
+
pub fn constructorFailed(app: *ThisApp) bool {
return uws_constructor_failed(ssl_flag, app);
}
@@ -2206,3 +2237,12 @@ pub const State = enum(i32) {
};
extern fn us_socket_sendfile_needs_more(socket: *Socket) void;
+
+extern fn uws_app_listen_domain_with_options(
+ ssl_flag: c_int,
+ app: *uws_app_t,
+ domain: [*:0]const u8,
+ i32,
+ *const (fn (*ListenSocket, domain: [*:0]const u8, i32, *anyopaque) callconv(.C) void),
+ ?*anyopaque,
+) void;
diff --git a/test/js/bun/http/serve.test.ts b/test/js/bun/http/serve.test.ts
index 3066b4b37..b6ef75b54 100644
--- a/test/js/bun/http/serve.test.ts
+++ b/test/js/bun/http/serve.test.ts
@@ -1,11 +1,12 @@
import { file, gc, Serve, serve, Server } from "bun";
import { afterEach, describe, it, expect, afterAll } from "bun:test";
import { readFileSync, writeFileSync } from "fs";
-import { resolve } from "path";
+import { join, resolve } from "path";
import { bunExe, bunEnv } from "harness";
import { renderToReadableStream } from "react-dom/server";
import app_jsx from "./app.jsx";
import { spawn } from "child_process";
+import { tmpdir } from "os";
type Handler = (req: Request) => Response;
afterEach(() => gc(true));
@@ -225,7 +226,7 @@ describe("streaming", () => {
return new Response(
new ReadableStream({
pull(controller) {
- throw new Error("Not a real error");
+ throw new Error("TestPassed");
},
cancel(reason) {},
}),
@@ -1117,3 +1118,50 @@ it("does propagate type for Blob", async () => {
server.stop(true);
});
+
+it("unix socket connection in Bun.serve", async () => {
+ const unix = join(tmpdir(), "bun." + Date.now() + ((Math.random() * 32) | 0).toString(16) + ".sock");
+ const server = Bun.serve({
+ port: 0,
+ unix,
+
+ async fetch(req) {
+ expect(req.headers.get("Content-Type")).toBeNull();
+ return new Response(new Blob(["hey"], { type: "text/plain;charset=utf-8" }));
+ },
+ });
+
+ const requestText = `GET / HTTP/1.1\r\nHost: localhost\r\n\r\n`;
+ const received: Buffer[] = [];
+ const { resolve, promise } = Promise.withResolvers();
+ const connection = await Bun.connect({
+ unix,
+ socket: {
+ data(socket, data) {
+ received.push(data);
+ resolve();
+ },
+ },
+ });
+ connection.write(requestText);
+ connection.flush();
+ await promise;
+ expect(Buffer.concat(received).toString()).toEndWith("\r\n\r\nhey");
+ connection.end();
+ server.stop(true);
+});
+
+it("unix socket connection throws an error on a bad domain without crashing", async () => {
+ const unix = "/i/don/tevent/exist/because/the/directory/is/invalid/yes.sock";
+ expect(() => {
+ const server = Bun.serve({
+ port: 0,
+ unix,
+
+ async fetch(req) {
+ expect(req.headers.get("Content-Type")).toBeNull();
+ return new Response(new Blob(["hey"], { type: "text/plain;charset=utf-8" }));
+ },
+ });
+ }).toThrow();
+});
diff --git a/tsconfig.json b/tsconfig.json
index fdef57bfe..e8b7322b4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,7 +2,6 @@
"extends": "./tsconfig.base.json",
"compilerOptions": {
"experimentalDecorators": true,
- "noEmit": true,
// "skipLibCheck": true,
"allowJs": true
},