authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-27 21:04:15-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
logd8cea032455228a12e3413068aff46b06fb02a59
tree83d5ffae07991b7286ab9f7852129cd607cad94f
parentab3a947bef0b167665068ab40232609e4d9b656c

std.http: more update


4 files changed, 99 insertions(+), 90 deletions(-)

lib/std/http.zig+53-10
......@@ -331,9 +331,18 @@ pub const Reader = struct {
331331 body_err: ?BodyError = null,
332332 /// Stolen from `in`.
333333 head_buffer: []u8 = &.{},
334 compression: Compression,
334335
335336 pub const max_chunk_header_len = 22;
336337
338 pub const Compression = union(enum) {
339 deflate: std.compress.zlib.Decompressor,
340 gzip: std.compress.gzip.Decompressor,
341 // https://github.com/ziglang/zig/issues/18937
342 //zstd: std.compress.zstd.Decompressor,
343 none: void,
344 };
345
337346 pub const RemainingChunkLen = enum(u64) {
338347 head = 0,
339348 n = 1,
......@@ -408,13 +417,18 @@ pub const Reader = struct {
408417 }
409418
410419 /// Asserts only called once and after `receiveHead`.
411 pub fn interface(reader: *Reader, transfer_encoding: TransferEncoding, content_length: ?u64) std.io.Reader {
420 pub fn interface(
421 reader: *Reader,
422 transfer_encoding: TransferEncoding,
423 content_length: ?u64,
424 content_encoding: ContentEncoding,
425 ) std.io.Reader {
412426 assert(reader.state == .received_head);
413427 reader.state = .receiving_body;
414 switch (transfer_encoding) {
415 .chunked => {
428 reader.transfer_br.unbuffered_reader = switch (transfer_encoding) {
429 .chunked => r: {
416430 reader.body_state = .{ .remaining_chunk_len = .head };
417 return .{
431 break :r .{
418432 .context = reader,
419433 .vtable = &.{
420434 .read = &chunkedRead,
......@@ -423,10 +437,10 @@ pub const Reader = struct {
423437 },
424438 };
425439 },
426 .none => {
440 .none => r: {
427441 if (content_length) |len| {
428442 reader.body_state = .{ .remaining_content_length = len };
429 return .{
443 break :r .{
430444 .context = reader,
431445 .vtable = &.{
432446 .read = &contentLengthRead,
......@@ -434,10 +448,39 @@ pub const Reader = struct {
434448 .discard = &contentLengthDiscard,
435449 },
436450 };
437 } else {
438 return reader.in.reader();
451 } else switch (content_encoding) {
452 .identity => {
453 reader.compression = .none;
454 return reader.in.reader();
455 },
456 .deflate => {
457 reader.compression = .{ .deflate = .init(reader.in) };
458 return reader.compression.deflate.reader();
459 },
460 .gzip, .@"x-gzip" => {
461 reader.compression = .{ .gzip = .init(reader.in) };
462 return reader.compression.gzip.reader();
463 },
464 .compress, .@"x-compress" => unreachable,
465 .zstd => unreachable, // https://github.com/ziglang/zig/issues/18937
439466 }
440467 },
468 };
469 switch (content_encoding) {
470 .identity => {
471 reader.compression = .none;
472 return reader.transfer_br.unbuffered_reader;
473 },
474 .deflate => {
475 reader.compression = .{ .deflate = .init(&reader.transfer_br) };
476 return reader.compression.deflate.reader();
477 },
478 .gzip, .@"x-gzip" => {
479 reader.compression = .{ .gzip = .init(&reader.transfer_br) };
480 return reader.compression.gzip.reader();
481 },
482 .compress, .@"x-compress" => unreachable,
483 .zstd => unreachable, // https://github.com/ziglang/zig/issues/18937
441484 }
442485 }
443486
......@@ -731,7 +774,7 @@ pub const BodyWriter = struct {
731774 /// BodyWriter is mid-chunk.
732775 pub fn flush(w: *BodyWriter) WriteError!void {
733776 switch (w.state) {
734 .none, .content_length => return w.http_protocol_output.flush(),
777 .end, .none, .content_length => return w.http_protocol_output.flush(),
735778 .chunked => |*chunked| switch (chunked.*) {
736779 .offset => |*offset| {
737780 try w.http_protocol_output.flushLimit(.limited(w.http_protocol_output.end - offset.*));
......@@ -1018,7 +1061,7 @@ pub const BodyWriter = struct {
10181061 }
10191062 }
10201063
1021 pub fn interface(w: *BodyWriter) std.io.Writer {
1064 pub fn writer(w: *BodyWriter) std.io.Writer {
10221065 return .{
10231066 .context = w,
10241067 .vtable = switch (w.state) {
lib/std/http/Client.zig+30-66
......@@ -116,7 +116,7 @@ pub const ConnectionPool = struct {
116116 /// `allocator` must be the same one used to create `connection`.
117117 ///
118118 /// Threadsafe.
119 pub fn release(pool: *ConnectionPool, allocator: Allocator, connection: *Connection) void {
119 pub fn release(pool: *ConnectionPool, connection: *Connection) void {
120120 if (connection.closing) return connection.destroy();
121121
122122 pool.mutex.lock();
......@@ -130,8 +130,7 @@ pub const ConnectionPool = struct {
130130 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);
131131 pool.free_len -= 1;
132132
133 popped.close(allocator);
134 allocator.destroy(popped);
133 popped.destroy();
135134 }
136135
137136 if (connection.proxied) {
......@@ -434,20 +433,6 @@ pub const Connection = struct {
434433 }
435434};
436435
437/// The decompressor for response messages.
438pub const Compression = union(enum) {
439 pub const DeflateDecompressor = std.compress.zlib.Decompressor;
440 pub const GzipDecompressor = std.compress.gzip.Decompressor;
441 // https://github.com/ziglang/zig/issues/18937
442 //pub const ZstdDecompressor = std.compress.zstd.DecompressStream(.{});
443
444 deflate: DeflateDecompressor,
445 gzip: GzipDecompressor,
446 // https://github.com/ziglang/zig/issues/18937
447 //zstd: ZstdDecompressor,
448 none: void,
449};
450
451436pub const Response = struct {
452437 request: *Request,
453438 /// Pointers in this struct are invalidated with the next call to
......@@ -469,9 +454,7 @@ pub const Response = struct {
469454 content_length: ?u64 = null,
470455
471456 transfer_encoding: http.TransferEncoding = .none,
472 transfer_compression: http.ContentEncoding = .identity,
473
474 compression: Compression = .none,
457 content_encoding: http.ContentEncoding = .identity,
475458
476459 pub const ParseError = error{
477460 HttpHeadersInvalid,
......@@ -554,8 +537,8 @@ pub const Response = struct {
554537 const trimmed_second = mem.trim(u8, second, " ");
555538
556539 if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| {
557 if (res.transfer_compression != .identity) return error.HttpHeadersInvalid; // double compression is not supported
558 res.transfer_compression = transfer;
540 if (res.content_encoding != .identity) return error.HttpHeadersInvalid; // double compression is not supported
541 res.content_encoding = transfer;
559542 } else {
560543 return error.HttpTransferEncodingUnsupported;
561544 }
......@@ -569,12 +552,12 @@ pub const Response = struct {
569552
570553 res.content_length = content_length;
571554 } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
572 if (res.transfer_compression != .identity) return error.HttpHeadersInvalid;
555 if (res.content_encoding != .identity) return error.HttpHeadersInvalid;
573556
574557 const trimmed = mem.trim(u8, header_value, " ");
575558
576559 if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
577 res.transfer_compression = ce;
560 res.content_encoding = ce;
578561 } else {
579562 return error.HttpTransferEncodingUnsupported;
580563 }
......@@ -592,7 +575,7 @@ pub const Response = struct {
592575 "TRansfer-encoding:\tdeflate, chunked \r\n" ++
593576 "connectioN:\t keep-alive \r\n\r\n";
594577
595 const head = Head.parse(response_bytes);
578 const head = try Head.parse(response_bytes);
596579
597580 try testing.expectEqual(.@"HTTP/1.1", head.version);
598581 try testing.expectEqualStrings("OK", head.reason);
......@@ -605,7 +588,7 @@ pub const Response = struct {
605588 try testing.expectEqual(true, head.keep_alive);
606589 try testing.expectEqual(10, head.content_length.?);
607590 try testing.expectEqual(.chunked, head.transfer_encoding);
608 try testing.expectEqual(.deflate, head.transfer_compression);
591 try testing.expectEqual(.deflate, head.content_encoding);
609592 }
610593
611594 pub fn iterateHeaders(h: Head) http.HeaderIterator {
......@@ -621,19 +604,8 @@ pub const Response = struct {
621604 "TRansfer-encoding:\tdeflate, chunked \r\n" ++
622605 "connectioN:\t keep-alive \r\n\r\n";
623606
624 var header_buffer: [1024]u8 = undefined;
625 var res = Response{
626 .status = undefined,
627 .reason = undefined,
628 .version = undefined,
629 .keep_alive = false,
630 .parser = .init(&header_buffer),
631 };
632
633 @memcpy(header_buffer[0..response_bytes.len], response_bytes);
634 res.parser.header_bytes_len = response_bytes.len;
635
636 var it = res.iterateHeaders();
607 const head = try Head.parse(response_bytes);
608 var it = head.iterateHeaders();
637609 {
638610 const header = it.next().?;
639611 try testing.expectEqualStrings("LOcation", header.name);
......@@ -695,7 +667,7 @@ pub const Response = struct {
695667 /// Asserts that this function is only called once.
696668 pub fn reader(response: *Response) std.io.Reader {
697669 const head = &response.head;
698 return response.request.reader.interface(head.transfer_encoding, head.content_length);
670 return response.request.reader.interface(head.transfer_encoding, head.content_length, head.content_encoding);
699671 }
700672};
701673
......@@ -778,16 +750,16 @@ pub const Request = struct {
778750 }
779751 };
780752
781 /// Frees all resources associated with the request.
782 pub fn deinit(req: *Request) void {
783 if (req.connection) |connection| {
784 if (!req.response.parser.done) {
785 // If the response wasn't fully read, then we need to close the connection.
753 /// Returns the request's `Connection` back to the pool of the `Client`.
754 pub fn deinit(r: *Request) void {
755 if (r.connection) |connection| {
756 if (r.reader.state != .ready) {
757 // Connection cannot be reused.
786758 connection.closing = true;
787759 }
788 req.client.connection_pool.release(req.client.allocator, connection);
760 r.client.connection_pool.release(connection);
789761 }
790 req.* = undefined;
762 r.* = undefined;
791763 }
792764
793765 /// Sends and flushes a complete request as only HTTP head, no body.
......@@ -810,12 +782,12 @@ pub const Request = struct {
810782 try sendHead(r);
811783 return .{
812784 .http_protocol_output = &r.connection.?.writer,
813 .transfer_encoding = if (r.transfer_encoding) |te| switch (te) {
785 .state = switch (r.transfer_encoding) {
814786 .chunked => .{ .chunked = .init },
815787 .content_length => |len| .{ .content_length = len },
816788 .none => .none,
817 } else .{ .chunked = .init },
818 .elide_body = false,
789 },
790 .elide = false,
819791 };
820792 }
821793
......@@ -912,7 +884,7 @@ pub const Request = struct {
912884 try w.writeAll("\r\n");
913885 }
914886
915 pub const ReceiveHeadError = http.Reader.HeadError || error{
887 pub const ReceiveHeadError = std.io.Writer.Error || http.Reader.HeadError || error{
916888 /// Server sent headers that did not conform to the HTTP protocol.
917889 ///
918890 /// To find out more detailed diagnostics, `http.Reader.head_buffer` can be
......@@ -956,7 +928,7 @@ pub const Request = struct {
956928
957929 if (head.status == .@"continue") {
958930 if (r.handle_continue) continue;
959 return; // we're not handling the 100-continue
931 return response; // we're not handling the 100-continue
960932 }
961933
962934 // This while loop is for handling redirects, which means the request's
......@@ -987,25 +959,17 @@ pub const Request = struct {
987959 if (r.redirect_behavior == .not_allowed) return error.TooManyHttpRedirects;
988960 const location = head.location orelse return error.HttpRedirectLocationMissing;
989961 try r.redirect(location, &aux_buf);
990 try r.send();
962 try r.sendBodiless();
991963 continue;
992964 }
993965
994 switch (head.transfer_compression) {
995 .identity => response.compression = .none,
966 switch (head.content_encoding) {
967 .identity, .deflate, .gzip, .@"x-gzip" => {},
996968 .compress, .@"x-compress" => return error.CompressionUnsupported,
997 .deflate => response.compression = .{
998 .deflate = std.compress.zlib.decompressor(r.transferReader()),
999 },
1000 .gzip, .@"x-gzip" => response.compression = .{
1001 .gzip = std.compress.gzip.decompressor(r.transferReader()),
1002 },
1003969 // https://github.com/ziglang/zig/issues/18937
1004 //.zstd => response.compression = .{
1005 // .zstd = std.compress.zstd.decompressStream(r.client.allocator, r.transferReader()),
1006 //},
1007970 .zstd => return error.CompressionUnsupported,
1008971 }
972
1009973 return response;
1010974 }
1011975 }
......@@ -1050,7 +1014,7 @@ pub const Request = struct {
10501014 std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and
10511015 sameParentDomain(old_host, new_host);
10521016
1053 r.client.connection_pool.release(r.client.allocator, old_connection);
1017 r.client.connection_pool.release(old_connection);
10541018 r.connection = null;
10551019
10561020 if (!keep_privileged_headers) {
......@@ -1327,7 +1291,7 @@ pub fn connectTunnel(
13271291 const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol);
13281292 errdefer {
13291293 conn.closing = true;
1330 client.connection_pool.release(client.allocator, conn);
1294 client.connection_pool.release(conn);
13311295 }
13321296
13331297 var buffer: [8096]u8 = undefined;
lib/std/http/Server.zig+5-2
......@@ -242,9 +242,12 @@ pub const Request = struct {
242242 br.initFixed(&read_buffer);
243243
244244 var server: Server = .{
245 .in = &br,
245 .reader = .{
246 .in = &br,
247 .state = .ready,
248 .body_state = undefined,
249 },
246250 .out = undefined,
247 .state = .ready,
248251 };
249252
250253 var request: Request = .{
lib/std/http/test.zig+11-12
......@@ -24,10 +24,10 @@ test "trailers" {
2424 var connection_bw = stream_writer.interface().buffered(&send_buffer);
2525 var server = http.Server.init(&connection_br, &connection_bw);
2626
27 try expectEqual(.ready, server.state);
27 try expectEqual(.ready, server.reader.state);
2828 var request = try server.receiveHead();
2929 try serve(&request);
30 try expectEqual(.ready, server.state);
30 try expectEqual(.ready, server.reader.state);
3131 }
3232 }
3333
......@@ -72,7 +72,7 @@ test "trailers" {
7272
7373 try expectEqualStrings("Hello, World!\n", body);
7474
75 var it = response.iterateHeaders();
75 var it = response.head.iterateHeaders();
7676 {
7777 const header = it.next().?;
7878 try expect(!it.is_trailer);
......@@ -174,7 +174,7 @@ test "echo content server" {
174174 var connection_bw = stream_writer.interface().buffered(&send_buffer);
175175 var http_server = http.Server.init(&connection_br, &connection_bw);
176176
177 while (http_server.state == .ready) {
177 while (http_server.reader.state == .ready) {
178178 var request = http_server.receiveHead() catch |err| switch (err) {
179179 error.HttpConnectionClosing => continue :accept,
180180 else => |e| return e,
......@@ -401,7 +401,7 @@ test "general client/server API coverage" {
401401 var connection_bw = stream_writer.interface().buffered(&send_buffer);
402402 var http_server = http.Server.init(&connection_br, &connection_bw);
403403
404 while (http_server.state == .ready) {
404 while (http_server.reader.state == .ready) {
405405 var request = http_server.receiveHead() catch |err| switch (err) {
406406 error.HttpConnectionClosing => continue :outer,
407407 else => |e| return e,
......@@ -618,7 +618,7 @@ test "general client/server API coverage" {
618618 defer gpa.free(body);
619619
620620 try expectEqualStrings("", body);
621 try expectEqualStrings("text/plain", response.content_type.?);
621 try expectEqualStrings("text/plain", response.head.content_type.?);
622622 try expectEqual(14, response.head.content_length.?);
623623 }
624624
......@@ -962,11 +962,10 @@ test "Server streams both reading and writing" {
962962 var body_writer = try req.sendBody();
963963 var response = try req.receiveHead(&redirect_buffer);
964964
965 var w = body_writer.interface().unbuffered();
965 var w = body_writer.writer().unbuffered();
966966 try w.writeAll("one ");
967967 try w.writeAll("fish");
968
969 try req.finish();
968 try body_writer.end();
970969
971970 const body = try response.reader().readRemainingAlloc(std.testing.allocator, .limited(8192));
972971 defer std.testing.allocator.free(body);
......@@ -994,7 +993,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
994993 req.transfer_encoding = .{ .content_length = 14 };
995994
996995 var body_writer = try req.sendBody();
997 var w = body_writer.interface().unbuffered();
996 var w = body_writer.writer().unbuffered();
998997 try w.writeAll("Hello, ");
999998 try w.writeAll("World!\n");
1000999 try body_writer.end();
......@@ -1028,7 +1027,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
10281027 req.transfer_encoding = .chunked;
10291028
10301029 var body_writer = try req.sendBody();
1031 var w = body_writer.interface().unbuffered();
1030 var w = body_writer.writer().unbuffered();
10321031 try w.writeAll("Hello, ");
10331032 try w.writeAll("World!\n");
10341033 try body_writer.end();
......@@ -1082,7 +1081,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
10821081 req.transfer_encoding = .chunked;
10831082
10841083 var body_writer = try req.sendBody();
1085 var w = body_writer.interface().unbuffered();
1084 var w = body_writer.writer().unbuffered();
10861085 try w.writeAll("Hello, ");
10871086 try w.writeAll("World!\n");
10881087 try body_writer.end();