authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-05 17:06:15-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
logd0b839285285c53da93dad0c81150dbdc18ead40
tree62a2e4c346b1511d46cd836e8b207a9542e74ec6
parentaf7721d20fc2330c0f4e7aa42df606254b06516f

std.http fixes


4 files changed, 62 insertions(+), 46 deletions(-)

lib/std/http.zig+6
......@@ -393,12 +393,18 @@ pub const Reader = struct {
393393 ReadFailed,
394394 };
395395
396 pub fn restituteHeadBuffer(reader: *Reader) void {
397 reader.in.restitute(reader.head_buffer.len);
398 reader.head_buffer.len = 0;
399 }
400
396401 /// Buffers the entire head into `head_buffer`, invalidating the previous
397402 /// `head_buffer`, if any.
398403 pub fn receiveHead(reader: *Reader) HeadError!void {
399404 reader.trailers = &.{};
400405 const in = reader.in;
401406 in.restitute(reader.head_buffer.len);
407 reader.head_buffer.len = 0;
402408 in.rebase();
403409 var hp: HeadParser = .{};
404410 var head_end: usize = 0;
lib/std/http/Client.zig+18-9
......@@ -117,14 +117,12 @@ pub const ConnectionPool = struct {
117117 ///
118118 /// Threadsafe.
119119 pub fn release(pool: *ConnectionPool, connection: *Connection) void {
120 if (connection.closing) return connection.destroy();
121
122120 pool.mutex.lock();
123121 defer pool.mutex.unlock();
124122
125123 pool.used.remove(&connection.pool_node);
126124
127 if (pool.free_size == 0) return connection.destroy();
125 if (connection.closing or pool.free_size == 0) return connection.destroy();
128126
129127 if (pool.free_len >= pool.free_size) {
130128 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);
......@@ -669,8 +667,10 @@ pub const Response = struct {
669667 /// See also:
670668 /// * `readerDecompressing`
671669 pub fn reader(response: *Response) std.io.Reader {
670 const req = response.request;
671 if (!req.method.responseHasBody()) return .ending;
672672 const head = &response.head;
673 return response.request.reader.bodyReader(head.transfer_encoding, head.content_length);
673 return req.reader.bodyReader(head.transfer_encoding, head.content_length);
674674 }
675675
676676 /// If compressed body has been negotiated this will return decompressed bytes.
......@@ -805,11 +805,13 @@ pub const Request = struct {
805805
806806 /// Returns the request's `Connection` back to the pool of the `Client`.
807807 pub fn deinit(r: *Request) void {
808 r.reader.restituteHeadBuffer();
808809 if (r.connection) |connection| {
809 if (r.reader.state != .ready) {
810 // Connection cannot be reused.
811 connection.closing = true;
812 }
810 connection.closing = connection.closing or switch (r.reader.state) {
811 .ready => false,
812 .received_head => r.method.requestHasBody(),
813 else => true,
814 };
813815 r.client.connection_pool.release(connection);
814816 }
815817 r.* = undefined;
......@@ -1025,7 +1027,14 @@ pub const Request = struct {
10251027 }
10261028
10271029 if (head.status.class() == .redirect and r.redirect_behavior != .unhandled) {
1028 if (r.redirect_behavior == .not_allowed) return error.TooManyHttpRedirects;
1030 if (r.redirect_behavior == .not_allowed) {
1031 // Connection can still be reused by skipping the body.
1032 var reader = r.reader.bodyReader(head.transfer_encoding, head.content_length);
1033 _ = reader.discardRemaining() catch |err| switch (err) {
1034 error.ReadFailed => connection.closing = true,
1035 };
1036 return error.TooManyHttpRedirects;
1037 }
10291038 try r.redirect(head, &aux_buf);
10301039 try r.sendBodiless();
10311040 continue;
lib/std/http/Server.zig+5
......@@ -30,6 +30,10 @@ pub fn init(in: *std.io.BufferedReader, out: *std.io.BufferedWriter) Server {
3030 };
3131}
3232
33pub fn deinit(s: *Server) void {
34 s.reader.restituteHeadBuffer();
35}
36
3337pub const ReceiveHeadError = http.Reader.HeadError || error{
3438 /// Client sent headers that did not conform to the HTTP protocol.
3539 ///
......@@ -483,6 +487,7 @@ pub const Request = struct {
483487 return error.HttpExpectationFailed;
484488 }
485489 }
490 if (!request.head.method.requestHasBody()) return .ending;
486491 return request.server.reader.bodyReader(request.head.transfer_encoding, request.head.content_length);
487492 }
488493
lib/std/http/test.zig+33-37
......@@ -10,7 +10,8 @@ const expectError = std.testing.expectError;
1010
1111test "trailers" {
1212 const test_server = try createTestServer(struct {
13 fn run(net_server: *std.net.Server) anyerror!void {
13 fn run(test_server: *TestServer) anyerror!void {
14 const net_server = &test_server.net_server;
1415 var recv_buffer: [1024]u8 = undefined;
1516 var send_buffer: [1024]u8 = undefined;
1617 var remaining: usize = 1;
......@@ -96,7 +97,8 @@ test "trailers" {
9697
9798test "HTTP server handles a chunked transfer coding request" {
9899 const test_server = try createTestServer(struct {
99 fn run(net_server: *std.net.Server) !void {
100 fn run(test_server: *TestServer) anyerror!void {
101 const net_server = &test_server.net_server;
100102 var recv_buffer: [8192]u8 = undefined;
101103 var send_buffer: [500]u8 = undefined;
102104 const connection = try net_server.accept();
......@@ -162,11 +164,12 @@ test "HTTP server handles a chunked transfer coding request" {
162164
163165test "echo content server" {
164166 const test_server = try createTestServer(struct {
165 fn run(net_server: *std.net.Server) anyerror!void {
167 fn run(test_server: *TestServer) anyerror!void {
168 const net_server = &test_server.net_server;
166169 var recv_buffer: [1024]u8 = undefined;
167170 var send_buffer: [100]u8 = undefined;
168171
169 accept: while (true) {
172 accept: while (!test_server.shutting_down) {
170173 const connection = try net_server.accept();
171174 defer connection.stream.close();
172175
......@@ -251,7 +254,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
251254 // In this case, the response is expected to stream until the connection is
252255 // closed, indicating the end of the body.
253256 const test_server = try createTestServer(struct {
254 fn run(net_server: *std.net.Server) anyerror!void {
257 fn run(test_server: *TestServer) anyerror!void {
258 const net_server = &test_server.net_server;
255259 var recv_buffer: [1000]u8 = undefined;
256260 var send_buffer: [500]u8 = undefined;
257261 var remaining: usize = 1;
......@@ -279,6 +283,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
279283 try bw.print("{d}, ah ha ha!\n", .{i});
280284 }
281285 try expectEqual(7390, bw.count);
286 try bw.flush();
282287 try response.end();
283288 try expectEqual(.closing, server.reader.state);
284289 }
......@@ -319,7 +324,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
319324
320325test "receiving arbitrary http headers from the client" {
321326 const test_server = try createTestServer(struct {
322 fn run(net_server: *std.net.Server) anyerror!void {
327 fn run(test_server: *TestServer) anyerror!void {
328 const net_server = &test_server.net_server;
323329 var recv_buffer: [666]u8 = undefined;
324330 var send_buffer: [777]u8 = undefined;
325331 var remaining: usize = 1;
......@@ -385,15 +391,13 @@ test "general client/server API coverage" {
385391 return error.SkipZigTest;
386392 }
387393
388 const global = struct {
389 var handle_new_requests = true;
390 };
391394 const test_server = try createTestServer(struct {
392 fn run(net_server: *std.net.Server) anyerror!void {
395 fn run(test_server: *TestServer) anyerror!void {
396 const net_server = &test_server.net_server;
393397 var recv_buffer: [1024]u8 = undefined;
394398 var send_buffer: [100]u8 = undefined;
395399
396 outer: while (global.handle_new_requests) {
400 outer: while (!test_server.shutting_down) {
397401 var connection = try net_server.accept();
398402 defer connection.stream.close();
399403
......@@ -544,17 +548,13 @@ test "general client/server API coverage" {
544548 return s.listen_address.in.getPort();
545549 }
546550 });
547 defer {
548 global.handle_new_requests = false;
549 test_server.destroy();
550 }
551 defer test_server.destroy();
551552
552553 const log = std.log.scoped(.client);
553554
554555 const gpa = std.testing.allocator;
555556 var client: http.Client = .{ .allocator = gpa };
556 errdefer client.deinit();
557 // defer client.deinit(); handled below
557 defer client.deinit();
558558
559559 const port = test_server.port();
560560
......@@ -870,20 +870,12 @@ test "general client/server API coverage" {
870870
871871 // connection has been kept alive
872872 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
873
874 client.deinit();
875
876 {
877 global.handle_new_requests = false;
878
879 const conn = try std.net.tcpConnectToAddress(test_server.net_server.listen_address);
880 conn.close();
881 }
882873}
883874
884875test "Server streams both reading and writing" {
885876 const test_server = try createTestServer(struct {
886 fn run(net_server: *std.net.Server) anyerror!void {
877 fn run(test_server: *TestServer) anyerror!void {
878 const net_server = &test_server.net_server;
887879 var recv_buffer: [1024]u8 = undefined;
888880 var send_buffer: [777]u8 = undefined;
889881
......@@ -1091,19 +1083,18 @@ fn echoTests(client: *http.Client, port: u16) !void {
10911083 try expectEqual(.expectation_failed, response.head.status);
10921084 _ = try response.reader().discardRemaining();
10931085 }
1094
1095 _ = try client.fetch(.{
1096 .location = .{
1097 .url = try std.fmt.bufPrint(&location_buffer, "http://127.0.0.1:{d}/end", .{port}),
1098 },
1099 });
11001086}
11011087
11021088const TestServer = struct {
1089 shutting_down: bool,
11031090 server_thread: std.Thread,
11041091 net_server: std.net.Server,
11051092
11061093 fn destroy(self: *@This()) void {
1094 self.shutting_down = true;
1095 const conn = std.net.tcpConnectToAddress(self.net_server.listen_address) catch @panic("shutdown failure");
1096 conn.close();
1097
11071098 self.server_thread.join();
11081099 self.net_server.deinit();
11091100 std.testing.allocator.destroy(self);
......@@ -1123,14 +1114,18 @@ fn createTestServer(S: type) !*TestServer {
11231114
11241115 const address = try std.net.Address.parseIp("127.0.0.1", 0);
11251116 const test_server = try std.testing.allocator.create(TestServer);
1126 test_server.net_server = try address.listen(.{ .reuse_address = true });
1127 test_server.server_thread = try std.Thread.spawn(.{}, S.run, .{&test_server.net_server});
1117 test_server.* = .{
1118 .net_server = try address.listen(.{ .reuse_address = true }),
1119 .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),
1120 .shutting_down = false,
1121 };
11281122 return test_server;
11291123}
11301124
11311125test "redirect to different connection" {
11321126 const test_server_new = try createTestServer(struct {
1133 fn run(net_server: *std.net.Server) anyerror!void {
1127 fn run(test_server: *TestServer) anyerror!void {
1128 const net_server = &test_server.net_server;
11341129 var recv_buffer: [888]u8 = undefined;
11351130 var send_buffer: [777]u8 = undefined;
11361131
......@@ -1155,7 +1150,8 @@ test "redirect to different connection" {
11551150 global.other_port = test_server_new.port();
11561151
11571152 const test_server_orig = try createTestServer(struct {
1158 fn run(net_server: *std.net.Server) anyerror!void {
1153 fn run(test_server: *TestServer) anyerror!void {
1154 const net_server = &test_server.net_server;
11591155 var recv_buffer: [999]u8 = undefined;
11601156 var send_buffer: [100]u8 = undefined;
11611157