Skip to content

Commit fd7ce67

Browse files
committed
Make TCP server websocket-aware
Adding HTTP & websocket awareness to the TCP server. HTTP server handles `GET /json/version` and websocket upgrade requests. Conceptually, websocket handling is the same code as before, but receiving data will parse the websocket frames and writing data will wrap it in a websocket frame. The previous `Ctx` was split into a `Server` and a `Client`. This was largely done to make it easy to write unit tests, since the `Client` is a generic, all its dependencies (i.e. the server) can be mocked out. This also makes it a bit nicer to know if there is or isn't a client (via the server's client optional). Added a MemoryPool for the Send object (I thought that was a nice touch!) Removed MacOS hack on accept/conn completion usage. Known issues: - When framing an outgoing message, the entire message has to be duped. This is no worse than how it was before, but it should be possible to eliminate this in the future. Probably not part of this PR. - Websocket parsing will reject continuation frames. I don't know of a single client that will send a fragmented message (websocket has its own message fragmentation), but we should probably still support this just in case. - I don't think the receive, timeout and close completions can safely be re-used like we're doing. I believe they need to be associated with a specific client socket. - A new connection creates a new browser session. I think this is right (??), but for the very first, we're throwing out a perfectly usable session. I'm thinking this might be a change to how Browser/Sessions work. - zig build test won't compile. This branch reproduces the issue with none of these changes: https://github.com/karlseguin/browser/tree/broken_test_build (or, as a diff to main): main...karlseguin:broken_test_build
1 parent 1594f14 commit fd7ce67

File tree

8 files changed

+1352
-670
lines changed

8 files changed

+1352
-670
lines changed

.gitmodules

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,3 @@
2828
[submodule "vendor/zig-async-io"]
2929
path = vendor/zig-async-io
3030
url = https://github.com/lightpanda-io/zig-async-io.git/
31-
[submodule "vendor/websocket.zig"]
32-
path = vendor/websocket.zig
33-
url = https://github.com/lightpanda-io/websocket.zig.git/
34-
branch = lightpanda

src/browser/loader.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ test "basic url get" {
8484
var loader = Loader.init(alloc);
8585
defer loader.deinit();
8686

87-
var result = try loader.get(alloc, "https://en.wikipedia.org/wiki/Main_Page");
87+
const uri = try std.Uri.parse("https://en.wikipedia.org/wiki/Main_Page");
88+
var result = try loader.get(alloc, uri);
8889
defer result.deinit();
8990

9091
try std.testing.expect(result.req.response.status == std.http.Status.ok);

src/cdp/runtime.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,12 @@ fn sendInspector(
131131
const buf = try alloc.alloc(u8, msg.json.len + 1);
132132
defer alloc.free(buf);
133133
_ = std.mem.replace(u8, msg.json, "\"awaitPromise\":true", "\"awaitPromise\":false", buf);
134-
ctx.sendInspector(buf);
134+
try ctx.sendInspector(buf);
135135
return "";
136136
}
137137
}
138138

139-
ctx.sendInspector(msg.json);
139+
try ctx.sendInspector(msg.json);
140140

141141
if (msg.id == null) return "";
142142

src/handler.zig

Lines changed: 0 additions & 95 deletions
This file was deleted.

src/main.zig

Lines changed: 6 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ const websocket = @import("websocket");
2424

2525
const Browser = @import("browser/browser.zig").Browser;
2626
const server = @import("server.zig");
27-
const handler = @import("handler.zig");
28-
const MaxSize = @import("msg.zig").MaxSize;
2927

3028
const parser = @import("netsurf");
3129
const apiweb = @import("apiweb.zig");
@@ -86,11 +84,9 @@ const CliMode = union(CliModeTag) {
8684
const Server = struct {
8785
execname: []const u8 = undefined,
8886
args: *std.process.ArgIterator = undefined,
89-
addr: std.net.Address = undefined,
9087
host: []const u8 = Host,
9188
port: u16 = Port,
9289
timeout: u8 = Timeout,
93-
tcp: bool = false, // undocumented TCP mode
9490

9591
// default options
9692
const Host = "127.0.0.1";
@@ -160,10 +156,6 @@ const CliMode = union(CliModeTag) {
160156
return printUsageExit(execname, 1);
161157
}
162158
}
163-
if (std.mem.eql(u8, "--tcp", opt)) {
164-
_server.tcp = true;
165-
continue;
166-
}
167159

168160
// unknown option
169161
if (std.mem.startsWith(u8, opt, "--")) {
@@ -186,10 +178,6 @@ const CliMode = union(CliModeTag) {
186178
if (default_mode == .server) {
187179

188180
// server mode
189-
_server.addr = std.net.Address.parseIp4(_server.host, _server.port) catch |err| {
190-
log.err("address (host:port) {any}\n", .{err});
191-
return printUsageExit(execname, 1);
192-
};
193181
_server.execname = execname;
194182
_server.args = args;
195183
return CliMode{ .server = _server };
@@ -247,65 +235,19 @@ pub fn main() !void {
247235

248236
switch (cli_mode) {
249237
.server => |opts| {
250-
251-
// Stream server
252-
const addr = blk: {
253-
if (opts.tcp) {
254-
break :blk opts.addr;
255-
} else {
256-
const unix_path = "/tmp/lightpanda";
257-
std.fs.deleteFileAbsolute(unix_path) catch {}; // file could not exists
258-
break :blk try std.net.Address.initUnix(unix_path);
259-
}
260-
};
261-
const socket = server.listen(addr) catch |err| {
262-
log.err("Server listen error: {any}\n", .{err});
238+
const address = std.net.Address.parseIp4(opts.host, opts.port) catch |err| {
239+
log.err("address (host:port) {any}\n", .{err});
263240
return printUsageExit(opts.execname, 1);
264241
};
265-
defer std.posix.close(socket);
266-
log.debug("Server opts: listening internally on {any}...", .{addr});
267-
268-
const timeout = std.time.ns_per_s * @as(u64, opts.timeout);
269242

270-
// loop
271243
var loop = try jsruntime.Loop.init(alloc);
272244
defer loop.deinit();
273245

274-
// TCP server mode
275-
if (opts.tcp) {
276-
return server.handle(alloc, &loop, socket, null, timeout);
277-
}
278-
279-
// start stream server in separate thread
280-
var stream = handler.Stream{
281-
.ws_host = opts.host,
282-
.ws_port = opts.port,
283-
.addr = addr,
246+
const timeout = std.time.ns_per_s * @as(u64, opts.timeout);
247+
server.run(alloc, address, timeout, &loop) catch |err| {
248+
log.err("Server error", .{});
249+
return err;
284250
};
285-
const cdp_thread = try std.Thread.spawn(
286-
.{ .allocator = alloc },
287-
server.handle,
288-
.{ alloc, &loop, socket, &stream, timeout },
289-
);
290-
291-
// Websocket server
292-
var ws = try websocket.Server(handler.Handler).init(alloc, .{
293-
.port = opts.port,
294-
.address = opts.host,
295-
.max_message_size = MaxSize + 14, // overhead websocket
296-
.max_conn = 1,
297-
.handshake = .{
298-
.timeout = 3,
299-
.max_size = 1024,
300-
// since we aren't using hanshake.headers
301-
// we can set this to 0 to save a few bytes.
302-
.max_headers = 0,
303-
},
304-
});
305-
defer ws.deinit();
306-
307-
try ws.listen(&stream);
308-
cdp_thread.join();
309251
},
310252

311253
.fetch => |opts| {

0 commit comments

Comments
 (0)