╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ serve.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
110 runWebListener(io, allocator, &wui_server, &tasks, .{ .remote = .{ 111 .admin_repo_path = admin_repo_path, 112 .session_store = session_store, 113 } }, err); 114 115 if (@TypeOf(runnable) != void) { 116 try runnable.run(wui_server.socket.address.getPort(), ssh_server.socket.address.getPort()); 117 } else { 118 try tasks.await(io); 119 } 120 } 121 122 // serve just the web UI for a single local repo, running `runnable` (the 123 // local TUI) in the foreground while the listener runs in the background. 124 // when the default port is taken, a random ephemeral port is used instead; 125 // the bound port is passed to `runnable.run`. 126 pub fn runLocal( 127 io: std.Io, 128 allocator: std.mem.Allocator, 129 local: ui.RepoSource, 130 err: *std.Io.Writer, 131 runnable: anytype, 132 ) !void { 133 // always fall back, so another instance viewing a second repo just binds 134 // its own port 135 const wui_listen_address = try parseListenAddress((Options{}).wui_listen); 136 var wui_server = try listenWithFallback(io, wui_listen_address, true); 137 defer wui_server.deinit(io); 138 139 var tasks: std.Io.Group = .init; 140 defer tasks.cancel(io); 141 142 runWebListener(io, allocator, &wui_server, &tasks, .{ .local = local }, err); 143 144 try runnable.run(wui_server.socket.address.getPort()); 145 } 146 147 fn runWebListener( 148 io: std.Io, 149 allocator: std.mem.Allocator, 150 net_server: *std.Io.net.Server, 151 tasks: *std.Io.Group, 152 host: web.Host, 153 err: *std.Io.Writer, 154 ) void { 155 const Context = struct { 156 io: std.Io, 157 allocator: std.mem.Allocator, 158 host: web.Host, 159 err: *std.Io.Writer, 160 }; 161 162 const handle = struct { 163 fn h(ctx: Context, stream: std.Io.net.Stream) void { 164 defer stream.close(ctx.io); 165 web.handleConnection(ctx.io, ctx.allocator, stream, ctx.host, ctx.err) catch |request_err| { 166 serve_common.logError(ctx.io, ctx.err, "web ui request failed: {s}\n", .{@errorName(request_err)}); 167 }; 168 } 169 }.h; 170 171 serve_common.runListener(io, net_server, tasks, err, "web ui", Context{ 172 .io = io, 173 .allocator = allocator, 174 .host = host, 175 .err = err, 176 }, handle); 177 } 178 179 // bind the address. when it's taken, fall back to an OS-assigned port, or 180 // fail without `fallback`. no reuse_address: on linux it also sets 181 // SO_REUSEPORT, which would let this bind silently share a port an 182 // already-running server holds rather than seeing it as taken. 183 fn listenWithFallback(io: std.Io, listen_address: ListenAddress, fallback: bool) !std.Io.net.Server { 184 const address = try std.Io.net.IpAddress.parseIp4(listen_address.host, listen_address.port); 185 return address.listen(io, .{}) catch |listen_err| switch (listen_err) { 186 error.AddressInUse => blk: { 187 if (!fallback) return listen_err; 188 const any_port = try std.Io.net.IpAddress.parseIp4(listen_address.host, 0); 189 break :blk try any_port.listen(io, .{}); 190 }, 191 else => |e| return e, 192 }; 193 } 194 195 fn parseListenAddress(value: []const u8) !ListenAddress { 196 const colon = std.mem.lastIndexOfScalar(u8, value, ':') orelse return error.InvalidListenAddress; 197 if (colon == 0 or colon + 1 >= value.len) return error.InvalidListenAddress; 198 const port = try std.fmt.parseInt(u16, value[colon + 1 ..], 10); 199 return .{ .host = value[0..colon], .port = port }; 200 } 201