authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-05 03:22:04-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-05 15:02:00+02:00
log8bd0af5eb97c8b76e519f4d61d391b04d99658eb
tree6c4d5e5a6367f7e55e84ecf9a56cbaca9edbe0a8
parent9292ded5a3b8c82ae731bb18d40c69b7e40177f8

std.http.Client.receiveHead: avoid poisioning pool

closes #30165

2 files changed, 70 insertions(+), 6 deletions(-)

lib/std/http/Client.zig+10-6
...@@ -1133,7 +1133,16 @@ pub const Request = struct {...@@ -1133,7 +1133,16 @@ pub const Request = struct {
1133 pub fn receiveHead(r: *Request, redirect_buffer: []u8) ReceiveHeadError!Response {1133 pub fn receiveHead(r: *Request, redirect_buffer: []u8) ReceiveHeadError!Response {
1134 var aux_buf = redirect_buffer;1134 var aux_buf = redirect_buffer;
1135 while (true) {1135 while (true) {
1136 const head_buffer = try r.reader.receiveHead();1136 // This while loop is for handling redirects, which means the request's
1137 // connection may be different than the previous iteration. However, it
1138 // is still guaranteed to be non-null with each iteration of this loop.
1139 const connection = r.connection.?;
1140
1141 const head_buffer = r.reader.receiveHead() catch |err| {
1142 // Failure here means the connection can no longer be reused.
1143 connection.closing = true;
1144 return err;
1145 };
1137 const response: Response = .{1146 const response: Response = .{
1138 .request = r,1147 .request = r,
1139 .head = Response.Head.parse(head_buffer) catch return error.HttpHeadersInvalid,1148 .head = Response.Head.parse(head_buffer) catch return error.HttpHeadersInvalid,
...@@ -1147,11 +1156,6 @@ pub const Request = struct {...@@ -1147,11 +1156,6 @@ pub const Request = struct {
1147 return response; // we're not handling the 100-continue1156 return response; // we're not handling the 100-continue
1148 }1157 }
11491158
1150 // This while loop is for handling redirects, which means the request's
1151 // connection may be different than the previous iteration. However, it
1152 // is still guaranteed to be non-null with each iteration of this loop.
1153 const connection = r.connection.?;
1154
1155 if (r.method == .CONNECT and head.status.class() == .success) {1159 if (r.method == .CONNECT and head.status.class() == .success) {
1156 // This connection is no longer doing HTTP.1160 // This connection is no longer doing HTTP.
1157 connection.closing = false;1161 connection.closing = false;
lib/std/http/test.zig+60
...@@ -1256,3 +1256,63 @@ test "redirect to different connection" {...@@ -1256,3 +1256,63 @@ test "redirect to different connection" {
1256 try expectEqualStrings("good job, you pass", body);1256 try expectEqualStrings("good job, you pass", body);
1257 }1257 }
1258}1258}
1259
1260test "boot failed connections from the pool" {
1261 if (builtin.cpu.arch.isPowerPC64() and builtin.mode != .Debug) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/171879
1262 if (builtin.os.tag == .openbsd) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/30806
1263
1264 const io = std.testing.io;
1265 const gpa = std.testing.allocator;
1266
1267 const test_server_orig = try createTestServer(io, struct {
1268 fn run(test_server: *TestServer) anyerror!void {
1269 const net_server = &test_server.net_server;
1270 var recv_buffer: [500]u8 = undefined;
1271 var send_buffer: [500]u8 = undefined;
1272
1273 accept: while (!test_server.shutting_down) {
1274 var stream = try net_server.accept(io);
1275 defer stream.close(io);
1276
1277 for (0..2) |i| {
1278 var connection_br = stream.reader(io, &recv_buffer);
1279 var connection_bw = stream.writer(io, &send_buffer);
1280 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
1281 var request = server.receiveHead() catch |err| switch (err) {
1282 error.HttpConnectionClosing => continue :accept,
1283 else => |e| return e,
1284 };
1285 if (i == 0) try request.respond("hello", .{});
1286 }
1287 }
1288 }
1289 });
1290 defer test_server_orig.destroy();
1291
1292 var client: http.Client = .{
1293 .allocator = gpa,
1294 .io = io,
1295 };
1296 defer client.deinit();
1297
1298 var loc_buf: [100]u8 = undefined;
1299 const location = try std.fmt.bufPrint(&loc_buf, "http://127.0.0.1:{d}/", .{
1300 test_server_orig.port(),
1301 });
1302 const uri = try std.Uri.parse(location);
1303
1304 {
1305 const response = try client.fetch(.{ .location = .{ .uri = uri } });
1306 try expectEqual(.ok, response.status);
1307 }
1308 {
1309 try expectError(error.HttpConnectionClosing, client.fetch(.{ .location = .{ .uri = uri } }));
1310 }
1311 {
1312 const response = try client.fetch(.{ .location = .{ .uri = uri } });
1313 try expectEqual(.ok, response.status);
1314 }
1315 {
1316 try expectError(error.HttpConnectionClosing, client.fetch(.{ .location = .{ .uri = uri } }));
1317 }
1318}