authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-16 16:24:11-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-08-16 16:24:11-07:00
log623290ea9b23780303e18f200fb0e4ca1861cf57
treeacc22088dfed1591332374afcd0f7315310881b0
parent551e009da7be279293fe261b729280d29fcf81b0
parent0cfd07bc865efa4b74d32b16b7dfd1a68167ce14
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24864 from ifreund/fix-std-cmd

http.BodyWriter: handle EOF in chunkedSendFile, simplify

4 files changed, 69 insertions(+), 118 deletions(-)

lib/compiler/std-docs.zig+13-3
......@@ -106,9 +106,19 @@ fn accept(context: *Context, connection: std.net.Server.Connection) void {
106106 return;
107107 },
108108 };
109 serveRequest(&request, context) catch |err| {
110 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });
111 return;
109 serveRequest(&request, context) catch |err| switch (err) {
110 error.WriteFailed => {
111 if (conn_writer.err) |e| {
112 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(e) });
113 } else {
114 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });
115 }
116 return;
117 },
118 else => {
119 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });
120 return;
121 },
112122 };
113123 }
114124}
lib/std/http.zig+52-111
......@@ -760,22 +760,14 @@ pub const BodyWriter = struct {
760760 /// As a debugging utility, counts down to zero as bytes are written.
761761 content_length: u64,
762762 /// Each chunk is wrapped in a header and trailer.
763 chunked: Chunked,
763 /// This length is the the number of bytes to be written before the
764 /// next header. This includes +2 for the `\r\n` trailer and is zero
765 /// for the beginning of the stream.
766 chunk_len: usize,
764767 /// Cleanly finished stream; connection can be reused.
765768 end,
766769
767 pub const Chunked = union(enum) {
768 /// Index to the start of the hex-encoded chunk length in the chunk
769 /// header within the buffer of `BodyWriter.http_protocol_output`.
770 /// Buffered chunk data starts here plus length of `chunk_header_template`.
771 offset: usize,
772 /// We are in the middle of a chunk and this is how many bytes are
773 /// left until the next header. This includes +2 for "\r"\n", and
774 /// is zero for the beginning of the stream.
775 chunk_len: usize,
776
777 pub const init: Chunked = .{ .chunk_len = 0 };
778 };
770 pub const init_chunked: State = .{ .chunk_len = 0 };
779771 };
780772
781773 pub fn isEliding(w: *const BodyWriter) bool {
......@@ -786,21 +778,7 @@ pub const BodyWriter = struct {
786778 pub fn flush(w: *BodyWriter) Error!void {
787779 const out = w.http_protocol_output;
788780 switch (w.state) {
789 .end, .none, .content_length => return out.flush(),
790 .chunked => |*chunked| switch (chunked.*) {
791 .offset => |offset| {
792 const chunk_len = out.end - offset - chunk_header_template.len;
793 if (chunk_len > 0) {
794 writeHex(out.buffer[offset..][0..chunk_len_digits], chunk_len);
795 chunked.* = .{ .chunk_len = 2 };
796 } else {
797 out.end = offset;
798 chunked.* = .{ .chunk_len = 0 };
799 }
800 try out.flush();
801 },
802 .chunk_len => return out.flush(),
803 },
781 .end, .none, .content_length, .chunk_len => return out.flush(),
804782 }
805783 }
806784
......@@ -843,7 +821,7 @@ pub const BodyWriter = struct {
843821 w.state = .end;
844822 },
845823 .none => {},
846 .chunked => return endChunkedUnflushed(w, .{}),
824 .chunk_len => return endChunkedUnflushed(w, .{}),
847825 }
848826 }
849827
......@@ -879,24 +857,16 @@ pub const BodyWriter = struct {
879857 /// * `endUnflushed`
880858 /// * `end`
881859 pub fn endChunkedUnflushed(w: *BodyWriter, options: EndChunkedOptions) Error!void {
882 const chunked = &w.state.chunked;
883860 if (w.isEliding()) {
884861 w.state = .end;
885862 return;
886863 }
887864 const bw = w.http_protocol_output;
888 switch (chunked.*) {
889 .offset => |offset| {
890 const chunk_len = bw.end - offset - chunk_header_template.len;
891 writeHex(bw.buffer[offset..][0..chunk_len_digits], chunk_len);
892 try bw.writeAll("\r\n");
893 },
894 .chunk_len => |chunk_len| switch (chunk_len) {
895 0 => {},
896 1 => try bw.writeByte('\n'),
897 2 => try bw.writeAll("\r\n"),
898 else => unreachable, // An earlier write call indicated more data would follow.
899 },
865 switch (w.state.chunk_len) {
866 0 => {},
867 1 => try bw.writeByte('\n'),
868 2 => try bw.writeAll("\r\n"),
869 else => unreachable, // An earlier write call indicated more data would follow.
900870 }
901871 try bw.writeAll("0\r\n");
902872 for (options.trailers) |trailer| {
......@@ -993,44 +963,32 @@ pub const BodyWriter = struct {
993963 return error.Unimplemented;
994964 };
995965 const out = bw.http_protocol_output;
996 const chunked = &bw.state.chunked;
997 state: switch (chunked.*) {
998 .offset => |off| {
999 // TODO: is it better perf to read small files into the buffer?
1000 const buffered_len = out.end - off - chunk_header_template.len;
1001 const chunk_len = data_len + buffered_len;
1002 writeHex(out.buffer[off..][0..chunk_len_digits], chunk_len);
966 switch (bw.state.chunk_len) {
967 0 => {
968 const header_buf = try out.writableArray(chunk_header_template.len);
969 @memcpy(header_buf, chunk_header_template);
970 writeHex(header_buf[0..chunk_len_digits], data_len);
1003971 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);
1004 chunked.* = .{ .chunk_len = data_len + 2 - n };
1005 return w.consume(n);
972 bw.state.chunk_len = data_len + 2 - n;
973 const ret = w.consume(n);
974 return ret;
1006975 },
1007 .chunk_len => |chunk_len| l: switch (chunk_len) {
1008 0 => {
1009 const off = out.end;
1010 const header_buf = try out.writableArray(chunk_header_template.len);
1011 @memcpy(header_buf, chunk_header_template);
1012 chunked.* = .{ .offset = off };
1013 continue :state .{ .offset = off };
1014 },
1015 1 => {
1016 try out.writeByte('\n');
1017 chunked.chunk_len = 0;
1018 continue :l 0;
1019 },
1020 2 => {
1021 try out.writeByte('\r');
1022 chunked.chunk_len = 1;
1023 continue :l 1;
1024 },
1025 else => {
1026 const chunk_limit: std.Io.Limit = .limited(chunk_len - 2);
1027 const n = if (chunk_limit.subtract(w.buffered().len)) |sendfile_limit|
1028 try out.sendFileHeader(w.buffered(), file_reader, sendfile_limit.min(limit))
1029 else
1030 try out.write(chunk_limit.slice(w.buffered()));
1031 chunked.chunk_len = chunk_len - n;
1032 return w.consume(n);
1033 },
976 1 => unreachable,
977 2 => {
978 try out.writeAll("\r\n");
979 bw.state.chunk_len = 0;
980 assert(file_reader.atEnd());
981 return error.EndOfStream;
982 },
983 else => {
984 const chunk_limit: std.Io.Limit = .limited(bw.state.chunk_len - 2);
985 const n = if (chunk_limit.subtract(w.buffered().len)) |sendfile_limit|
986 try out.sendFileHeader(w.buffered(), file_reader, sendfile_limit.min(limit))
987 else
988 try out.write(chunk_limit.slice(w.buffered()));
989 bw.state.chunk_len -= n;
990 const ret = w.consume(n);
991 return ret;
1034992 },
1035993 }
1036994 }
......@@ -1040,42 +998,25 @@ pub const BodyWriter = struct {
1040998 assert(!bw.isEliding());
1041999 const out = bw.http_protocol_output;
10421000 const data_len = w.end + Writer.countSplat(data, splat);
1043 const chunked = &bw.state.chunked;
1044 state: switch (chunked.*) {
1045 .offset => |offset| {
1046 if (out.unusedCapacityLen() >= data_len) {
1047 return w.consume(out.writeSplatHeader(w.buffered(), data, splat) catch unreachable);
1048 }
1049 const buffered_len = out.end - offset - chunk_header_template.len;
1050 const chunk_len = data_len + buffered_len;
1051 writeHex(out.buffer[offset..][0..chunk_len_digits], chunk_len);
1001 l: switch (bw.state.chunk_len) {
1002 0 => {
1003 const header_buf = try out.writableArray(chunk_header_template.len);
1004 @memcpy(header_buf, chunk_header_template);
1005 writeHex(header_buf[0..chunk_len_digits], data_len);
10521006 const n = try out.writeSplatHeader(w.buffered(), data, splat);
1053 chunked.* = .{ .chunk_len = data_len + 2 - n };
1007 bw.state.chunk_len = data_len + 2 - n;
10541008 return w.consume(n);
10551009 },
1056 .chunk_len => |chunk_len| l: switch (chunk_len) {
1057 0 => {
1058 const offset = out.end;
1059 const header_buf = try out.writableArray(chunk_header_template.len);
1060 @memcpy(header_buf, chunk_header_template);
1061 chunked.* = .{ .offset = offset };
1062 continue :state .{ .offset = offset };
1063 },
1064 1 => {
1065 try out.writeByte('\n');
1066 chunked.chunk_len = 0;
1067 continue :l 0;
1068 },
1069 2 => {
1070 try out.writeByte('\r');
1071 chunked.chunk_len = 1;
1072 continue :l 1;
1073 },
1074 else => {
1075 const n = try out.writeSplatHeaderLimit(w.buffered(), data, splat, .limited(chunk_len - 2));
1076 chunked.chunk_len = chunk_len - n;
1077 return w.consume(n);
1078 },
1010 1 => unreachable,
1011 2 => {
1012 try out.writeAll("\r\n");
1013 bw.state.chunk_len = 0;
1014 continue :l 0;
1015 },
1016 else => {
1017 const n = try out.writeSplatHeaderLimit(w.buffered(), data, splat, .limited(bw.state.chunk_len - 2));
1018 bw.state.chunk_len -= n;
1019 return w.consume(n);
10791020 },
10801021 }
10811022 }
lib/std/http/Client.zig+1-1
......@@ -912,7 +912,7 @@ pub const Request = struct {
912912 return switch (r.transfer_encoding) {
913913 .chunked => .{
914914 .http_protocol_output = http_protocol_output,
915 .state = .{ .chunked = .init },
915 .state = .init_chunked,
916916 .writer = .{
917917 .buffer = buffer,
918918 .vtable = &.{
lib/std/http/Server.zig+3-3
......@@ -450,11 +450,11 @@ pub const Request = struct {
450450 try out.writeAll("\r\n");
451451 const elide_body = request.head.method == .HEAD;
452452 const state: http.BodyWriter.State = if (o.transfer_encoding) |te| switch (te) {
453 .chunked => .{ .chunked = .init },
453 .chunked => .init_chunked,
454454 .none => .none,
455455 } else if (options.content_length) |len| .{
456456 .content_length = len,
457 } else .{ .chunked = .init };
457 } else .init_chunked;
458458
459459 return if (elide_body) .{
460460 .http_protocol_output = request.server.out,
......@@ -480,7 +480,7 @@ pub const Request = struct {
480480 .drain = http.BodyWriter.contentLengthDrain,
481481 .sendFile = http.BodyWriter.contentLengthSendFile,
482482 },
483 .chunked => &.{
483 .chunk_len => &.{
484484 .drain = http.BodyWriter.chunkedDrain,
485485 .sendFile = http.BodyWriter.chunkedSendFile,
486486 },