╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │29 defer allocator.free(repo_root); │ │30 │ │31 try std.Io.Dir.cwd().createDirPath(io, repo_root); │ │32 + │ │33 + const http_listen_address = try parseListenAddress(options.http_listen); │ │34 + const http_address = try std.Io.net.IpAddress.parseIp4(http_listen_address.host, http_listen_address.port); │ │35 + var http_server = try http_address.listen(io, .{ .reuse_address = true }); │ │36 + defer http_server.deinit(io); │ │37 + │ │38 + try err.print("serving HTTP on {s}, repo root {s}\n", .{ options.http_listen, repo_root }); │ │39 + try err.flush(); │ │40 + │ │41 + var tasks: std.Io.Group = .init; │ │42 + defer tasks.cancel(io); │ │43 │ │44 + runHttpListener(repo_kind, any_repo_opts, io, allocator, repo_root, &http_server, &tasks, err); │ │45 + │ │46 + var ssh_server: ?std.Io.net.Server = null; │ │47 + defer if (ssh_server) |*server| server.deinit(io); │ │48 + │ │49 if (options.ssh_listen) |ssh_listen| { │ │34 - const HttpContext = struct { │ │35 - io: std.Io, │ │36 - allocator: std.mem.Allocator, │ │37 - repo_root: []const u8, │ │38 - http_listen: []const u8, │ │39 - err: *std.Io.Writer, │ │50 + const ssh_listen_address = try parseListenAddress(ssh_listen); │ │51 + const ssh_address = try std.Io.net.IpAddress.parseIp4(ssh_listen_address.host, ssh_listen_address.port); │ │52 + ssh_server = try ssh_address.listen(io, .{ .reuse_address = true }); │ │53 │ │41 - fn run(ctx: @This()) !void { │ │42 - try runHttpListener(repo_kind, any_repo_opts, ctx.io, ctx.allocator, ctx.repo_root, ctx.http_listen, ctx.err);│ │43 - } │ │44 - }; │ │45 - │ │46 - const http_thread = try std.Thread.spawn(.{}, HttpContext.run, .{HttpContext{ │ │47 - .io = io, │ │48 - .allocator = allocator, │ │49 - .repo_root = repo_root, │ │50 - .http_listen = options.http_listen, │ │51 - .err = err, │ │52 - }}); │ │53 - defer http_thread.join(); │ │54 + try err.print("serving SSH helper connections on {s}, repo root {s}\n", .{ ssh_listen, repo_root }); │ │55 + try err.flush(); │ │56 │ │55 - try runSshListener(repo_kind, any_repo_opts, io, allocator, repo_root, ssh_listen, err); │ │56 - } else { │ │57 - try runHttpListener(repo_kind, any_repo_opts, io, allocator, repo_root, options.http_listen, err); │ │57 + if (ssh_server) |*server| { │ │58 + runSshListener(repo_kind, any_repo_opts, io, allocator, repo_root, server, &tasks, err); │ │59 + } │ │60 } │ │61 + │ │62 + try tasks.await(io); │ │63 } │ │64 │ │65 fn runHttpListener( │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 68 io: std.Io, │ │ 69 allocator: std.mem.Allocator, │ │ 70 repo_root: []const u8, │ │ 67 - http_listen: []const u8, │ │ 71 + net_server: *std.Io.net.Server, │ │ 72 + tasks: *std.Io.Group, │ │ 73 err: *std.Io.Writer, │ │ 69 -) !void { │ │ 70 - const listen_address = try parseListenAddress(http_listen); │ │ 71 - const address = try std.Io.net.IpAddress.parseIp4(listen_address.host, listen_address.port); │ │ 72 - var net_server = try address.listen(io, .{ .reuse_address = true }); │ │ 73 - defer net_server.deinit(io); │ │ 74 +) void { │ │ 75 + const Listener = struct { │ │ 76 + io: std.Io, │ │ 77 + allocator: std.mem.Allocator, │ │ 78 + repo_root: []const u8, │ │ 79 + net_server: *std.Io.net.Server, │ │ 80 + tasks: *std.Io.Group, │ │ 81 + err: *std.Io.Writer, │ │ 82 + │ │ 83 + fn run(ctx: @This()) void { │ │ 84 + while (true) { │ │ 85 + const stream = ctx.net_server.accept(ctx.io) catch |accept_err| { │ │ 86 + logError(ctx.err, "accept failed: {s}\n", .{@errorName(accept_err)}); │ │ 87 + continue; │ │ 88 + }; │ │ 89 + │ │ 90 + const Connection = struct { │ │ 91 + io: std.Io, │ │ 92 + allocator: std.mem.Allocator, │ │ 93 + repo_root: []const u8, │ │ 94 + stream: std.Io.net.Stream, │ │ 95 + err: *std.Io.Writer, │ │ 96 + │ │ 97 + fn run(conn: @This()) void { │ │ 98 + defer conn.stream.close(conn.io); │ │ 99 + handleHttpConnection(repo_kind, any_repo_opts, conn.io, conn.allocator, conn.repo_root, conn.stream, conn.err) catch |request_err| {│ │100 + logError(conn.err, "connection failed: {s}\n", .{@errorName(request_err)}); │ │101 + }; │ │102 + } │ │103 + }; │ │104 + │ │105 + ctx.tasks.async(ctx.io, Connection.run, .{Connection{ │ │106 + .io = ctx.io, │ │107 + .allocator = ctx.allocator, │ │108 + .repo_root = ctx.repo_root, │ │109 + .stream = stream, │ │110 + .err = ctx.err, │ │111 + }}); │ │112 + } │ │113 + } │ │114 + }; │ │115 │ │ 75 - try err.print("serving HTTP on {s}, repo root {s}\n", .{ http_listen, repo_root }); │ │ 76 - try err.flush(); │ │116 + tasks.async(io, Listener.run, .{Listener{ │ │117 + .io = io, │ │118 + .allocator = allocator, │ │119 + .repo_root = repo_root, │ │120 + .net_server = net_server, │ │121 + .tasks = tasks, │ │122 + .err = err, │ │123 + }}); │ │124 +} │ │125 │ │126 +fn handleHttpConnection( │ │127 + comptime repo_kind: rp.RepoKind, │ │128 + comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), │ │129 + io: std.Io, │ │130 + allocator: std.mem.Allocator, │ │131 + repo_root: []const u8, │ │132 + stream: std.Io.net.Stream, │ │133 + err: *std.Io.Writer, │ │134 +) !void { │ │135 var send_buffer = [_]u8{0} ** any_repo_opts.net_buffer_size; │ │136 var recv_buffer = [_]u8{0} ** any_repo_opts.net_buffer_size; │ │137 + var conn_br = stream.reader(io, &recv_buffer); │ │138 + var conn_bw = stream.writer(io, &send_buffer); │ │139 + var http_server = std.http.Server.init(&conn_br.interface, &conn_bw.interface); │ │140 │ │ 81 - while (true) { │ │ 82 - const stream = net_server.accept(io) catch |accept_err| { │ │ 83 - try err.print("accept failed: {s}\n", .{@errorName(accept_err)}); │ │ 84 - continue; │ │141 + while (http_server.reader.state == .ready) { │ │142 + var request = http_server.receiveHead() catch |receive_err| switch (receive_err) { │ │143 + error.HttpConnectionClosing => break, │ │144 + error.ReadFailed => break, │ │145 + else => |e| return e, │ │146 }; │ │ 86 - defer stream.close(io); │ │147 │ │ 88 - var conn_br = stream.reader(io, &recv_buffer); │ │ 89 - var conn_bw = stream.writer(io, &send_buffer); │ │ 90 - var http_server = std.http.Server.init(&conn_br.interface, &conn_bw.interface); │ │ 91 - │ │ 92 - while (http_server.reader.state == .ready) { │ │ 93 - var request = http_server.receiveHead() catch |receive_err| switch (receive_err) { │ │ 94 - error.HttpConnectionClosing => break, │ │ 95 - error.ReadFailed => break, │ │ 96 - else => |e| return e, │ │ 97 - }; │ │ 98 - │ │ 99 - handleRequest(repo_kind, any_repo_opts, io, allocator, repo_root, &http_server, &request) catch |request_err| { │ │100 - try err.print("request failed: {s}\n", .{@errorName(request_err)}); │ │101 - try err.flush(); │ │102 - if (http_server.reader.state == .received_head) { │ │103 - http_server.reader.state = .ready; │ │104 - } │ │105 - try writeSimpleResponse(&http_server, 500, "Internal Server Error", "text/plain", @errorName(request_err)); │ │106 - }; │ │107 - try http_server.out.flush(); │ │108 - break; │ │109 - } │ │148 + handleHttpGitRequest(repo_kind, any_repo_opts, io, allocator, repo_root, &http_server, &request) catch |request_err| { │ │149 + try err.print("request failed: {s}\n", .{@errorName(request_err)}); │ │150 + try err.flush(); │ │151 + if (http_server.reader.state == .received_head) { │ │152 + http_server.reader.state = .ready; │ │153 + } │ │154 + try writeSimpleResponse(&http_server, 500, "Internal Server Error", "text/plain", @errorName(request_err)); │ │155 + }; │ │156 + try http_server.out.flush(); │ │157 + break; │ │158 } │ │159 } │ │160 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────╮ │113 -fn handleRequest( │ │161 +fn handleHttpGitRequest( │ │162 comptime repo_kind: rp.RepoKind, │ │163 comptime any_repo_opts: rp.AnyRepoOpts(repo_kind),│ │164 io: std.Io, │ ╰───────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │224 .has_remote_user = has_remote_user, │ │225 .protocol_version = protocol_version, │ │226 }; │ │179 - │ │180 - if (any_repo_opts.hash) |hash_kind| { │ │181 - var repo = try openRepo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), io, allocator, repo_path, create_if_missing);│ │182 - defer repo.deinit(io, allocator); │ │183 - try repo.httpBackend(io, allocator, &body_reader, http_server.out, .http, http_backend_options); │ │184 - } else { │ │185 - var any_repo = rp.AnyRepo(repo_kind, any_repo_opts).open(io, allocator, .{ .path = repo_path }) catch |err| switch (err) { │ │186 - error.RepoNotFound => { │ │187 - if (!create_if_missing) return err; │ │227 │ │189 - var repo = try openRepo(repo_kind, any_repo_opts.toRepoOpts(), io, allocator, repo_path, true); │ │190 - defer repo.deinit(io, allocator); │ │191 - │ │192 - try repo.httpBackend(io, allocator, &body_reader, http_server.out, .http, http_backend_options); │ │193 - return; │ │194 - }, │ │195 - else => |e| return e, │ │196 - }; │ │197 - defer any_repo.deinit(io, allocator); │ │198 - │ │199 - switch (any_repo) { │ │200 - inline else => |*repo| { │ │201 - try repo.httpBackend(io, allocator, &body_reader, http_server.out, .http, http_backend_options); │ │202 - }, │ │203 - } │ │204 - } │ │228 + try openRepoAndServe(repo_kind, any_repo_opts, io, allocator, repo_path, create_if_missing, HttpGitService{ │ │229 + .body_reader = &body_reader, │ │230 + .writer = http_server.out, │ │231 + .options = http_backend_options, │ │232 + }); │ │233 } │ │234 │ │235 fn findRoute(path: []const u8) ?struct { xit.net_server_http_backend.HandlerKind, []const u8 } { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │262 io: std.Io, │ │263 allocator: std.mem.Allocator, │ │264 repo_root: []const u8, │ │237 - ssh_listen: []const u8, │ │265 + net_server: *std.Io.net.Server, │ │266 + tasks: *std.Io.Group, │ │267 err: *std.Io.Writer, │ │239 -) !void { │ │240 - const listen_address = try parseListenAddress(ssh_listen); │ │241 - const address = try std.Io.net.IpAddress.parseIp4(listen_address.host, listen_address.port); │ │242 - var net_server = try address.listen(io, .{ .reuse_address = true }); │ │243 - defer net_server.deinit(io); │ │268 +) void { │ │269 + const Listener = struct { │ │270 + io: std.Io, │ │271 + allocator: std.mem.Allocator, │ │272 + repo_root: []const u8, │ │273 + net_server: *std.Io.net.Server, │ │274 + tasks: *std.Io.Group, │ │275 + err: *std.Io.Writer, │ │276 + │ │277 + fn run(ctx: @This()) void { │ │278 + while (true) { │ │279 + const stream = ctx.net_server.accept(ctx.io) catch |accept_err| { │ │280 + logError(ctx.err, "ssh accept failed: {s}\n", .{@errorName(accept_err)}); │ │281 + continue; │ │282 + }; │ │283 │ │245 - try err.print("serving SSH helper connections on {s}, repo root {s}\n", .{ ssh_listen, repo_root }); │ │246 - try err.flush(); │ │284 + const Connection = struct { │ │285 + io: std.Io, │ │286 + allocator: std.mem.Allocator, │ │287 + repo_root: []const u8, │ │288 + stream: std.Io.net.Stream, │ │289 + err: *std.Io.Writer, │ │290 + │ │291 + fn run(conn: @This()) void { │ │292 + defer conn.stream.close(conn.io); │ │293 + handleSshGitConnection(repo_kind, any_repo_opts, conn.io, conn.allocator, conn.repo_root, conn.stream) catch |request_err| {│ │294 + logError(conn.err, "ssh request failed: {s}\n", .{@errorName(request_err)}); │ │295 + }; │ │296 + } │ │297 + }; │ │298 │ │248 - while (true) { │ │249 - const stream = net_server.accept(io) catch |accept_err| { │ │250 - try err.print("ssh accept failed: {s}\n", .{@errorName(accept_err)}); │ │251 - continue; │ │252 - }; │ │253 - defer stream.close(io); │ │299 + ctx.tasks.async(ctx.io, Connection.run, .{Connection{ │ │300 + .io = ctx.io, │ │301 + .allocator = ctx.allocator, │ │302 + .repo_root = ctx.repo_root, │ │303 + .stream = stream, │ │304 + .err = ctx.err, │ │305 + }}); │ │306 + } │ │307 + } │ │308 + }; │ │309 │ │255 - handleSshConnection(repo_kind, any_repo_opts, io, allocator, repo_root, stream) catch |request_err| { │ │256 - try err.print("ssh request failed: {s}\n", .{@errorName(request_err)}); │ │257 - try err.flush(); │ │258 - }; │ │259 - } │ │310 + tasks.async(io, Listener.run, .{Listener{ │ │311 + .io = io, │ │312 + .allocator = allocator, │ │313 + .repo_root = repo_root, │ │314 + .net_server = net_server, │ │315 + .tasks = tasks, │ │316 + .err = err, │ │317 + }}); │ │318 } │ │319 │ │262 -fn handleSshConnection( │ │320 +fn handleSshGitConnection( │ │321 comptime repo_kind: rp.RepoKind, │ │322 comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), │ │323 io: std.Io, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │337 defer allocator.free(repo_path); │ │338 │ │339 if (!isSubPath(repo_root, repo_path)) return error.Forbidden; │ │340 + │ │341 + try openRepoAndServe(repo_kind, any_repo_opts, io, allocator, repo_path, request.service == .receive_pack, SshGitService{ │ │342 + .reader = &reader.interface, │ │343 + .writer = &writer.interface, │ │344 + .request = request, │ │345 + }); │ │346 +} │ │347 │ │348 +fn openRepoAndServe( │ │349 + comptime repo_kind: rp.RepoKind, │ │350 + comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), │ │351 + io: std.Io, │ │352 + allocator: std.mem.Allocator, │ │353 + repo_path: []const u8, │ │354 + create_if_missing: bool, │ │355 + service: anytype, │ │356 +) !void { │ │357 if (any_repo_opts.hash) |hash_kind| { │ │284 - var repo = try openRepo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), io, allocator, repo_path, request.service == .receive_pack); │ │358 + var repo = try openRepo(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), io, allocator, repo_path, create_if_missing); │ │359 defer repo.deinit(io, allocator); │ │286 - try runSshService(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), &repo, io, allocator, &reader.interface, &writer.interface, request);│ │360 + try service.serve(repo_kind, any_repo_opts.toRepoOptsWithHash(hash_kind), &repo, io, allocator); │ │361 } else { │ │362 var any_repo = rp.AnyRepo(repo_kind, any_repo_opts).open(io, allocator, .{ .path = repo_path }) catch |open_err| switch (open_err) { │ │363 error.RepoNotFound => { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │290 - if (request.service != .receive_pack) return open_err; │ │364 + if (!create_if_missing) return open_err; │ │365 │ │366 var repo = try openRepo(repo_kind, any_repo_opts.toRepoOpts(), io, allocator, repo_path, true);│ │367 defer repo.deinit(io, allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │294 - try runSshService(repo_kind, any_repo_opts.toRepoOpts(), &repo, io, allocator, &reader.interface, &writer.interface, request);│ │368 + try service.serve(repo_kind, any_repo_opts.toRepoOpts(), &repo, io, allocator); │ │369 return; │ │370 }, │ │371 else => |e| return e, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │372 }; │ │373 defer any_repo.deinit(io, allocator); │ │374 + │ │375 switch (any_repo) { │ │376 inline else => |*repo| { │ │302 - try runSshService(repo.self_repo_kind, repo.self_repo_opts, repo, io, allocator, &reader.interface, &writer.interface, request);│ │377 + try service.serve(repo.self_repo_kind, repo.self_repo_opts, repo, io, allocator); │ │378 }, │ │379 } │ │380 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │381 } │ │382 │ │308 -fn runSshService( │ │309 - comptime repo_kind: rp.RepoKind, │ │310 - comptime repo_opts: rp.RepoOpts(repo_kind), │ │311 - repo: *rp.Repo(repo_kind, repo_opts), │ │312 - io: std.Io, │ │313 - allocator: std.mem.Allocator, │ │314 - reader: *std.Io.Reader, │ │315 - writer: *std.Io.Writer, │ │316 - request: SshRequest, │ │317 -) !void { │ │318 - switch (request.service) { │ │319 - .upload_pack => try repo.uploadPack(io, allocator, reader, writer, .{ │ │320 - .protocol_version = request.protocol_version, │ │321 - }), │ │322 - .receive_pack => try repo.receivePack(io, allocator, reader, writer, .{│ │323 - .protocol_version = request.protocol_version, │ │324 - }), │ │325 - } │ │326 -} │ │327 - │ │383 fn openRepo( │ │384 comptime repo_kind: rp.RepoKind, │ │385 comptime repo_opts: rp.RepoOpts(repo_kind), │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯