diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index bd4e22895a..660c520d4e 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -1133,7 +1133,16 @@ pub const Request = struct { pub fn receiveHead(r: *Request, redirect_buffer: []u8) ReceiveHeadError!Response { var aux_buf = redirect_buffer; while (true) { - const head_buffer = try r.reader.receiveHead(); + // This while loop is for handling redirects, which means the request's + // connection may be different than the previous iteration. However, it + // is still guaranteed to be non-null with each iteration of this loop. + const connection = r.connection.?; + + const head_buffer = r.reader.receiveHead() catch |err| { + // Failure here means the connection can no longer be reused. + connection.closing = true; + return err; + }; const response: Response = .{ .request = r, .head = Response.Head.parse(head_buffer) catch return error.HttpHeadersInvalid, @@ -1147,11 +1156,6 @@ pub const Request = struct { return response; // we're not handling the 100-continue } - // This while loop is for handling redirects, which means the request's - // connection may be different than the previous iteration. However, it - // is still guaranteed to be non-null with each iteration of this loop. - const connection = r.connection.?; - if (r.method == .CONNECT and head.status.class() == .success) { // This connection is no longer doing HTTP. connection.closing = false; diff --git a/lib/std/http/test.zig b/lib/std/http/test.zig index 0e6130d66a..b061f4b2ac 100644 --- a/lib/std/http/test.zig +++ b/lib/std/http/test.zig @@ -1256,3 +1256,63 @@ test "redirect to different connection" { try expectEqualStrings("good job, you pass", body); } } + +test "boot failed connections from the pool" { + if (builtin.cpu.arch.isPowerPC64() and builtin.mode != .Debug) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/171879 + if (builtin.os.tag == .openbsd) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/30806 + + const io = std.testing.io; + const gpa = std.testing.allocator; + + const test_server_orig = try createTestServer(io, struct { + fn run(test_server: *TestServer) anyerror!void { + const net_server = &test_server.net_server; + var recv_buffer: [500]u8 = undefined; + var send_buffer: [500]u8 = undefined; + + accept: while (!test_server.shutting_down) { + var stream = try net_server.accept(io); + defer stream.close(io); + + for (0..2) |i| { + var connection_br = stream.reader(io, &recv_buffer); + var connection_bw = stream.writer(io, &send_buffer); + var server = http.Server.init(&connection_br.interface, &connection_bw.interface); + var request = server.receiveHead() catch |err| switch (err) { + error.HttpConnectionClosing => continue :accept, + else => |e| return e, + }; + if (i == 0) try request.respond("hello", .{}); + } + } + } + }); + defer test_server_orig.destroy(); + + var client: http.Client = .{ + .allocator = gpa, + .io = io, + }; + defer client.deinit(); + + var loc_buf: [100]u8 = undefined; + const location = try std.fmt.bufPrint(&loc_buf, "http://127.0.0.1:{d}/", .{ + test_server_orig.port(), + }); + const uri = try std.Uri.parse(location); + + { + const response = try client.fetch(.{ .location = .{ .uri = uri } }); + try expectEqual(.ok, response.status); + } + { + try expectError(error.HttpConnectionClosing, client.fetch(.{ .location = .{ .uri = uri } })); + } + { + const response = try client.fetch(.{ .location = .{ .uri = uri } }); + try expectEqual(.ok, response.status); + } + { + try expectError(error.HttpConnectionClosing, client.fetch(.{ .location = .{ .uri = uri } })); + } +}