From 4e2e895cd92691ba0cb838730b71f816cd5315a0 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sun, 22 Mar 2026 12:40:17 +0800 Subject: [PATCH 1/2] Add --advertise_host option to serve command Allows overwriting the --host for the json/version payload. When --host is set to 0.0.0.0, we want to provide a mechanism to specify the specific address to connect to in /json/version (or anywhere else that we "advertise" the address). Inspired by https://github.com/lightpanda-io/browser/pull/1923 but rather than defaulting to 127.0.0.1 (which seems just as unsafe), adds the explicit config option. --- src/Config.zig | 29 +++++++++++++++++++++++++++++ src/Server.zig | 18 ++++++++++++------ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Config.zig b/src/Config.zig index 0bec5b7a..8b3f0dfd 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -163,6 +163,20 @@ pub fn cdpTimeout(self: *const Config) usize { }; } +pub fn port(self: *const Config) u16 { + return switch (self.mode) { + .serve => |opts| opts.port, + else => unreachable, + }; +} + +pub fn advertiseHost(self: *const Config) []const u8 { + return switch (self.mode) { + .serve => |opts| opts.advertise_host orelse opts.host, + else => unreachable, + }; +} + pub fn webBotAuth(self: *const Config) ?WebBotAuthConfig { return switch (self.mode) { inline .serve, .fetch, .mcp => |opts| WebBotAuthConfig{ @@ -199,6 +213,7 @@ pub const Mode = union(RunMode) { pub const Serve = struct { host: []const u8 = "127.0.0.1", port: u16 = 9222, + advertise_host: ?[]const u8 = null, timeout: u31 = 10, cdp_max_connections: u16 = 16, cdp_max_pending_connections: u16 = 128, @@ -416,6 +431,11 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { \\--port Port of the CDP server \\ Defaults to 9222 \\ + \\--advertise_host + \\ The host to advertise, e.g. in the /json/version response. + \\ Useful, for example, when --host is 0.0.0.0. + \\ Defaults to --host value + \\ \\--timeout Inactivity timeout in seconds before disconnecting clients \\ Defaults to 10 (seconds). Limited to 604800 (1 week). \\ @@ -557,6 +577,15 @@ fn parseServeArgs( continue; } + if (std.mem.eql(u8, "--advertise_host", opt)) { + const str = args.next() orelse { + log.fatal(.app, "missing argument value", .{ .arg = "--advertise_host" }); + return error.InvalidArgument; + }; + serve.advertise_host = try allocator.dupe(u8, str); + continue; + } + if (std.mem.eql(u8, "--timeout", opt)) { const str = args.next() orelse { log.fatal(.app, "missing argument value", .{ .arg = "--timeout" }); diff --git a/src/Server.zig b/src/Server.zig index d172f6dd..02ebf844 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -45,7 +45,7 @@ clients_pool: std.heap.MemoryPool(Client), pub fn init(app: *App, address: net.Address) !*Server { const allocator = app.allocator; - const json_version_response = try buildJSONVersionResponse(allocator, address); + const json_version_response = try buildJSONVersionResponse(app); errdefer allocator.free(json_version_response); const self = try allocator.create(Server); @@ -484,11 +484,17 @@ pub const Client = struct { // -------- fn buildJSONVersionResponse( - allocator: Allocator, - address: net.Address, + app: *const App, ) ![]const u8 { - const body_format = "{{\"webSocketDebuggerUrl\": \"ws://{f}/\"}}"; - const body_len = std.fmt.count(body_format, .{address}); + const port = app.config.port(); + const host = app.config.advertiseHost(); + if (std.mem.eql(u8, host, "0.0.0.0")) { + log.info(.cdp, "unreachable advertised host", .{ + .message = "when --host is set to 0.0.0.0 consider setting --advertise_host to a reachable address", + }); + } + const body_format = "{{\"webSocketDebuggerUrl\": \"ws://{s}:{d}/\"}}"; + const body_len = std.fmt.count(body_format, .{host, port}); // We send a Connection: Close (and actually close the connection) // because chromedp (Go driver) sends a request to /json/version and then @@ -502,7 +508,7 @@ fn buildJSONVersionResponse( "Connection: Close\r\n" ++ "Content-Type: application/json; charset=UTF-8\r\n\r\n" ++ body_format; - return try std.fmt.allocPrint(allocator, response_format, .{ body_len, address }); + return try std.fmt.allocPrint(app.allocator, response_format, .{ body_len, host, port }); } pub const timestamp = @import("datetime.zig").timestamp; From e3085cb0f1ade8c0253acd057f938042eb72fd36 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sun, 22 Mar 2026 12:47:33 +0800 Subject: [PATCH 2/2] fix test --- src/Server.zig | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index 02ebf844..0329dcd8 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -494,7 +494,7 @@ fn buildJSONVersionResponse( }); } const body_format = "{{\"webSocketDebuggerUrl\": \"ws://{s}:{d}/\"}}"; - const body_len = std.fmt.count(body_format, .{host, port}); + const body_len = std.fmt.count(body_format, .{ host, port }); // We send a Connection: Close (and actually close the connection) // because chromedp (Go driver) sends a request to /json/version and then @@ -514,17 +514,16 @@ fn buildJSONVersionResponse( pub const timestamp = @import("datetime.zig").timestamp; pub const milliTimestamp = @import("datetime.zig").milliTimestamp; -const testing = std.testing; +const testing = @import("testing.zig"); test "server: buildJSONVersionResponse" { - const address = try net.Address.parseIp4("127.0.0.1", 9001); - const res = try buildJSONVersionResponse(testing.allocator, address); - defer testing.allocator.free(res); + const res = try buildJSONVersionResponse(testing.test_app); + defer testing.test_app.allocator.free(res); - try testing.expectEqualStrings("HTTP/1.1 200 OK\r\n" ++ + try testing.expectEqual("HTTP/1.1 200 OK\r\n" ++ "Content-Length: 48\r\n" ++ "Connection: Close\r\n" ++ "Content-Type: application/json; charset=UTF-8\r\n\r\n" ++ - "{\"webSocketDebuggerUrl\": \"ws://127.0.0.1:9001/\"}", res); + "{\"webSocketDebuggerUrl\": \"ws://127.0.0.1:9222/\"}", res); } test "Client: http invalid request" { @@ -532,7 +531,7 @@ test "Client: http invalid request" { defer c.deinit(); const res = try c.httpRequest("GET /over/9000 HTTP/1.1\r\n" ++ "Header: " ++ ("a" ** 4100) ++ "\r\n\r\n"); - try testing.expectEqualStrings("HTTP/1.1 413 \r\n" ++ + try testing.expectEqual("HTTP/1.1 413 \r\n" ++ "Connection: Close\r\n" ++ "Content-Length: 17\r\n\r\n" ++ "Request too large", res); @@ -601,7 +600,7 @@ test "Client: http valid handshake" { "Custom: Header-Value\r\n\r\n"; const res = try c.httpRequest(request); - try testing.expectEqualStrings("HTTP/1.1 101 Switching Protocols\r\n" ++ + try testing.expectEqual("HTTP/1.1 101 Switching Protocols\r\n" ++ "Upgrade: websocket\r\n" ++ "Connection: upgrade\r\n" ++ "Sec-Websocket-Accept: flzHu2DevQ2dSCSVqKSii5e9C2o=\r\n\r\n", res); @@ -729,7 +728,7 @@ test "server: 404" { defer c.deinit(); const res = try c.httpRequest("GET /unknown HTTP/1.1\r\n\r\n"); - try testing.expectEqualStrings("HTTP/1.1 404 \r\n" ++ + try testing.expectEqual("HTTP/1.1 404 \r\n" ++ "Connection: Close\r\n" ++ "Content-Length: 9\r\n\r\n" ++ "Not found", res); @@ -741,7 +740,7 @@ test "server: get /json/version" { "Content-Length: 48\r\n" ++ "Connection: Close\r\n" ++ "Content-Type: application/json; charset=UTF-8\r\n\r\n" ++ - "{\"webSocketDebuggerUrl\": \"ws://127.0.0.1:9583/\"}"; + "{\"webSocketDebuggerUrl\": \"ws://127.0.0.1:9222/\"}"; { // twice on the same connection @@ -749,7 +748,7 @@ test "server: get /json/version" { defer c.deinit(); const res1 = try c.httpRequest("GET /json/version HTTP/1.1\r\n\r\n"); - try testing.expectEqualStrings(expected_response, res1); + try testing.expectEqual(expected_response, res1); } { @@ -758,7 +757,7 @@ test "server: get /json/version" { defer c.deinit(); const res1 = try c.httpRequest("GET /json/version HTTP/1.1\r\n\r\n"); - try testing.expectEqualStrings(expected_response, res1); + try testing.expectEqual(expected_response, res1); } } @@ -776,7 +775,7 @@ fn assertHTTPError( .{ expected_status, expected_body.len, expected_body }, ); - try testing.expectEqualStrings(expected_response, res); + try testing.expectEqual(expected_response, res); } fn assertWebSocketError(close_code: u16, input: []const u8) !void { @@ -920,7 +919,7 @@ const TestClient = struct { "Custom: Header-Value\r\n\r\n"; const res = try self.httpRequest(request); - try testing.expectEqualStrings("HTTP/1.1 101 Switching Protocols\r\n" ++ + try testing.expectEqual("HTTP/1.1 101 Switching Protocols\r\n" ++ "Upgrade: websocket\r\n" ++ "Connection: upgrade\r\n" ++ "Sec-Websocket-Accept: flzHu2DevQ2dSCSVqKSii5e9C2o=\r\n\r\n", res);