Split server.zig into more files (#20139)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
Jarred Sumner
2025-06-03 01:38:26 -07:00
committed by GitHub
parent 293215778f
commit d9cf836b67
9 changed files with 4903 additions and 4833 deletions

View File

@@ -55,11 +55,17 @@ src/bun.js/api/html_rewriter.zig
src/bun.js/api/JSBundler.zig
src/bun.js/api/JSTranspiler.zig
src/bun.js/api/server.zig
src/bun.js/api/server/AnyRequestContext.zig
src/bun.js/api/server/HTMLBundle.zig
src/bun.js/api/server/HTTPStatusText.zig
src/bun.js/api/server/InspectorBunFrontendDevServerAgent.zig
src/bun.js/api/server/NodeHTTPResponse.zig
src/bun.js/api/server/RequestContext.zig
src/bun.js/api/server/ServerConfig.zig
src/bun.js/api/server/ServerWebSocket.zig
src/bun.js/api/server/SSLConfig.zig
src/bun.js/api/server/StaticRoute.zig
src/bun.js/api/server/WebSocketServerContext.zig
src/bun.js/api/streams.classes.zig
src/bun.js/api/Timer.zig
src/bun.js/api/TOMLObject.zig

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
//! A generic wrapper for the HTTP(s) Server`RequestContext`s.
//! Only really exists because of `NewServer()` and `NewRequestContext()` generics.
const AnyRequestContext = @This();
pub const Pointer = bun.TaggedPointerUnion(.{
HTTPServer.RequestContext,
HTTPSServer.RequestContext,
DebugHTTPServer.RequestContext,
DebugHTTPSServer.RequestContext,
});
tagged_pointer: Pointer,
pub const Null: @This() = .{ .tagged_pointer = Pointer.Null };
pub fn init(request_ctx: anytype) AnyRequestContext {
return .{ .tagged_pointer = Pointer.init(request_ctx) };
}
pub fn memoryCost(self: AnyRequestContext) usize {
if (self.tagged_pointer.isNull()) {
return 0;
}
switch (self.tagged_pointer.tag()) {
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPServer.RequestContext).memoryCost();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPSServer.RequestContext).memoryCost();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPServer.RequestContext).memoryCost();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPSServer.RequestContext).memoryCost();
},
else => @panic("Unexpected AnyRequestContext tag"),
}
}
pub fn get(self: AnyRequestContext, comptime T: type) ?*T {
return self.tagged_pointer.get(T);
}
pub fn setTimeout(self: AnyRequestContext, seconds: c_uint) bool {
if (self.tagged_pointer.isNull()) {
return false;
}
switch (self.tagged_pointer.tag()) {
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPServer.RequestContext).setTimeout(seconds);
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPSServer.RequestContext).setTimeout(seconds);
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPServer.RequestContext).setTimeout(seconds);
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPSServer.RequestContext).setTimeout(seconds);
},
else => @panic("Unexpected AnyRequestContext tag"),
}
return false;
}
pub fn setCookies(self: AnyRequestContext, cookie_map: ?*JSC.WebCore.CookieMap) void {
if (self.tagged_pointer.isNull()) {
return;
}
switch (self.tagged_pointer.tag()) {
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPServer.RequestContext).setCookies(cookie_map);
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPSServer.RequestContext).setCookies(cookie_map);
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPServer.RequestContext).setCookies(cookie_map);
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPSServer.RequestContext).setCookies(cookie_map);
},
else => @panic("Unexpected AnyRequestContext tag"),
}
}
pub fn enableTimeoutEvents(self: AnyRequestContext) void {
if (self.tagged_pointer.isNull()) {
return;
}
switch (self.tagged_pointer.tag()) {
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPServer.RequestContext).setTimeoutHandler();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPSServer.RequestContext).setTimeoutHandler();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPServer.RequestContext).setTimeoutHandler();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPSServer.RequestContext).setTimeoutHandler();
},
else => @panic("Unexpected AnyRequestContext tag"),
}
}
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"),
}
}
pub fn detachRequest(self: AnyRequestContext) 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 = null;
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => {
self.tagged_pointer.as(HTTPSServer.RequestContext).req = null;
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => {
self.tagged_pointer.as(DebugHTTPServer.RequestContext).req = null;
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
self.tagged_pointer.as(DebugHTTPSServer.RequestContext).req = null;
},
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"),
}
}
pub fn deref(self: AnyRequestContext) 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).deref();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => {
self.tagged_pointer.as(HTTPSServer.RequestContext).deref();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => {
self.tagged_pointer.as(DebugHTTPServer.RequestContext).deref();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
self.tagged_pointer.as(DebugHTTPSServer.RequestContext).deref();
},
else => @panic("Unexpected AnyRequestContext tag"),
}
}
const bun = @import("bun");
const JSC = bun.JSC;
const uws = bun.uws;
const HTTPServer = @import("../server.zig").HTTPServer;
const HTTPSServer = @import("../server.zig").HTTPSServer;
const DebugHTTPServer = @import("../server.zig").DebugHTTPServer;
const DebugHTTPSServer = @import("../server.zig").DebugHTTPSServer;

View File

@@ -0,0 +1,68 @@
pub fn get(code: u16) ?[]const u8 {
return switch (code) {
100 => "100 Continue",
101 => "101 Switching protocols",
102 => "102 Processing",
103 => "103 Early Hints",
200 => "200 OK",
201 => "201 Created",
202 => "202 Accepted",
203 => "203 Non-Authoritative Information",
204 => "204 No Content",
205 => "205 Reset Content",
206 => "206 Partial Content",
207 => "207 Multi-Status",
208 => "208 Already Reported",
226 => "226 IM Used",
300 => "300 Multiple Choices",
301 => "301 Moved Permanently",
302 => "302 Found",
303 => "303 See Other",
304 => "304 Not Modified",
305 => "305 Use Proxy",
306 => "306 Switch Proxy",
307 => "307 Temporary Redirect",
308 => "308 Permanent Redirect",
400 => "400 Bad Request",
401 => "401 Unauthorized",
402 => "402 Payment Required",
403 => "403 Forbidden",
404 => "404 Not Found",
405 => "405 Method Not Allowed",
406 => "406 Not Acceptable",
407 => "407 Proxy Authentication Required",
408 => "408 Request Timeout",
409 => "409 Conflict",
410 => "410 Gone",
411 => "411 Length Required",
412 => "412 Precondition Failed",
413 => "413 Payload Too Large",
414 => "414 URI Too Long",
415 => "415 Unsupported Media Type",
416 => "416 Range Not Satisfiable",
417 => "417 Expectation Failed",
418 => "418 I'm a Teapot",
421 => "421 Misdirected Request",
422 => "422 Unprocessable Entity",
423 => "423 Locked",
424 => "424 Failed Dependency",
425 => "425 Too Early",
426 => "426 Upgrade Required",
428 => "428 Precondition Required",
429 => "429 Too Many Requests",
431 => "431 Request Header Fields Too Large",
451 => "451 Unavailable For Legal Reasons",
500 => "500 Internal Server Error",
501 => "501 Not Implemented",
502 => "502 Bad Gateway",
503 => "503 Service Unavailable",
504 => "504 Gateway Timeout",
505 => "505 HTTP Version Not Supported",
506 => "506 Variant Also Negotiates",
507 => "507 Insufficient Storage",
508 => "508 Loop Detected",
510 => "510 Not Extended",
511 => "511 Network Authentication Required",
else => null,
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,620 @@
const SSLConfig = @This();
requires_custom_request_ctx: bool = false,
server_name: [*c]const u8 = null,
key_file_name: [*c]const u8 = null,
cert_file_name: [*c]const u8 = null,
ca_file_name: [*c]const u8 = null,
dh_params_file_name: [*c]const u8 = null,
passphrase: [*c]const u8 = null,
low_memory_mode: bool = false,
key: ?[][*c]const u8 = null,
key_count: u32 = 0,
cert: ?[][*c]const u8 = null,
cert_count: u32 = 0,
ca: ?[][*c]const u8 = null,
ca_count: u32 = 0,
secure_options: u32 = 0,
request_cert: i32 = 0,
reject_unauthorized: i32 = 0,
ssl_ciphers: ?[*:0]const u8 = null,
protos: ?[*:0]const u8 = null,
protos_len: usize = 0,
client_renegotiation_limit: u32 = 0,
client_renegotiation_window: u32 = 0,
const BlobFileContentResult = struct {
data: [:0]const u8,
fn init(comptime fieldname: []const u8, js_obj: JSC.JSValue, global: *JSC.JSGlobalObject) bun.JSError!?BlobFileContentResult {
{
const body = try JSC.WebCore.Body.Value.fromJS(global, js_obj);
if (body == .Blob and body.Blob.store != null and body.Blob.store.?.data == .file) {
var fs: JSC.Node.fs.NodeFS = .{};
const read = fs.readFileWithOptions(.{ .path = body.Blob.store.?.data.file.pathlike }, .sync, .null_terminated);
switch (read) {
.err => {
return global.throwValue(read.err.toJSC(global));
},
else => {
const str = read.result.null_terminated;
if (str.len > 0) {
return .{ .data = str };
}
return global.throwInvalidArguments(std.fmt.comptimePrint("Invalid {s} file", .{fieldname}), .{});
},
}
}
}
return null;
}
};
pub fn asUSockets(this: SSLConfig) uws.SocketContext.BunSocketContextOptions {
var ctx_opts: uws.SocketContext.BunSocketContextOptions = .{};
if (this.key_file_name != null)
ctx_opts.key_file_name = this.key_file_name;
if (this.cert_file_name != null)
ctx_opts.cert_file_name = this.cert_file_name;
if (this.ca_file_name != null)
ctx_opts.ca_file_name = this.ca_file_name;
if (this.dh_params_file_name != null)
ctx_opts.dh_params_file_name = this.dh_params_file_name;
if (this.passphrase != null)
ctx_opts.passphrase = this.passphrase;
ctx_opts.ssl_prefer_low_memory_usage = @intFromBool(this.low_memory_mode);
if (this.key) |key| {
ctx_opts.key = key.ptr;
ctx_opts.key_count = this.key_count;
}
if (this.cert) |cert| {
ctx_opts.cert = cert.ptr;
ctx_opts.cert_count = this.cert_count;
}
if (this.ca) |ca| {
ctx_opts.ca = ca.ptr;
ctx_opts.ca_count = this.ca_count;
}
if (this.ssl_ciphers != null) {
ctx_opts.ssl_ciphers = this.ssl_ciphers;
}
ctx_opts.request_cert = this.request_cert;
ctx_opts.reject_unauthorized = this.reject_unauthorized;
return ctx_opts;
}
pub fn isSame(thisConfig: *const SSLConfig, otherConfig: *const SSLConfig) bool {
{ //strings
const fields = .{
"server_name",
"key_file_name",
"cert_file_name",
"ca_file_name",
"dh_params_file_name",
"passphrase",
"ssl_ciphers",
"protos",
};
inline for (fields) |field| {
const lhs = @field(thisConfig, field);
const rhs = @field(otherConfig, field);
if (lhs != null and rhs != null) {
if (!stringsEqual(lhs, rhs))
return false;
} else if (lhs != null or rhs != null) {
return false;
}
}
}
{
//numbers
const fields = .{ "secure_options", "request_cert", "reject_unauthorized", "low_memory_mode" };
inline for (fields) |field| {
const lhs = @field(thisConfig, field);
const rhs = @field(otherConfig, field);
if (lhs != rhs)
return false;
}
}
{
// complex fields
const fields = .{ "key", "ca", "cert" };
inline for (fields) |field| {
const lhs_count = @field(thisConfig, field ++ "_count");
const rhs_count = @field(otherConfig, field ++ "_count");
if (lhs_count != rhs_count)
return false;
if (lhs_count > 0) {
const lhs = @field(thisConfig, field);
const rhs = @field(otherConfig, field);
for (0..lhs_count) |i| {
if (!stringsEqual(lhs.?[i], rhs.?[i]))
return false;
}
}
}
}
return true;
}
fn stringsEqual(a: [*c]const u8, b: [*c]const u8) bool {
const lhs = bun.asByteSlice(a);
const rhs = bun.asByteSlice(b);
return strings.eqlLong(lhs, rhs, true);
}
pub fn deinit(this: *SSLConfig) void {
const fields = .{
"server_name",
"key_file_name",
"cert_file_name",
"ca_file_name",
"dh_params_file_name",
"passphrase",
"ssl_ciphers",
"protos",
};
inline for (fields) |field| {
if (@field(this, field)) |slice_ptr| {
const slice = std.mem.span(slice_ptr);
if (slice.len > 0) {
bun.freeSensitive(bun.default_allocator, slice);
}
@field(this, field) = "";
}
}
if (this.cert) |cert| {
for (0..this.cert_count) |i| {
const slice = std.mem.span(cert[i]);
if (slice.len > 0) {
bun.freeSensitive(bun.default_allocator, slice);
}
}
bun.default_allocator.free(cert);
this.cert = null;
}
if (this.key) |key| {
for (0..this.key_count) |i| {
const slice = std.mem.span(key[i]);
if (slice.len > 0) {
bun.freeSensitive(bun.default_allocator, slice);
}
}
bun.default_allocator.free(key);
this.key = null;
}
if (this.ca) |ca| {
for (0..this.ca_count) |i| {
const slice = std.mem.span(ca[i]);
if (slice.len > 0) {
bun.freeSensitive(bun.default_allocator, slice);
}
}
bun.default_allocator.free(ca);
this.ca = null;
}
}
pub const zero = SSLConfig{};
pub fn fromJS(vm: *JSC.VirtualMachine, global: *JSC.JSGlobalObject, obj: JSC.JSValue) bun.JSError!?SSLConfig {
var result = zero;
errdefer result.deinit();
var arena: bun.ArenaAllocator = bun.ArenaAllocator.init(bun.default_allocator);
defer arena.deinit();
if (!obj.isObject()) {
return global.throwInvalidArguments("tls option expects an object", .{});
}
var any = false;
result.reject_unauthorized = @intFromBool(vm.getTLSRejectUnauthorized());
// Required
if (try obj.getTruthy(global, "keyFile")) |key_file_name| {
var sliced = try key_file_name.toSlice(global, bun.default_allocator);
defer sliced.deinit();
if (sliced.len > 0) {
result.key_file_name = try bun.default_allocator.dupeZ(u8, sliced.slice());
if (std.posix.system.access(result.key_file_name, std.posix.F_OK) != 0) {
return global.throwInvalidArguments("Unable to access keyFile path", .{});
}
any = true;
result.requires_custom_request_ctx = true;
}
}
if (try obj.getTruthy(global, "key")) |js_obj| {
if (js_obj.jsType().isArray()) {
const count = js_obj.getLength(global);
if (count > 0) {
const native_array = try bun.default_allocator.alloc([*c]const u8, count);
var valid_count: u32 = 0;
for (0..count) |i| {
const item = js_obj.getIndex(global, @intCast(i));
if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item)) |sb| {
defer sb.deinit();
const sliced = sb.slice();
if (sliced.len > 0) {
native_array[valid_count] = try bun.default_allocator.dupeZ(u8, sliced);
valid_count += 1;
any = true;
result.requires_custom_request_ctx = true;
}
} else if (try BlobFileContentResult.init("key", item, global)) |content| {
if (content.data.len > 0) {
native_array[valid_count] = content.data.ptr;
valid_count += 1;
result.requires_custom_request_ctx = true;
any = true;
} else {
// mark and free all CA's
result.cert = native_array;
result.deinit();
return null;
}
} else {
// mark and free all keys
result.key = native_array;
return global.throwInvalidArguments("key argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{});
}
}
if (valid_count == 0) {
bun.default_allocator.free(native_array);
} else {
result.key = native_array;
}
result.key_count = valid_count;
}
} else if (try BlobFileContentResult.init("key", js_obj, global)) |content| {
if (content.data.len > 0) {
const native_array = try bun.default_allocator.alloc([*c]const u8, 1);
native_array[0] = content.data.ptr;
result.key = native_array;
result.key_count = 1;
any = true;
result.requires_custom_request_ctx = true;
} else {
result.deinit();
return null;
}
} else {
const native_array = try bun.default_allocator.alloc([*c]const u8, 1);
if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj)) |sb| {
defer sb.deinit();
const sliced = sb.slice();
if (sliced.len > 0) {
native_array[0] = try bun.default_allocator.dupeZ(u8, sliced);
any = true;
result.requires_custom_request_ctx = true;
result.key = native_array;
result.key_count = 1;
} else {
bun.default_allocator.free(native_array);
}
} else {
// mark and free all certs
result.key = native_array;
return global.throwInvalidArguments("key argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{});
}
}
}
if (try obj.getTruthy(global, "certFile")) |cert_file_name| {
var sliced = try cert_file_name.toSlice(global, bun.default_allocator);
defer sliced.deinit();
if (sliced.len > 0) {
result.cert_file_name = try bun.default_allocator.dupeZ(u8, sliced.slice());
if (std.posix.system.access(result.cert_file_name, std.posix.F_OK) != 0) {
return global.throwInvalidArguments("Unable to access certFile path", .{});
}
any = true;
result.requires_custom_request_ctx = true;
}
}
if (try obj.getTruthy(global, "ALPNProtocols")) |protocols| {
if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), protocols)) |sb| {
defer sb.deinit();
const sliced = sb.slice();
if (sliced.len > 0) {
result.protos = try bun.default_allocator.dupeZ(u8, sliced);
result.protos_len = sliced.len;
}
any = true;
result.requires_custom_request_ctx = true;
} else {
return global.throwInvalidArguments("ALPNProtocols argument must be an string, Buffer or TypedArray", .{});
}
}
if (try obj.getTruthy(global, "cert")) |js_obj| {
if (js_obj.jsType().isArray()) {
const count = js_obj.getLength(global);
if (count > 0) {
const native_array = try bun.default_allocator.alloc([*c]const u8, count);
var valid_count: u32 = 0;
for (0..count) |i| {
const item = js_obj.getIndex(global, @intCast(i));
if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item)) |sb| {
defer sb.deinit();
const sliced = sb.slice();
if (sliced.len > 0) {
native_array[valid_count] = try bun.default_allocator.dupeZ(u8, sliced);
valid_count += 1;
any = true;
result.requires_custom_request_ctx = true;
}
} else if (try BlobFileContentResult.init("cert", item, global)) |content| {
if (content.data.len > 0) {
native_array[valid_count] = content.data.ptr;
valid_count += 1;
result.requires_custom_request_ctx = true;
any = true;
} else {
// mark and free all CA's
result.cert = native_array;
result.deinit();
return null;
}
} else {
// mark and free all certs
result.cert = native_array;
return global.throwInvalidArguments("cert argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{});
}
}
if (valid_count == 0) {
bun.default_allocator.free(native_array);
} else {
result.cert = native_array;
}
result.cert_count = valid_count;
}
} else if (try BlobFileContentResult.init("cert", js_obj, global)) |content| {
if (content.data.len > 0) {
const native_array = try bun.default_allocator.alloc([*c]const u8, 1);
native_array[0] = content.data.ptr;
result.cert = native_array;
result.cert_count = 1;
any = true;
result.requires_custom_request_ctx = true;
} else {
result.deinit();
return null;
}
} else {
const native_array = try bun.default_allocator.alloc([*c]const u8, 1);
if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj)) |sb| {
defer sb.deinit();
const sliced = sb.slice();
if (sliced.len > 0) {
native_array[0] = try bun.default_allocator.dupeZ(u8, sliced);
any = true;
result.requires_custom_request_ctx = true;
result.cert = native_array;
result.cert_count = 1;
} else {
bun.default_allocator.free(native_array);
}
} else {
// mark and free all certs
result.cert = native_array;
return global.throwInvalidArguments("cert argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{});
}
}
}
if (try obj.getBooleanStrict(global, "requestCert")) |request_cert| {
result.request_cert = if (request_cert) 1 else 0;
any = true;
}
if (try obj.getBooleanStrict(global, "rejectUnauthorized")) |reject_unauthorized| {
result.reject_unauthorized = if (reject_unauthorized) 1 else 0;
any = true;
}
if (try obj.getTruthy(global, "ciphers")) |ssl_ciphers| {
var sliced = try ssl_ciphers.toSlice(global, bun.default_allocator);
defer sliced.deinit();
if (sliced.len > 0) {
result.ssl_ciphers = try bun.default_allocator.dupeZ(u8, sliced.slice());
any = true;
result.requires_custom_request_ctx = true;
}
}
if (try obj.getTruthy(global, "serverName") orelse try obj.getTruthy(global, "servername")) |server_name| {
var sliced = try server_name.toSlice(global, bun.default_allocator);
defer sliced.deinit();
if (sliced.len > 0) {
result.server_name = try bun.default_allocator.dupeZ(u8, sliced.slice());
any = true;
result.requires_custom_request_ctx = true;
}
}
if (try obj.getTruthy(global, "ca")) |js_obj| {
if (js_obj.jsType().isArray()) {
const count = js_obj.getLength(global);
if (count > 0) {
const native_array = try bun.default_allocator.alloc([*c]const u8, count);
var valid_count: u32 = 0;
for (0..count) |i| {
const item = js_obj.getIndex(global, @intCast(i));
if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item)) |sb| {
defer sb.deinit();
const sliced = sb.slice();
if (sliced.len > 0) {
native_array[valid_count] = bun.default_allocator.dupeZ(u8, sliced) catch unreachable;
valid_count += 1;
any = true;
result.requires_custom_request_ctx = true;
}
} else if (try BlobFileContentResult.init("ca", item, global)) |content| {
if (content.data.len > 0) {
native_array[valid_count] = content.data.ptr;
valid_count += 1;
any = true;
result.requires_custom_request_ctx = true;
} else {
// mark and free all CA's
result.cert = native_array;
result.deinit();
return null;
}
} else {
// mark and free all CA's
result.cert = native_array;
return global.throwInvalidArguments("ca argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{});
}
}
if (valid_count == 0) {
bun.default_allocator.free(native_array);
} else {
result.ca = native_array;
}
result.ca_count = valid_count;
}
} else if (try BlobFileContentResult.init("ca", js_obj, global)) |content| {
if (content.data.len > 0) {
const native_array = try bun.default_allocator.alloc([*c]const u8, 1);
native_array[0] = content.data.ptr;
result.ca = native_array;
result.ca_count = 1;
any = true;
result.requires_custom_request_ctx = true;
} else {
result.deinit();
return null;
}
} else {
const native_array = try bun.default_allocator.alloc([*c]const u8, 1);
if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj)) |sb| {
defer sb.deinit();
const sliced = sb.slice();
if (sliced.len > 0) {
native_array[0] = try bun.default_allocator.dupeZ(u8, sliced);
any = true;
result.requires_custom_request_ctx = true;
result.ca = native_array;
result.ca_count = 1;
} else {
bun.default_allocator.free(native_array);
}
} else {
// mark and free all certs
result.ca = native_array;
return global.throwInvalidArguments("ca argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile", .{});
}
}
}
if (try obj.getTruthy(global, "caFile")) |ca_file_name| {
var sliced = try ca_file_name.toSlice(global, bun.default_allocator);
defer sliced.deinit();
if (sliced.len > 0) {
result.ca_file_name = try bun.default_allocator.dupeZ(u8, sliced.slice());
if (std.posix.system.access(result.ca_file_name, std.posix.F_OK) != 0) {
return global.throwInvalidArguments("Invalid caFile path", .{});
}
}
}
// Optional
if (any) {
if (try obj.getTruthy(global, "secureOptions")) |secure_options| {
if (secure_options.isNumber()) {
result.secure_options = secure_options.toU32();
}
}
if (try obj.getTruthy(global, "clientRenegotiationLimit")) |client_renegotiation_limit| {
if (client_renegotiation_limit.isNumber()) {
result.client_renegotiation_limit = client_renegotiation_limit.toU32();
}
}
if (try obj.getTruthy(global, "clientRenegotiationWindow")) |client_renegotiation_window| {
if (client_renegotiation_window.isNumber()) {
result.client_renegotiation_window = client_renegotiation_window.toU32();
}
}
if (try obj.getTruthy(global, "dhParamsFile")) |dh_params_file_name| {
var sliced = try dh_params_file_name.toSlice(global, bun.default_allocator);
defer sliced.deinit();
if (sliced.len > 0) {
result.dh_params_file_name = try bun.default_allocator.dupeZ(u8, sliced.slice());
if (std.posix.system.access(result.dh_params_file_name, std.posix.F_OK) != 0) {
return global.throwInvalidArguments("Invalid dhParamsFile path", .{});
}
}
}
if (try obj.getTruthy(global, "passphrase")) |passphrase| {
var sliced = try passphrase.toSlice(global, bun.default_allocator);
defer sliced.deinit();
if (sliced.len > 0) {
result.passphrase = try bun.default_allocator.dupeZ(u8, sliced.slice());
}
}
if (try obj.get(global, "lowMemoryMode")) |low_memory_mode| {
if (low_memory_mode.isBoolean() or low_memory_mode.isUndefined()) {
result.low_memory_mode = low_memory_mode.toBoolean();
any = true;
} else {
return global.throw("Expected lowMemoryMode to be a boolean", .{});
}
}
}
if (!any)
return null;
return result;
}
const std = @import("std");
const bun = @import("bun");
const JSC = bun.JSC;
const uws = bun.uws;
const JSValue = JSC.JSValue;
const JSGlobalObject = JSC.JSGlobalObject;
const VirtualMachine = JSC.VirtualMachine;
const strings = bun.strings;

File diff suppressed because it is too large Load Diff

View File

@@ -1289,6 +1289,6 @@ const bun = @import("bun");
const string = []const u8;
const std = @import("std");
const ZigString = JSC.ZigString;
const WebSocketServer = @import("../server.zig").WebSocketServer;
const WebSocketServer = @import("../server.zig").WebSocketServerContext;
const uws = bun.uws;
const Output = bun.Output;

View File

@@ -0,0 +1,322 @@
const WebSocketServerContext = @This();
globalObject: *JSC.JSGlobalObject = undefined,
handler: Handler = .{},
maxPayloadLength: u32 = 1024 * 1024 * 16, // 16MB
maxLifetime: u16 = 0,
idleTimeout: u16 = 120, // 2 minutes
compression: i32 = 0,
backpressureLimit: u32 = 1024 * 1024 * 16, // 16MB
sendPingsAutomatically: bool = true,
resetIdleTimeoutOnSend: bool = true,
closeOnBackpressureLimit: bool = false,
pub const Handler = struct {
onOpen: JSC.JSValue = .zero,
onMessage: JSC.JSValue = .zero,
onClose: JSC.JSValue = .zero,
onDrain: JSC.JSValue = .zero,
onError: JSC.JSValue = .zero,
onPing: JSC.JSValue = .zero,
onPong: JSC.JSValue = .zero,
app: ?*anyopaque = null,
// Always set manually.
vm: *JSC.VirtualMachine = undefined,
globalObject: *JSC.JSGlobalObject = undefined,
active_connections: usize = 0,
/// used by publish()
flags: packed struct(u2) {
ssl: bool = false,
publish_to_self: bool = false,
} = .{},
pub fn runErrorCallback(this: *const Handler, vm: *JSC.VirtualMachine, globalObject: *JSC.JSGlobalObject, error_value: JSC.JSValue) void {
const onError = this.onError;
if (!onError.isEmptyOrUndefinedOrNull()) {
_ = onError.call(globalObject, .undefined, &.{error_value}) catch |err|
this.globalObject.reportActiveExceptionAsUnhandled(err);
return;
}
_ = vm.uncaughtException(globalObject, error_value, false);
}
pub fn fromJS(globalObject: *JSC.JSGlobalObject, object: JSC.JSValue) bun.JSError!Handler {
var handler = Handler{ .globalObject = globalObject, .vm = VirtualMachine.get() };
var valid = false;
if (try object.getTruthyComptime(globalObject, "message")) |message_| {
if (!message_.isCallable()) {
return globalObject.throwInvalidArguments("websocket expects a function for the message option", .{});
}
const message = message_.withAsyncContextIfNeeded(globalObject);
handler.onMessage = message;
message.ensureStillAlive();
valid = true;
}
if (try object.getTruthy(globalObject, "open")) |open_| {
if (!open_.isCallable()) {
return globalObject.throwInvalidArguments("websocket expects a function for the open option", .{});
}
const open = open_.withAsyncContextIfNeeded(globalObject);
handler.onOpen = open;
open.ensureStillAlive();
valid = true;
}
if (try object.getTruthy(globalObject, "close")) |close_| {
if (!close_.isCallable()) {
return globalObject.throwInvalidArguments("websocket expects a function for the close option", .{});
}
const close = close_.withAsyncContextIfNeeded(globalObject);
handler.onClose = close;
close.ensureStillAlive();
valid = true;
}
if (try object.getTruthy(globalObject, "drain")) |drain_| {
if (!drain_.isCallable()) {
return globalObject.throwInvalidArguments("websocket expects a function for the drain option", .{});
}
const drain = drain_.withAsyncContextIfNeeded(globalObject);
handler.onDrain = drain;
drain.ensureStillAlive();
valid = true;
}
if (try object.getTruthy(globalObject, "onError")) |onError_| {
if (!onError_.isCallable()) {
return globalObject.throwInvalidArguments("websocket expects a function for the onError option", .{});
}
const onError = onError_.withAsyncContextIfNeeded(globalObject);
handler.onError = onError;
onError.ensureStillAlive();
}
if (try object.getTruthy(globalObject, "ping")) |cb| {
if (!cb.isCallable()) {
return globalObject.throwInvalidArguments("websocket expects a function for the ping option", .{});
}
handler.onPing = cb;
cb.ensureStillAlive();
valid = true;
}
if (try object.getTruthy(globalObject, "pong")) |cb| {
if (!cb.isCallable()) {
return globalObject.throwInvalidArguments("websocket expects a function for the pong option", .{});
}
handler.onPong = cb;
cb.ensureStillAlive();
valid = true;
}
if (valid)
return handler;
return globalObject.throwInvalidArguments("WebSocketServerContext expects a message handler", .{});
}
pub fn protect(this: Handler) void {
this.onOpen.protect();
this.onMessage.protect();
this.onClose.protect();
this.onDrain.protect();
this.onError.protect();
this.onPing.protect();
this.onPong.protect();
}
pub fn unprotect(this: Handler) void {
if (this.vm.isShuttingDown()) {
return;
}
this.onOpen.unprotect();
this.onMessage.unprotect();
this.onClose.unprotect();
this.onDrain.unprotect();
this.onError.unprotect();
this.onPing.unprotect();
this.onPong.unprotect();
}
};
pub fn toBehavior(this: WebSocketServerContext) uws.WebSocketBehavior {
return .{
.maxPayloadLength = this.maxPayloadLength,
.idleTimeout = this.idleTimeout,
.compression = this.compression,
.maxBackpressure = this.backpressureLimit,
.sendPingsAutomatically = this.sendPingsAutomatically,
.maxLifetime = this.maxLifetime,
.resetIdleTimeoutOnSend = this.resetIdleTimeoutOnSend,
.closeOnBackpressureLimit = this.closeOnBackpressureLimit,
};
}
pub fn protect(this: WebSocketServerContext) void {
this.handler.protect();
}
pub fn unprotect(this: WebSocketServerContext) void {
this.handler.unprotect();
}
const CompressTable = bun.ComptimeStringMap(i32, .{
.{ "disable", 0 },
.{ "shared", uws.SHARED_COMPRESSOR },
.{ "dedicated", uws.DEDICATED_COMPRESSOR },
.{ "3KB", uws.DEDICATED_COMPRESSOR_3KB },
.{ "4KB", uws.DEDICATED_COMPRESSOR_4KB },
.{ "8KB", uws.DEDICATED_COMPRESSOR_8KB },
.{ "16KB", uws.DEDICATED_COMPRESSOR_16KB },
.{ "32KB", uws.DEDICATED_COMPRESSOR_32KB },
.{ "64KB", uws.DEDICATED_COMPRESSOR_64KB },
.{ "128KB", uws.DEDICATED_COMPRESSOR_128KB },
.{ "256KB", uws.DEDICATED_COMPRESSOR_256KB },
});
const DecompressTable = bun.ComptimeStringMap(i32, .{
.{ "disable", 0 },
.{ "shared", uws.SHARED_DECOMPRESSOR },
.{ "dedicated", uws.DEDICATED_DECOMPRESSOR },
.{ "3KB", uws.DEDICATED_COMPRESSOR_3KB },
.{ "4KB", uws.DEDICATED_COMPRESSOR_4KB },
.{ "8KB", uws.DEDICATED_COMPRESSOR_8KB },
.{ "16KB", uws.DEDICATED_COMPRESSOR_16KB },
.{ "32KB", uws.DEDICATED_COMPRESSOR_32KB },
.{ "64KB", uws.DEDICATED_COMPRESSOR_64KB },
.{ "128KB", uws.DEDICATED_COMPRESSOR_128KB },
.{ "256KB", uws.DEDICATED_COMPRESSOR_256KB },
});
pub fn onCreate(globalObject: *JSC.JSGlobalObject, object: JSValue) bun.JSError!WebSocketServerContext {
var server = WebSocketServerContext{};
server.handler = try Handler.fromJS(globalObject, object);
if (try object.get(globalObject, "perMessageDeflate")) |per_message_deflate| {
getter: {
if (per_message_deflate.isUndefined()) {
break :getter;
}
if (per_message_deflate.isBoolean() or per_message_deflate.isNull()) {
if (per_message_deflate.toBoolean()) {
server.compression = uws.SHARED_COMPRESSOR | uws.SHARED_DECOMPRESSOR;
} else {
server.compression = 0;
}
break :getter;
}
if (try per_message_deflate.getTruthy(globalObject, "compress")) |compression| {
if (compression.isBoolean()) {
server.compression |= if (compression.toBoolean()) uws.SHARED_COMPRESSOR else 0;
} else if (compression.isString()) {
server.compression |= CompressTable.getWithEql(try compression.getZigString(globalObject), ZigString.eqlComptime) orelse {
return globalObject.throwInvalidArguments("WebSocketServerContext expects a valid compress option, either disable \"shared\" \"dedicated\" \"3KB\" \"4KB\" \"8KB\" \"16KB\" \"32KB\" \"64KB\" \"128KB\" or \"256KB\"", .{});
};
} else {
return globalObject.throwInvalidArguments("websocket expects a valid compress option, either disable \"shared\" \"dedicated\" \"3KB\" \"4KB\" \"8KB\" \"16KB\" \"32KB\" \"64KB\" \"128KB\" or \"256KB\"", .{});
}
}
if (try per_message_deflate.getTruthy(globalObject, "decompress")) |compression| {
if (compression.isBoolean()) {
server.compression |= if (compression.toBoolean()) uws.SHARED_DECOMPRESSOR else 0;
} else if (compression.isString()) {
server.compression |= DecompressTable.getWithEql(try compression.getZigString(globalObject), ZigString.eqlComptime) orelse {
return globalObject.throwInvalidArguments("websocket expects a valid decompress option, either \"disable\" \"shared\" \"dedicated\" \"3KB\" \"4KB\" \"8KB\" \"16KB\" \"32KB\" \"64KB\" \"128KB\" or \"256KB\"", .{});
};
} else {
return globalObject.throwInvalidArguments("websocket expects a valid decompress option, either \"disable\" \"shared\" \"dedicated\" \"3KB\" \"4KB\" \"8KB\" \"16KB\" \"32KB\" \"64KB\" \"128KB\" or \"256KB\"", .{});
}
}
}
}
if (try object.get(globalObject, "maxPayloadLength")) |value| {
if (!value.isUndefinedOrNull()) {
if (!value.isAnyInt()) {
return globalObject.throwInvalidArguments("websocket expects maxPayloadLength to be an integer", .{});
}
server.maxPayloadLength = @truncate(@max(value.toInt64(), 0));
}
}
if (try object.get(globalObject, "idleTimeout")) |value| {
if (!value.isUndefinedOrNull()) {
if (!value.isAnyInt()) {
return globalObject.throwInvalidArguments("websocket expects idleTimeout to be an integer", .{});
}
var idleTimeout: u16 = @truncate(@max(value.toInt64(), 0));
if (idleTimeout > 960) {
return globalObject.throwInvalidArguments("websocket expects idleTimeout to be 960 or less", .{});
} else if (idleTimeout > 0) {
// uws does not allow idleTimeout to be between (0, 8),
// since its timer is not that accurate, therefore round up.
idleTimeout = @max(idleTimeout, 8);
}
server.idleTimeout = idleTimeout;
}
}
if (try object.get(globalObject, "backpressureLimit")) |value| {
if (!value.isUndefinedOrNull()) {
if (!value.isAnyInt()) {
return globalObject.throwInvalidArguments("websocket expects backpressureLimit to be an integer", .{});
}
server.backpressureLimit = @truncate(@max(value.toInt64(), 0));
}
}
if (try object.get(globalObject, "closeOnBackpressureLimit")) |value| {
if (!value.isUndefinedOrNull()) {
if (!value.isBoolean()) {
return globalObject.throwInvalidArguments("websocket expects closeOnBackpressureLimit to be a boolean", .{});
}
server.closeOnBackpressureLimit = value.toBoolean();
}
}
if (try object.get(globalObject, "sendPings")) |value| {
if (!value.isUndefinedOrNull()) {
if (!value.isBoolean()) {
return globalObject.throwInvalidArguments("websocket expects sendPings to be a boolean", .{});
}
server.sendPingsAutomatically = value.toBoolean();
}
}
if (try object.get(globalObject, "publishToSelf")) |value| {
if (!value.isUndefinedOrNull()) {
if (!value.isBoolean()) {
return globalObject.throwInvalidArguments("websocket expects publishToSelf to be a boolean", .{});
}
server.handler.flags.publish_to_self = value.toBoolean();
}
}
server.protect();
return server;
}
const bun = @import("bun");
const uws = bun.uws;
const JSC = bun.JSC;
const JSValue = JSC.JSValue;
const JSGlobalObject = JSC.JSGlobalObject;
const JSError = bun.JSError;
const VirtualMachine = JSC.VirtualMachine;
const ZigString = JSC.ZigString;