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 {...@@ -393,12 +393,18 @@ pub const Reader = struct {
393 ReadFailed,393 ReadFailed,
394 };394 };
395395
396 pub fn restituteHeadBuffer(reader: *Reader) void {
397 reader.in.restitute(reader.head_buffer.len);
398 reader.head_buffer.len = 0;
399 }
400
396 /// Buffers the entire head into `head_buffer`, invalidating the previous401 /// Buffers the entire head into `head_buffer`, invalidating the previous
397 /// `head_buffer`, if any.402 /// `head_buffer`, if any.
398 pub fn receiveHead(reader: *Reader) HeadError!void {403 pub fn receiveHead(reader: *Reader) HeadError!void {
399 reader.trailers = &.{};404 reader.trailers = &.{};
400 const in = reader.in;405 const in = reader.in;
401 in.restitute(reader.head_buffer.len);406 in.restitute(reader.head_buffer.len);
407 reader.head_buffer.len = 0;
402 in.rebase();408 in.rebase();
403 var hp: HeadParser = .{};409 var hp: HeadParser = .{};
404 var head_end: usize = 0;410 var head_end: usize = 0;
lib/std/http/Client.zig+18-9
...@@ -117,14 +117,12 @@ pub const ConnectionPool = struct {...@@ -117,14 +117,12 @@ pub const ConnectionPool = struct {
117 ///117 ///
118 /// Threadsafe.118 /// Threadsafe.
119 pub fn release(pool: *ConnectionPool, connection: *Connection) void {119 pub fn release(pool: *ConnectionPool, connection: *Connection) void {
120 if (connection.closing) return connection.destroy();
121
122 pool.mutex.lock();120 pool.mutex.lock();
123 defer pool.mutex.unlock();121 defer pool.mutex.unlock();
124122
125 pool.used.remove(&connection.pool_node);123 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
129 if (pool.free_len >= pool.free_size) {127 if (pool.free_len >= pool.free_size) {
130 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);128 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);
...@@ -669,8 +667,10 @@ pub const Response = struct {...@@ -669,8 +667,10 @@ pub const Response = struct {
669 /// See also:667 /// See also:
670 /// * `readerDecompressing`668 /// * `readerDecompressing`
671 pub fn reader(response: *Response) std.io.Reader {669 pub fn reader(response: *Response) std.io.Reader {
670 const req = response.request;
671 if (!req.method.responseHasBody()) return .ending;
672 const head = &response.head;672 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);
674 }674 }
675675
676 /// If compressed body has been negotiated this will return decompressed bytes.676 /// If compressed body has been negotiated this will return decompressed bytes.
...@@ -805,11 +805,13 @@ pub const Request = struct {...@@ -805,11 +805,13 @@ pub const Request = struct {
805805
806 /// Returns the request's `Connection` back to the pool of the `Client`.806 /// Returns the request's `Connection` back to the pool of the `Client`.
807 pub fn deinit(r: *Request) void {807 pub fn deinit(r: *Request) void {
808 r.reader.restituteHeadBuffer();
808 if (r.connection) |connection| {809 if (r.connection) |connection| {
809 if (r.reader.state != .ready) {810 connection.closing = connection.closing or switch (r.reader.state) {
810 // Connection cannot be reused.811 .ready => false,
811 connection.closing = true;812 .received_head => r.method.requestHasBody(),
812 }813 else => true,
814 };
813 r.client.connection_pool.release(connection);815 r.client.connection_pool.release(connection);
814 }816 }
815 r.* = undefined;817 r.* = undefined;
...@@ -1025,7 +1027,14 @@ pub const Request = struct {...@@ -1025,7 +1027,14 @@ pub const Request = struct {
1025 }1027 }
10261028
1027 if (head.status.class() == .redirect and r.redirect_behavior != .unhandled) {1029 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 }
1029 try r.redirect(head, &aux_buf);1038 try r.redirect(head, &aux_buf);
1030 try r.sendBodiless();1039 try r.sendBodiless();
1031 continue;1040 continue;
lib/std/http/Server.zig+5
...@@ -30,6 +30,10 @@ pub fn init(in: *std.io.BufferedReader, out: *std.io.BufferedWriter) Server {...@@ -30,6 +30,10 @@ pub fn init(in: *std.io.BufferedReader, out: *std.io.BufferedWriter) Server {
30 };30 };
31}31}
3232
33pub fn deinit(s: *Server) void {
34 s.reader.restituteHeadBuffer();
35}
36
33pub const ReceiveHeadError = http.Reader.HeadError || error{37pub const ReceiveHeadError = http.Reader.HeadError || error{
34 /// Client sent headers that did not conform to the HTTP protocol.38 /// Client sent headers that did not conform to the HTTP protocol.
35 ///39 ///
...@@ -483,6 +487,7 @@ pub const Request = struct {...@@ -483,6 +487,7 @@ pub const Request = struct {
483 return error.HttpExpectationFailed;487 return error.HttpExpectationFailed;
484 }488 }
485 }489 }
490 if (!request.head.method.requestHasBody()) return .ending;
486 return request.server.reader.bodyReader(request.head.transfer_encoding, request.head.content_length);491 return request.server.reader.bodyReader(request.head.transfer_encoding, request.head.content_length);
487 }492 }
488493
lib/std/http/test.zig+33-37
...@@ -10,7 +10,8 @@ const expectError = std.testing.expectError;...@@ -10,7 +10,8 @@ const expectError = std.testing.expectError;
1010
11test "trailers" {11test "trailers" {
12 const test_server = try createTestServer(struct {12 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;
14 var recv_buffer: [1024]u8 = undefined;15 var recv_buffer: [1024]u8 = undefined;
15 var send_buffer: [1024]u8 = undefined;16 var send_buffer: [1024]u8 = undefined;
16 var remaining: usize = 1;17 var remaining: usize = 1;
...@@ -96,7 +97,8 @@ test "trailers" {...@@ -96,7 +97,8 @@ test "trailers" {
9697
97test "HTTP server handles a chunked transfer coding request" {98test "HTTP server handles a chunked transfer coding request" {
98 const test_server = try createTestServer(struct {99 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;
100 var recv_buffer: [8192]u8 = undefined;102 var recv_buffer: [8192]u8 = undefined;
101 var send_buffer: [500]u8 = undefined;103 var send_buffer: [500]u8 = undefined;
102 const connection = try net_server.accept();104 const connection = try net_server.accept();
...@@ -162,11 +164,12 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -162,11 +164,12 @@ test "HTTP server handles a chunked transfer coding request" {
162164
163test "echo content server" {165test "echo content server" {
164 const test_server = try createTestServer(struct {166 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;
166 var recv_buffer: [1024]u8 = undefined;169 var recv_buffer: [1024]u8 = undefined;
167 var send_buffer: [100]u8 = undefined;170 var send_buffer: [100]u8 = undefined;
168171
169 accept: while (true) {172 accept: while (!test_server.shutting_down) {
170 const connection = try net_server.accept();173 const connection = try net_server.accept();
171 defer connection.stream.close();174 defer connection.stream.close();
172175
...@@ -251,7 +254,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -251,7 +254,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
251 // In this case, the response is expected to stream until the connection is254 // In this case, the response is expected to stream until the connection is
252 // closed, indicating the end of the body.255 // closed, indicating the end of the body.
253 const test_server = try createTestServer(struct {256 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;
255 var recv_buffer: [1000]u8 = undefined;259 var recv_buffer: [1000]u8 = undefined;
256 var send_buffer: [500]u8 = undefined;260 var send_buffer: [500]u8 = undefined;
257 var remaining: usize = 1;261 var remaining: usize = 1;
...@@ -279,6 +283,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -279,6 +283,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
279 try bw.print("{d}, ah ha ha!\n", .{i});283 try bw.print("{d}, ah ha ha!\n", .{i});
280 }284 }
281 try expectEqual(7390, bw.count);285 try expectEqual(7390, bw.count);
286 try bw.flush();
282 try response.end();287 try response.end();
283 try expectEqual(.closing, server.reader.state);288 try expectEqual(.closing, server.reader.state);
284 }289 }
...@@ -319,7 +324,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -319,7 +324,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
319324
320test "receiving arbitrary http headers from the client" {325test "receiving arbitrary http headers from the client" {
321 const test_server = try createTestServer(struct {326 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;
323 var recv_buffer: [666]u8 = undefined;329 var recv_buffer: [666]u8 = undefined;
324 var send_buffer: [777]u8 = undefined;330 var send_buffer: [777]u8 = undefined;
325 var remaining: usize = 1;331 var remaining: usize = 1;
...@@ -385,15 +391,13 @@ test "general client/server API coverage" {...@@ -385,15 +391,13 @@ test "general client/server API coverage" {
385 return error.SkipZigTest;391 return error.SkipZigTest;
386 }392 }
387393
388 const global = struct {
389 var handle_new_requests = true;
390 };
391 const test_server = try createTestServer(struct {394 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;
393 var recv_buffer: [1024]u8 = undefined;397 var recv_buffer: [1024]u8 = undefined;
394 var send_buffer: [100]u8 = undefined;398 var send_buffer: [100]u8 = undefined;
395399
396 outer: while (global.handle_new_requests) {400 outer: while (!test_server.shutting_down) {
397 var connection = try net_server.accept();401 var connection = try net_server.accept();
398 defer connection.stream.close();402 defer connection.stream.close();
399403
...@@ -544,17 +548,13 @@ test "general client/server API coverage" {...@@ -544,17 +548,13 @@ test "general client/server API coverage" {
544 return s.listen_address.in.getPort();548 return s.listen_address.in.getPort();
545 }549 }
546 });550 });
547 defer {551 defer test_server.destroy();
548 global.handle_new_requests = false;
549 test_server.destroy();
550 }
551552
552 const log = std.log.scoped(.client);553 const log = std.log.scoped(.client);
553554
554 const gpa = std.testing.allocator;555 const gpa = std.testing.allocator;
555 var client: http.Client = .{ .allocator = gpa };556 var client: http.Client = .{ .allocator = gpa };
556 errdefer client.deinit();557 defer client.deinit();
557 // defer client.deinit(); handled below
558558
559 const port = test_server.port();559 const port = test_server.port();
560560
...@@ -870,20 +870,12 @@ test "general client/server API coverage" {...@@ -870,20 +870,12 @@ test "general client/server API coverage" {
870870
871 // connection has been kept alive871 // connection has been kept alive
872 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);872 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 }
882}873}
883874
884test "Server streams both reading and writing" {875test "Server streams both reading and writing" {
885 const test_server = try createTestServer(struct {876 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;
887 var recv_buffer: [1024]u8 = undefined;879 var recv_buffer: [1024]u8 = undefined;
888 var send_buffer: [777]u8 = undefined;880 var send_buffer: [777]u8 = undefined;
889881
...@@ -1091,19 +1083,18 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -1091,19 +1083,18 @@ fn echoTests(client: *http.Client, port: u16) !void {
1091 try expectEqual(.expectation_failed, response.head.status);1083 try expectEqual(.expectation_failed, response.head.status);
1092 _ = try response.reader().discardRemaining();1084 _ = try response.reader().discardRemaining();
1093 }1085 }
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 });
1100}1086}
11011087
1102const TestServer = struct {1088const TestServer = struct {
1089 shutting_down: bool,
1103 server_thread: std.Thread,1090 server_thread: std.Thread,
1104 net_server: std.net.Server,1091 net_server: std.net.Server,
11051092
1106 fn destroy(self: *@This()) void {1093 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
1107 self.server_thread.join();1098 self.server_thread.join();
1108 self.net_server.deinit();1099 self.net_server.deinit();
1109 std.testing.allocator.destroy(self);1100 std.testing.allocator.destroy(self);
...@@ -1123,14 +1114,18 @@ fn createTestServer(S: type) !*TestServer {...@@ -1123,14 +1114,18 @@ fn createTestServer(S: type) !*TestServer {
11231114
1124 const address = try std.net.Address.parseIp("127.0.0.1", 0);1115 const address = try std.net.Address.parseIp("127.0.0.1", 0);
1125 const test_server = try std.testing.allocator.create(TestServer);1116 const test_server = try std.testing.allocator.create(TestServer);
1126 test_server.net_server = try address.listen(.{ .reuse_address = true });1117 test_server.* = .{
1127 test_server.server_thread = try std.Thread.spawn(.{}, S.run, .{&test_server.net_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 };
1128 return test_server;1122 return test_server;
1129}1123}
11301124
1131test "redirect to different connection" {1125test "redirect to different connection" {
1132 const test_server_new = try createTestServer(struct {1126 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;
1134 var recv_buffer: [888]u8 = undefined;1129 var recv_buffer: [888]u8 = undefined;
1135 var send_buffer: [777]u8 = undefined;1130 var send_buffer: [777]u8 = undefined;
11361131
...@@ -1155,7 +1150,8 @@ test "redirect to different connection" {...@@ -1155,7 +1150,8 @@ test "redirect to different connection" {
1155 global.other_port = test_server_new.port();1150 global.other_port = test_server_new.port();
11561151
1157 const test_server_orig = try createTestServer(struct {1152 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;
1159 var recv_buffer: [999]u8 = undefined;1155 var recv_buffer: [999]u8 = undefined;
1160 var send_buffer: [100]u8 = undefined;1156 var send_buffer: [100]u8 = undefined;
11611157