╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───╮ │src/test.zig│ ╰────────────╯ ╭─────────────────────────────────────────────────────────────╮ │ 98 const Core = switch (transport_def) { │ │ 99 .file => void, │ │100 .wire => |wire_kind| switch (wire_kind) { │ │ 94 - .http => struct { │ │101 + .http => if (use_serve_command) struct {│ │102 + io: std.Io, │ │103 + allocator: std.mem.Allocator, │ │104 + process: ?std.process.Child, │ │105 + } else struct { │ │106 io: std.Io, │ │107 allocator: std.mem.Allocator, │ │108 temp_dir_name: []const u8, │ ╰─────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │121 fn init( │ │122 io: std.Io, │ │123 allocator: std.mem.Allocator, │ │113 - ) !Server(server_repo_kind, transport_def, temp_dir_name, port) { │ │124 + ) !Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command) {│ │125 switch (transport_def) { │ │126 .file => return .{ .core = {} }, │ │127 .wire => |wire_kind| switch (wire_kind) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │128 .http => { │ │129 + if (use_serve_command) { │ │130 + comptime { │ │131 + std.debug.assert(server_repo_kind == .xit); │ │132 + } │ │133 + return .{ │ │134 + .core = .{ │ │135 + .io = io, │ │136 + .allocator = allocator, │ │137 + .process = null, │ │138 + }, │ │139 + }; │ │140 + } │ │141 + │ │142 const address = try std.Io.net.IpAddress.parseIp4("127.0.0.1", port); │ │143 const net_server = try address.listen(io, .{ .reuse_address = true });│ │120 - errdefer net_server.deinit(); │ │144 + errdefer net_server.deinit(io); │ │145 return .{ │ │146 .core = .{ │ │147 .io = io, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │271 } │ │272 } │ │273 │ │250 - fn start(self: *Server(server_repo_kind, transport_def, temp_dir_name, port)) !void { │ │274 + fn start(self: *Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command)) !void {│ │275 switch (transport_def) { │ │276 .file => {}, │ │277 .wire => |wire_kind| switch (wire_kind) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │278 .http => { │ │255 - const ServerHandler = struct { │ │256 - fn run(core: *Core) !void { │ │257 - var send_buffer = [_]u8{0} ** 1024; │ │258 - var recv_buffer = [_]u8{0} ** 1024; │ │279 + if (use_serve_command) { │ │280 + std.debug.assert(self.core.process == null); │ │281 │ │260 - accept: while (true) { │ │261 - const stream = try core.net_server.accept(core.io); │ │262 - defer stream.close(core.io); │ │282 + const cwd_path = try std.process.currentPathAlloc(self.core.io, self.core.allocator); │ │283 + defer self.core.allocator.free(cwd_path); │ │284 + const haxy_path = try std.fs.path.join(self.core.allocator, &.{ cwd_path, "zig-out/bin/haxy" }); │ │285 + defer self.core.allocator.free(haxy_path); │ │286 + const listen_arg = std.fmt.comptimePrint("127.0.0.1:{}", .{port}); │ │287 │ │264 - var conn_br = stream.reader(core.io, &recv_buffer); │ │265 - var conn_bw = stream.writer(core.io, &send_buffer); │ │266 - var http_server = std.http.Server.init(&conn_br.interface, &conn_bw.interface); │ │288 + self.core.process = try std.process.spawn(self.core.io, .{ │ │289 + .argv = &.{ haxy_path, "serve", "--http-listen", listen_arg, "--project-root", temp_dir_name }, │ │290 + .stdin = .ignore, │ │291 + .stdout = .ignore, │ │292 + .stderr = .ignore, │ │293 + }); │ │294 + } else { │ │295 + const ServerHandler = struct { │ │296 + fn run(core: *Core) !void { │ │297 + var send_buffer = [_]u8{0} ** 1024; │ │298 + var recv_buffer = [_]u8{0} ** 1024; │ │299 │ │268 - while (http_server.reader.state == .ready) { │ │269 - var request = http_server.receiveHead() catch |err| switch (err) { │ │270 - error.HttpConnectionClosing => continue :accept, │ │271 - else => |e| return e, │ │272 - }; │ │273 - if (std.mem.eql(u8, request.head.target, "/stop-server")) { │ │274 - break :accept; │ │275 - } │ │300 + accept: while (true) { │ │301 + const stream = try core.net_server.accept(core.io); │ │302 + defer stream.close(core.io); │ │303 │ │277 - const uri = try std.Uri.parseAfterScheme("", request.head.target); │ │278 - if (uri.path.percent_encoded[0] != '/') return error.PathMustStartWithSlash; │ │279 - const path = if (std.mem.indexOfScalar(u8, uri.path.percent_encoded[1..], '/')) |idx| │ │280 - uri.path.percent_encoded[idx + 1 ..] │ │281 - else │ │282 - return error.SlashNotFound; │ │304 + var conn_br = stream.reader(core.io, &recv_buffer); │ │305 + var conn_bw = stream.writer(core.io, &send_buffer); │ │306 + var http_server = std.http.Server.init(&conn_br.interface, &conn_bw.interface); │ │307 │ │284 - const cwd_path = try std.process.currentPathAlloc(core.io, core.allocator); │ │285 - defer core.allocator.free(cwd_path); │ │286 - const temp_dir_path = try std.fs.path.join(core.allocator, &.{ cwd_path, core.temp_dir_name }); │ │287 - defer core.allocator.free(temp_dir_path); │ │288 - const path_translated = try std.fmt.allocPrint(core.allocator, "{s}{s}", .{ │ │289 - temp_dir_path, │ │290 - uri.path.percent_encoded, │ │291 - }); │ │292 - defer core.allocator.free(path_translated); │ │308 + while (http_server.reader.state == .ready) { │ │309 + var request = http_server.receiveHead() catch |err| switch (err) { │ │310 + error.HttpConnectionClosing => continue :accept, │ │311 + else => |e| return e, │ │312 + }; │ │313 + if (std.mem.eql(u8, request.head.target, "/stop-server")) { │ │314 + break :accept; │ │315 + } │ │316 │ │294 - // init env map │ │295 - var env_map = std.process.Environ.Map.init(core.allocator); │ │296 - defer env_map.deinit(); │ │297 - try env_map.put("GATEWAY_INTERFACE", "CGI/1.1"); │ │298 - try env_map.put("REQUEST_METHOD", @tagName(request.head.method)); │ │299 - try env_map.put("PATH_INFO", path); │ │300 - try env_map.put("PATH_TRANSLATED", path_translated); │ │301 - if (uri.query) |query| { │ │302 - try env_map.put("QUERY_STRING", query.percent_encoded); │ │303 - } │ │317 + const uri = try std.Uri.parseAfterScheme("", request.head.target); │ │318 + if (uri.path.percent_encoded[0] != '/') return error.PathMustStartWithSlash; │ │319 + const path = if (std.mem.indexOfScalar(u8, uri.path.percent_encoded[1..], '/')) |idx| │ │320 + uri.path.percent_encoded[idx + 1 ..] │ │321 + else │ │322 + return error.SlashNotFound; │ │323 │ │305 - var accept: std.ArrayList([]const u8) = .empty; │ │306 - defer accept.deinit(core.allocator); │ │324 + const cwd_path = try std.process.currentPathAlloc(core.io, core.allocator); │ │325 + defer core.allocator.free(cwd_path); │ │326 + const temp_dir_path = try std.fs.path.join(core.allocator, &.{ cwd_path, core.temp_dir_name }); │ │327 + defer core.allocator.free(temp_dir_path); │ │328 + const path_translated = try std.fmt.allocPrint(core.allocator, "{s}{s}", .{ │ │329 + temp_dir_path, │ │330 + uri.path.percent_encoded, │ │331 + }); │ │332 + defer core.allocator.free(path_translated); │ │333 │ │308 - var keep_alive = true; // HTTP 1.1 defaults to keep-alive │ │334 + // init env map │ │335 + var env_map = std.process.Environ.Map.init(core.allocator); │ │336 + defer env_map.deinit(); │ │337 + try env_map.put("GATEWAY_INTERFACE", "CGI/1.1"); │ │338 + try env_map.put("REQUEST_METHOD", @tagName(request.head.method)); │ │339 + try env_map.put("PATH_INFO", path); │ │340 + try env_map.put("PATH_TRANSLATED", path_translated); │ │341 + if (uri.query) |query| { │ │342 + try env_map.put("QUERY_STRING", query.percent_encoded); │ │343 + } │ │344 │ │310 - // iterate over headers to fill env map │ │311 - var req_header_it = request.iterateHeaders(); │ │312 - while (req_header_it.next()) |header| { │ │313 - const header_name = header.name; │ │314 - const header_value = header.value; │ │345 + var accept: std.ArrayList([]const u8) = .empty; │ │346 + defer accept.deinit(core.allocator); │ │347 │ │316 - if (std.ascii.eqlIgnoreCase(header_name, "content-type")) { │ │317 - try env_map.put("CONTENT_TYPE", header_value); │ │318 - } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) { │ │319 - try env_map.put("CONTENT_LENGTH", header_value); │ │320 - } else if (std.ascii.eqlIgnoreCase(header_name, "referer")) { │ │321 - try env_map.put("HTTP_REFERER", header_value); │ │322 - } else if (std.ascii.eqlIgnoreCase(header_name, "accept")) { │ │323 - try accept.append(core.allocator, header_value); │ │324 - } else if (std.ascii.eqlIgnoreCase(header_name, "user-agent")) { │ │325 - try env_map.put("HTTP_USER_AGENT", header_value); │ │326 - } else if (std.ascii.eqlIgnoreCase(header_name, "connection")) { │ │327 - if (std.ascii.eqlIgnoreCase(header_value, "close")) { │ │328 - keep_alive = false; │ │348 + var keep_alive = true; // HTTP 1.1 defaults to keep-alive │ │349 + │ │350 + // iterate over headers to fill env map │ │351 + var req_header_it = request.iterateHeaders(); │ │352 + while (req_header_it.next()) |header| { │ │353 + const header_name = header.name; │ │354 + const header_value = header.value; │ │355 + │ │356 + if (std.ascii.eqlIgnoreCase(header_name, "content-type")) { │ │357 + try env_map.put("CONTENT_TYPE", header_value); │ │358 + } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) { │ │359 + try env_map.put("CONTENT_LENGTH", header_value); │ │360 + } else if (std.ascii.eqlIgnoreCase(header_name, "referer")) { │ │361 + try env_map.put("HTTP_REFERER", header_value); │ │362 + } else if (std.ascii.eqlIgnoreCase(header_name, "accept")) { │ │363 + try accept.append(core.allocator, header_value); │ │364 + } else if (std.ascii.eqlIgnoreCase(header_name, "user-agent")) { │ │365 + try env_map.put("HTTP_USER_AGENT", header_value); │ │366 + } else if (std.ascii.eqlIgnoreCase(header_name, "connection")) { │ │367 + if (std.ascii.eqlIgnoreCase(header_value, "close")) { │ │368 + keep_alive = false; │ │369 + } │ │370 + } else if (std.ascii.eqlIgnoreCase(header_name, "git-protocol")) { │ │371 + try env_map.put("GIT_PROTOCOL", header_value); │ │372 } │ │330 - } else if (std.ascii.eqlIgnoreCase(header_name, "git-protocol")) { │ │331 - try env_map.put("GIT_PROTOCOL", header_value); │ │373 } │ │333 - } │ │374 │ │335 - const accept_str = try std.mem.join(core.allocator, ",", accept.items); │ │336 - defer core.allocator.free(accept_str); │ │337 - if (accept_str.len > 0) { │ │338 - try env_map.put("HTTP_ACCEPT", accept_str); │ │339 - } │ │375 + const accept_str = try std.mem.join(core.allocator, ",", accept.items); │ │376 + defer core.allocator.free(accept_str); │ │377 + if (accept_str.len > 0) { │ │378 + try env_map.put("HTTP_ACCEPT", accept_str); │ │379 + } │ │380 │ │341 - var process = try std.process.spawn(core.io, .{ │ │342 - .argv = switch (server_repo_kind) { │ │343 - .git => &.{ "git", "http-backend" }, │ │344 - .xit => &.{ "../zig-out/bin/haxy", "http-backend" }, │ │345 - }, │ │346 - .cwd = switch (server_repo_kind) { │ │347 - .git => .inherit, │ │348 - .xit => .{ .path = temp_dir_name }, │ │349 - }, │ │350 - .environ_map = &env_map, │ │351 - .stdin = .pipe, │ │352 - .stdout = .pipe, │ │353 - .stderr = .pipe, │ │354 - }); │ │355 - defer process.kill(core.io); │ │381 + var process = try std.process.spawn(core.io, .{ │ │382 + .argv = switch (server_repo_kind) { │ │383 + .git => &.{ "git", "http-backend" }, │ │384 + .xit => &.{ "../zig-out/bin/haxy", "http-backend" }, │ │385 + }, │ │386 + .cwd = switch (server_repo_kind) { │ │387 + .git => .inherit, │ │388 + .xit => .{ .path = temp_dir_name }, │ │389 + }, │ │390 + .environ_map = &env_map, │ │391 + .stdin = .pipe, │ │392 + .stdout = .pipe, │ │393 + .stderr = .pipe, │ │394 + }); │ │395 + defer process.kill(core.io); │ │396 │ │357 - if (request.head.method == .POST) { │ │358 - const reader = try request.readerExpectContinue(&.{}); │ │359 - const request_body = try reader.allocRemaining(core.allocator, .unlimited); │ │360 - defer core.allocator.free(request_body); │ │361 - try process.stdin.?.writeStreamingAll(core.io, request_body); │ │362 - } │ │363 - process.stdin.?.close(core.io); │ │364 - process.stdin = null; │ │397 + if (request.head.method == .POST) { │ │398 + const reader = try request.readerExpectContinue(&.{}); │ │399 + const request_body = try reader.allocRemaining(core.allocator, .unlimited); │ │400 + defer core.allocator.free(request_body); │ │401 + try process.stdin.?.writeStreamingAll(core.io, request_body); │ │402 + } │ │403 + process.stdin.?.close(core.io); │ │404 + process.stdin = null; │ │405 │ │366 - var multi_reader_buffer: std.Io.File.MultiReader.Buffer(2) = undefined; │ │367 - var multi_reader: std.Io.File.MultiReader = undefined; │ │368 - multi_reader.init(core.allocator, core.io, multi_reader_buffer.toStreams(), &.{ process.stdout.?, process.stderr.? }); │ │369 - defer multi_reader.deinit(); │ │406 + var multi_reader_buffer: std.Io.File.MultiReader.Buffer(2) = undefined; │ │407 + var multi_reader: std.Io.File.MultiReader = undefined; │ │408 + multi_reader.init(core.allocator, core.io, multi_reader_buffer.toStreams(), &.{ process.stdout.?, process.stderr.? }); │ │409 + defer multi_reader.deinit(); │ │410 │ │371 - while (multi_reader.fill(64, .none)) |_| {} else |err| switch (err) { │ │372 - error.EndOfStream => {}, │ │373 - else => |e| return e, │ │374 - } │ │411 + while (multi_reader.fill(64, .none)) |_| {} else |err| switch (err) { │ │412 + error.EndOfStream => {}, │ │413 + else => |e| return e, │ │414 + } │ │415 │ │376 - try multi_reader.checkAnyError(); │ │416 + try multi_reader.checkAnyError(); │ │417 │ │378 - _ = try process.wait(core.io); │ │418 + _ = try process.wait(core.io); │ │419 │ │380 - const stdout_slice = try multi_reader.toOwnedSlice(0); │ │381 - defer core.allocator.free(stdout_slice); │ │382 - const stderr_slice = try multi_reader.toOwnedSlice(1); │ │383 - defer core.allocator.free(stderr_slice); │ │420 + const stdout_slice = try multi_reader.toOwnedSlice(0); │ │421 + defer core.allocator.free(stdout_slice); │ │422 + const stderr_slice = try multi_reader.toOwnedSlice(1); │ │423 + defer core.allocator.free(stderr_slice); │ │424 │ │385 - // transition the http state machine so it can │ │386 - // read the next request on this connection │ │387 - if (http_server.reader.state == .received_head) { │ │388 - http_server.reader.state = .ready; │ │389 - } │ │425 + // transition the http state machine so it can │ │426 + // read the next request on this connection │ │427 + if (http_server.reader.state == .received_head) { │ │428 + http_server.reader.state = .ready; │ │429 + } │ │430 │ │391 - if (stderr_slice.len > 0) { │ │392 - std.debug.print("Error from git-http-backend:\n{s}\n", .{stderr_slice}); │ │393 - try http_server.out.writeAll("HTTP/1.1 500 Internal Server Error\r\n\r\n"); │ │394 - } else { │ │395 - try http_server.out.writeAll("HTTP/1.1 200 OK\r\n"); │ │396 - const double_newline = "\r\n\r\n"; │ │397 - const double_newline_idx = std.mem.indexOf(u8, stdout_slice, double_newline) orelse unreachable; │ │398 - try http_server.out.writeAll(stdout_slice[0..double_newline_idx]); │ │399 - try http_server.out.print("\r\nContent-Length: {}", .{stdout_slice.len - (double_newline_idx + double_newline.len)}); │ │400 - try http_server.out.writeAll(stdout_slice[double_newline_idx..]); │ │401 - } │ │402 - try http_server.out.flush(); │ │431 + if (stderr_slice.len > 0) { │ │432 + std.debug.print("Error from git-http-backend:\n{s}\n", .{stderr_slice}); │ │433 + try http_server.out.writeAll("HTTP/1.1 500 Internal Server Error\r\n\r\n"); │ │434 + } else { │ │435 + try http_server.out.writeAll("HTTP/1.1 200 OK\r\n"); │ │436 + const double_newline = "\r\n\r\n"; │ │437 + const double_newline_idx = std.mem.indexOf(u8, stdout_slice, double_newline) orelse unreachable; │ │438 + try http_server.out.writeAll(stdout_slice[0..double_newline_idx]); │ │439 + try http_server.out.print("\r\nContent-Length: {}", .{stdout_slice.len - (double_newline_idx + double_newline.len)});│ │440 + try http_server.out.writeAll(stdout_slice[double_newline_idx..]); │ │441 + } │ │442 + try http_server.out.flush(); │ │443 │ │404 - if (!keep_alive) { │ │405 - continue :accept; │ │444 + if (!keep_alive) { │ │445 + continue :accept; │ │446 + } │ │447 } │ │448 } │ │449 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │409 - } │ │410 - }; │ │411 - self.core.server_thread = try std.Thread.spawn(.{}, ServerHandler.run, .{&self.core}); │ │450 + }; │ │451 + self.core.server_thread = try std.Thread.spawn(.{}, ServerHandler.run, .{&self.core});│ │452 + } │ │453 }, │ │454 .raw => unreachable, │ │455 .ssh => { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │479 } │ │480 } │ │481 │ │441 - fn stop(self: *Server(server_repo_kind, transport_def, temp_dir_name, port)) void { │ │482 + fn stop(self: *Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command)) void {│ │483 switch (transport_def) { │ │484 .file => {}, │ │485 .wire => |wire_kind| switch (wire_kind) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │486 .http => { │ │487 + if (use_serve_command) { │ │488 + if (self.core.process) |*process| { │ │489 + _ = process.kill(self.core.io); │ │490 + self.core.process = null; │ │491 + } │ │492 + return; │ │493 + } │ │494 + │ │495 var client = std.http.Client{ .io = self.core.io, .allocator = self.core.allocator }; │ │496 defer client.deinit(); │ │497 _ = client.fetch(.{ .location = .{ .url = self.core.stop_server_endpoint } }) catch return;│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────╮ │513 comptime server_repo_kind: rp.RepoKind, │ │514 comptime transport_def: net.TransportDefinition,│ │515 comptime port: u16, │ │516 + comptime use_serve_command: bool, │ │517 io: std.Io, │ │518 allocator: std.mem.Allocator, │ │519 ) !void { │ ╰─────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │531 defer temp_dir.close(io); │ │532 │ │533 // init server │ │484 - var server = try Server(server_repo_kind, transport_def, temp_dir_name, port).init(io, allocator); │ │534 + var server = try Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command).init(io, allocator);│ │535 try server.start(); │ │536 defer server.stop(); │ │537 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯