╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │109 errdefer listener.deinit(io); │ │111 - const ssh_port = listener.port(); │ │110 + const ssh_url = options.ssh_url orelse url_blk: { │ │111 + const url = try allocator.print("ssh://localhost:{d}", .{listener.port()}); │ │112 + ssh_url_owned = url; │ │113 + break :url_blk url; │ │114 + }; │ │115 break :blk .{ │ │116 .listener = listener, │ │117 .host_key = try serve_ssh_protocol.HostKey.loadOrGenerate(io, allocator, data_dir_path),│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │118 .session_handler = .{ │ │119 .admin_repo_path = admin_repo_path, │ │120 .server = .{ .users_dir = users_dir, .admin_repo_path = admin_repo_path },│ │118 - .wui_port = wui_server.socket.address.getPort(), │ │119 - .git_http_port = git_http_port, │ │120 - .git_ssh_port = ssh_port, │ │121 + .web_url = web_url, │ │122 + .ssh_url = ssh_url, │ │123 .git_ssh_prefix = options.git_ssh_prefix, │ │124 .err = err, │ │125 }, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │134 │ │135 // run listeners, printing the ports actually bound │ │136 │ │135 - const git_ssh_port: ?u16 = if (ssh_maybe) |*ssh| ssh.listener.port() else null; │ │136 - │ │137 - if (http_maybe) |*http| { │ │138 - try err.print("serving HTTP on {s}:{d}, users dir {s}\n", .{ http.address.host, http.port(), users_dir }); │ │139 - try err.flush(); │ │140 - serve_http.runListener(repo_kind, any_repo_opts, io, allocator, users_dir, admin_repo_path, &http.server, &tasks, err); │ │141 - } │ │137 + const ssh_url: ?[]const u8 = if (ssh_maybe) |*ssh| ssh.session_handler.ssh_url else null; │ │138 │ │139 if (ssh_maybe) |*ssh| { │ │140 try err.print("serving SSH on {s}:{d}, host key ED25519 {s}\n", .{ ssh.listener.address.host, ssh.listener.port(), try ssh.host_key.fingerprint(allocator) });│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │142 serve_ssh.runListener(io, allocator, &ssh.host_key, &ssh.session_handler, &ssh.watchdog, &ssh.listener.server, &tasks, err); │ │143 } │ │144 │ │149 - try err.print("serving web UI on http://{s}:{d}/\n", .{ wui_listen_address.host, wui_server.socket.address.getPort() }); │ │145 + try err.print("serving web UI and git over HTTP on http://{s}:{d}/, users dir {s}\n", .{ web_listen_address.host, web_server.socket.address.getPort(), users_dir });│ │146 try err.flush(); │ │147 │ │152 - runWebListener(io, allocator, &wui_server, &tasks, .{ .server = .{ │ │148 + runWebListener(io, allocator, &web_server, &tasks, .{ .server = .{ │ │149 .admin_repo_path = admin_repo_path, │ │150 .users_dir = users_dir, │ │151 .session_store = session_store, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │156 - .git_http_port = git_http_port, │ │157 - .git_ssh_port = git_ssh_port, │ │152 + .web_url = web_url, │ │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) { │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │162 - try runnable.run(wui_server.socket.address.getPort(), git_http_port, git_ssh_port);│ │159 + try runnable.run(web_url, ssh_url); │ │160 } else { │ │161 try tasks.await(io); │ │162 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │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;│ │171 -// the bound port is passed to `runnable.run`. │ │168 +// the web UI's url is passed to `runnable.run`. │ │169 pub fn runLocal( │ │170 io: std.Io, │ │171 allocator: std.mem.Allocator, │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │175 ) !void { │ │176 // always fall back, so another instance viewing a second repo just binds │ │177 // its own port │ │181 - const wui_listen_address = try parseListenAddress((Options{}).wui_listen);│ │182 - var wui_server = try listen(io, wui_listen_address, .fallback); │ │183 - defer wui_server.deinit(io); │ │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 │ │188 - runWebListener(io, allocator, &wui_server, &tasks, .{ .local = local }, err); │ │185 + runWebListener(io, allocator, &web_server, &tasks, .{ .local = local }, err); │ │186 │ │190 - try runnable.run(wui_server.socket.address.getPort()); │ │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( │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/serve_http.zig│ ╰──────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 4 const evt = @import("./event.zig"); │ │ 5 const serve_common = @import("./serve_common.zig"); │ │ 6 │ │ 7 -pub fn runListener( │ │ 8 - comptime repo_kind: rp.RepoKind, │ │ 9 - comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), │ │10 - io: std.Io, │ │11 - allocator: std.mem.Allocator, │ │12 - users_dir: []const u8, │ │13 - admin_repo_path: []const u8, │ │14 - net_server: *std.Io.net.Server, │ │15 - tasks: *std.Io.Group, │ │16 - err: *std.Io.Writer, │ │17 -) void { │ │18 - const Context = struct { │ │19 - io: std.Io, │ │20 - allocator: std.mem.Allocator, │ │21 - users_dir: []const u8, │ │22 - admin_repo_path: []const u8, │ │23 - err: *std.Io.Writer, │ │ 7 +// whether a web listener request is git's smart http rather than a page │ │ 8 +pub fn isGitRequest(head: *const std.http.Server.Request.Head) bool { │ │ 9 + const uri = std.Uri.parseAfterScheme("", head.target) catch return false; │ │10 + const handler, _ = findRoute(uri.path.percent_encoded) orelse return false; │ │11 + return switch (handler) { │ │12 + // a page url can end in "/info/refs" too, but never has a query │ │13 + .get_info_refs => if (uri.query) |q| std.mem.startsWith(u8, q.percent_encoded, "service=") else false, │ │14 + .run_service => head.method == .POST, │ │15 }; │ │25 - │ │26 - const handle = struct { │ │27 - fn h(ctx: Context, stream: std.Io.net.Stream) void { │ │28 - defer stream.close(ctx.io); │ │29 - handleConnection(repo_kind, any_repo_opts, ctx.io, ctx.allocator, ctx.users_dir, ctx.admin_repo_path, stream, ctx.err) catch |request_err| {│ │30 - serve_common.logError(ctx.io, ctx.err, "connection failed: {s}\n", .{@errorName(request_err)}); │ │31 - }; │ │32 - } │ │33 - }.h; │ │34 - │ │35 - serve_common.runListener(io, net_server, tasks, err, "http", Context{ │ │36 - .io = io, │ │37 - .allocator = allocator, │ │38 - .users_dir = users_dir, │ │39 - .admin_repo_path = admin_repo_path, │ │40 - .err = err, │ │41 - }, handle); │ │16 } │ │17 │ │44 -fn handleConnection( │ │45 - comptime repo_kind: rp.RepoKind, │ │46 - comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), │ │18 +pub const GitHandler = *const fn ( │ │19 io: std.Io, │ │20 allocator: std.mem.Allocator, │ │21 users_dir: []const u8, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯