╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ serve.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
153 .ssh_url = ssh_url, 154 .git_ssh_prefix = options.git_ssh_prefix, 155 .serve_git = serve_http.gitHandler(repo_kind, any_repo_opts), 156 } }, err); 157 158 if (@TypeOf(runnable) != void) { 159 try runnable.run(web_url, ssh_url); 160 } else { 161 try tasks.await(io); 162 } 163 } 164 165 // serve just the web UI for a single local repo, running `runnable` (the 166 // local TUI) in the foreground while the listener runs in the background. 167 // when the default port is taken, a random ephemeral port is used instead; 168 // the web UI's url is passed to `runnable.run`. 169 pub fn runLocal( 170 io: std.Io, 171 allocator: std.mem.Allocator, 172 local: ui.RepoSource, 173 err: *std.Io.Writer, 174 runnable: anytype, 175 ) !void { 176 // always fall back, so another instance viewing a second repo just binds 177 // its own port 178 const web_listen_address = try parseListenAddress((Options{}).web_listen); 179 var web_server = try listen(io, web_listen_address, .fallback); 180 defer web_server.deinit(io); 181 182 var tasks: std.Io.Group = .init; 183 defer tasks.cancel(io); 184 185 runWebListener(io, allocator, &web_server, &tasks, .{ .local = local }, err); 186 187 const web_url = try allocator.print("http://localhost:{d}", .{web_server.socket.address.getPort()}); 188 defer allocator.free(web_url); 189 try runnable.run(web_url); 190 } 191 192 fn runWebListener( 193 io: std.Io, 194 allocator: std.mem.Allocator, 195 net_server: *std.Io.net.Server, 196 tasks: *std.Io.Group, 197 host: web.Host, 198 err: *std.Io.Writer, 199 ) void { 200 const Context = struct { 201 io: std.Io, 202 allocator: std.mem.Allocator, 203 host: web.Host, 204 err: *std.Io.Writer, 205 }; 206 207 const handle = struct { 208 fn h(ctx: Context, stream: std.Io.net.Stream) void { 209 defer stream.close(ctx.io); 210 web.handleConnection(ctx.io, ctx.allocator, stream, ctx.host, ctx.err) catch |request_err| { 211 serve_common.logError(ctx.io, ctx.err, "web ui request failed: {s}\n", .{@errorName(request_err)}); 212 }; 213 } 214 }.h; 215 216 serve_common.runListener(io, net_server, tasks, err, "web ui", Context{ 217 .io = io, 218 .allocator = allocator, 219 .host = host, 220 .err = err, 221 }, handle); 222 } 223 224 // what a listener does with a port that is already taken 225 const PortPolicy = enum { 226 exact, 227 // leave it to whoever holds it and take an OS-assigned port instead 228 fallback, 229 }; 230 231 fn listen(io: std.Io, listen_address: ListenAddress, policy: PortPolicy) !std.Io.net.Server { 232 const address = try std.Io.net.IpAddress.parseIp4(listen_address.host, listen_address.port); 233 return switch (policy) { 234 .exact => try address.listen(io, .{}), 235 .fallback => address.listen(io, .{}) catch |listen_err| switch (listen_err) { 236 error.AddressInUse => blk: { 237 const any_port = try std.Io.net.IpAddress.parseIp4(listen_address.host, 0); 238 break :blk try any_port.listen(io, .{}); 239 }, 240 else => |e| return e, 241 }, 242 }; 243 } 244 245 fn parseListenAddress(value: []const u8) !ListenAddress { 246 const colon = std.mem.lastIndexOfScalar(u8, value, ':') orelse return error.InvalidListenAddress; 247 if (colon == 0 or colon + 1 >= value.len) return error.InvalidListenAddress; 248 const port = try std.fmt.parseInt(u16, value[colon + 1 ..], 10); 249 return .{ .host = value[0..colon], .port = port }; 250 } 251