commit 8bd0af5eb97c8b76e519f4d61d391b04d99658eb (tree)
parent 9292ded5a3b8c82ae731bb18d40c69b7e40177f8
Author: Andrew Kelley <andrew@ziglang.org>
Date: Sun, 5 Apr 2026 03:22:04 -0700
std.http.Client.receiveHead: avoid poisioning pool
closes #30165
Diffstat:
2 files changed, 70 insertions(+), 6 deletions(-)
diff --git 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
@@ -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 } }));
+ }
+}