std.http.Client.receiveHead: avoid poisioning pool

closes #30165
This commit is contained in:
Andrew Kelley
2026-04-05 03:22:04 -07:00
parent 9292ded5a3
commit 8bd0af5eb9
2 changed files with 70 additions and 6 deletions
+10 -6
View File
@@ -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;
+60
View File
@@ -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 } }));
}
}