authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-18 20:32:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:27-07:00
log9fe0ce377c8d74bdfe19b426922a57083de7a94a
tree2d3e40b6167a7c8276c929be8f5278cf832fb68e
parentcdd05df532511be471adb2006d89175335c0b980

std.http.Server: update end methods


3 files changed, 117 insertions(+), 164 deletions(-)

lib/compiler/std-docs.zig+3-2
......@@ -176,9 +176,10 @@ fn serveDocsFile(
176176 },
177177 });
178178
179 try response.writer().unbuffered().writeFileAll(file, .{
179 var bw = response.writer().unbuffered();
180 try bw.writeFileAll(file, .{
180181 .offset = .zero,
181 .limit = .init(content_length),
182 .limit = .limited(content_length),
182183 });
183184 try response.end();
184185}
lib/std/fifo.zig+2-2
......@@ -261,10 +261,10 @@ pub fn LinearFifo(
261261 _ = data;
262262 @panic("TODO");
263263 }
264 fn readerDiscard(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize {
264 fn readerDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize {
265265 const fifo: *Self = @alignCast(@ptrCast(ctx));
266266 _ = fifo;
267 _ = data;
267 _ = limit;
268268 @panic("TODO");
269269 }
270270
lib/std/http/Server.zig+112-160
......@@ -875,49 +875,86 @@ pub const Response = struct {
875875
876876 /// When using content-length, asserts that the amount of data sent matches
877877 /// the value sent in the header, then calls `flush`.
878 ///
878879 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
879 /// end-of-stream message, then flushes the stream to the system.
880 /// end-of-stream message with empty trailers, then flushes the stream to
881 /// the system.
882 ///
880883 /// Respects the value of `elide_body` to omit all data after the headers.
884 ///
885 /// Sets `r` to undefined.
886 ///
887 /// See also:
888 /// * `endUnflushed`
889 /// * `endChunked`
881890 pub fn end(r: *Response) WriteError!void {
891 try endUnflushed(r);
892 try r.server_output.flush();
893 r.* = undefined;
894 }
895
896 /// When using content-length, asserts that the amount of data sent matches
897 /// the value sent in the header.
898 ///
899 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
900 /// end-of-stream message with empty trailers.
901 ///
902 /// Respects the value of `elide_body` to omit all data after the headers.
903 ///
904 /// See also:
905 /// * `end`
906 /// * `endChunked`
907 pub fn endUnflushed(r: *Response) WriteError!void {
882908 switch (r.transfer_encoding) {
883 .content_length => |len| {
884 assert(len == 0); // Trips when end() called before all bytes written.
885 try flushContentLength(r);
886 },
887 .none => {
888 try flushContentLength(r);
889 },
890 .chunked => {
891 try flushChunked(r, &.{});
892 },
909 .content_length => |len| assert(len == 0), // Trips when end() called before all bytes written.
910 .none => {},
911 .chunked => try endChunked(r, .{}),
893912 }
894 r.* = undefined;
895913 }
896914
897915 pub const EndChunkedOptions = struct {
898916 trailers: []const http.Header = &.{},
899917 };
900918
919 /// Writes the end-of-stream message and any optional trailers.
920 ///
921 /// Does not flush.
922 ///
901923 /// Asserts that the Response is using transfer-encoding: chunked.
902 /// Writes the end-of-stream message and any optional trailers, then
903 /// flushes the stream to the system.
924 ///
904925 /// Respects the value of `elide_body` to omit all data after the headers.
905 /// Asserts there are at most 25 trailers.
926 ///
927 /// See also:
928 /// * `end`
929 /// * `endUnflushed`
906930 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {
907 assert(r.transfer_encoding == .chunked);
908 try flushChunked(r, options.trailers);
909 r.* = undefined;
910 }
911
912 /// If using content-length, asserts that writing these bytes to the client
913 /// would not exceed the content-length value sent in the HTTP header.
914 /// May return 0, which does not indicate end of stream. The caller decides
915 /// when the end of stream occurs by calling `end`.
916 pub fn write(r: *Response, bytes: []const u8) WriteError!usize {
917 switch (r.transfer_encoding) {
918 .content_length, .none => return contentLengthWriteSplat(r, &.{bytes}, 1),
919 .chunked => return chunkedWriteSplat(r, &.{bytes}, 1),
931 const chunked = &r.transfer_encoding.chunked;
932 if (r.elide_body) return;
933 const bw = r.server_output;
934 switch (chunked.*) {
935 .offset => |offset| {
936 const chunk_len = bw.end - offset - chunk_header_template.len;
937 writeHex(bw.buffer[offset..][0..chunk_len_digits], chunk_len);
938 try bw.writeAll("\r\n");
939 },
940 .chunk_len => |chunk_len| switch (chunk_len) {
941 0 => {},
942 1 => try bw.writeByte('\n'),
943 2 => try bw.writeAll("\r\n"),
944 else => unreachable, // An earlier write call indicated more data would follow.
945 },
946 }
947 if (options.trailers.len > 0) {
948 try bw.writeAll("0\r\n");
949 for (options.trailers) |trailer| {
950 try bw.writeAll(trailer.name);
951 try bw.writeAll(": ");
952 try bw.writeAll(trailer.value);
953 try bw.writeAll("\r\n");
954 }
955 try bw.writeAll("\r\n");
920956 }
957 r.* = undefined;
921958 }
922959
923960 fn contentLengthWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) WriteError!usize {
......@@ -1017,32 +1054,30 @@ pub const Response = struct {
10171054 chunked.* = .{ .chunk_len = data_len + 2 - n };
10181055 return n;
10191056 },
1020 .chunk_len => |chunk_len| {
1021 l: switch (chunk_len) {
1022 0 => {
1023 const header_buf = try bw.writableArray(chunk_header_template.len);
1024 const off = bw.end;
1025 @memcpy(header_buf, chunk_header_template);
1026 chunked.* = .{ .offset = off };
1027 continue :state .{ .offset = off };
1028 },
1029 1 => {
1030 try bw.writeByte('\n');
1031 chunked.chunk_len = 0;
1032 continue :l 0;
1033 },
1034 2 => {
1035 try bw.writeByte('\r');
1036 chunked.chunk_len = 1;
1037 continue :l 1;
1038 },
1039 else => {
1040 const new_limit = limit.min(.limited(chunk_len - 2));
1041 const n = try bw.writeFile(file, offset, new_limit, headers_and_trailers, headers_len);
1042 chunked.chunk_len = chunk_len - n;
1043 return n;
1044 },
1045 }
1057 .chunk_len => |chunk_len| l: switch (chunk_len) {
1058 0 => {
1059 const header_buf = try bw.writableArray(chunk_header_template.len);
1060 const off = bw.end;
1061 @memcpy(header_buf, chunk_header_template);
1062 chunked.* = .{ .offset = off };
1063 continue :state .{ .offset = off };
1064 },
1065 1 => {
1066 try bw.writeByte('\n');
1067 chunked.chunk_len = 0;
1068 continue :l 0;
1069 },
1070 2 => {
1071 try bw.writeByte('\r');
1072 chunked.chunk_len = 1;
1073 continue :l 1;
1074 },
1075 else => {
1076 const new_limit = limit.min(.limited(chunk_len - 2));
1077 const n = try bw.writeFile(file, offset, new_limit, headers_and_trailers, headers_len);
1078 chunked.chunk_len = chunk_len - n;
1079 return n;
1080 },
10461081 },
10471082 }
10481083 }
......@@ -1068,31 +1103,29 @@ pub const Response = struct {
10681103 chunked.* = .{ .chunk_len = data_len + 2 - n };
10691104 return n;
10701105 },
1071 .chunk_len => |chunk_len| {
1072 l: switch (chunk_len) {
1073 0 => {
1074 const header_buf = try bw.writableArray(chunk_header_template.len);
1075 const offset = bw.end;
1076 @memcpy(header_buf, chunk_header_template);
1077 chunked.* = .{ .offset = offset };
1078 continue :state .{ .offset = offset };
1079 },
1080 1 => {
1081 try bw.writeByte('\n');
1082 chunked.chunk_len = 0;
1083 continue :l 0;
1084 },
1085 2 => {
1086 try bw.writeByte('\r');
1087 chunked.chunk_len = 1;
1088 continue :l 1;
1089 },
1090 else => {
1091 const n = try bw.writeSplatLimit(data, splat, .limited(chunk_len - 2));
1092 chunked.chunk_len = chunk_len - n;
1093 return n;
1094 },
1095 }
1106 .chunk_len => |chunk_len| l: switch (chunk_len) {
1107 0 => {
1108 const header_buf = try bw.writableArray(chunk_header_template.len);
1109 const offset = bw.end;
1110 @memcpy(header_buf, chunk_header_template);
1111 chunked.* = .{ .offset = offset };
1112 continue :state .{ .offset = offset };
1113 },
1114 1 => {
1115 try bw.writeByte('\n');
1116 chunked.chunk_len = 0;
1117 continue :l 0;
1118 },
1119 2 => {
1120 try bw.writeByte('\r');
1121 chunked.chunk_len = 1;
1122 continue :l 1;
1123 },
1124 else => {
1125 const n = try bw.writeSplatLimit(data, splat, .limited(chunk_len - 2));
1126 chunked.chunk_len = chunk_len - n;
1127 return n;
1128 },
10961129 },
10971130 }
10981131 }
......@@ -1112,87 +1145,6 @@ pub const Response = struct {
11121145 }
11131146 }
11141147
1115 /// Sends all buffered data to the client.
1116 /// This is redundant after calling `end`.
1117 /// Respects the value of `elide_body` to omit all data after the headers.
1118 pub fn flush(r: *Response) Error!void {
1119 switch (r.transfer_encoding) {
1120 .none, .content_length => return flushContentLength(r),
1121 .chunked => return flushChunked(r, null),
1122 }
1123 }
1124
1125 fn flushContentLength(r: *Response) Error!void {
1126 try r.out.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]);
1127 r.send_buffer_start = 0;
1128 r.send_buffer_end = 0;
1129 }
1130
1131 fn flushChunked(r: *Response, end_trailers: ?[]const http.Header) Error!void {
1132 const max_trailers = 25;
1133 if (end_trailers) |trailers| assert(trailers.len <= max_trailers);
1134 assert(r.transfer_encoding == .chunked);
1135
1136 const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len];
1137
1138 if (r.elide_body) {
1139 try r.out.writeAll(http_headers);
1140 r.send_buffer_start = 0;
1141 r.send_buffer_end = 0;
1142 r.chunk_len = 0;
1143 return;
1144 }
1145
1146 var header_buf: [18]u8 = undefined;
1147 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable;
1148
1149 var iovecs: [max_trailers * 4 + 5][]const u8 = undefined;
1150 var iovecs_len: usize = 0;
1151
1152 iovecs[iovecs_len] = http_headers;
1153 iovecs_len += 1;
1154
1155 if (r.chunk_len > 0) {
1156 iovecs[iovecs_len] = chunk_header;
1157 iovecs_len += 1;
1158
1159 iovecs[iovecs_len] = r.send_buffer[r.send_buffer_end - r.chunk_len ..][0..r.chunk_len];
1160 iovecs_len += 1;
1161
1162 iovecs[iovecs_len] = "\r\n";
1163 iovecs_len += 1;
1164 }
1165
1166 if (end_trailers) |trailers| {
1167 iovecs[iovecs_len] = "0\r\n";
1168 iovecs_len += 1;
1169
1170 for (trailers) |trailer| {
1171 iovecs[iovecs_len] = trailer.name;
1172 iovecs_len += 1;
1173
1174 iovecs[iovecs_len] = ": ";
1175 iovecs_len += 1;
1176
1177 if (trailer.value.len != 0) {
1178 iovecs[iovecs_len] = trailer.value;
1179 iovecs_len += 1;
1180 }
1181
1182 iovecs[iovecs_len] = "\r\n";
1183 iovecs_len += 1;
1184 }
1185
1186 iovecs[iovecs_len] = "\r\n";
1187 iovecs_len += 1;
1188 }
1189
1190 try r.out.writeVecAll(iovecs[0..iovecs_len]);
1191 r.send_buffer_start = 0;
1192 r.send_buffer_end = 0;
1193 r.chunk_len = 0;
1194 }
1195
11961148 pub fn writer(r: *Response) std.io.Writer {
11971149 return .{
11981150 .context = r,