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(...@@ -176,9 +176,10 @@ fn serveDocsFile(
176 },176 },
177 });177 });
178178
179 try response.writer().unbuffered().writeFileAll(file, .{179 var bw = response.writer().unbuffered();
180 try bw.writeFileAll(file, .{
180 .offset = .zero,181 .offset = .zero,
181 .limit = .init(content_length),182 .limit = .limited(content_length),
182 });183 });
183 try response.end();184 try response.end();
184}185}
lib/std/fifo.zig+2-2
...@@ -261,10 +261,10 @@ pub fn LinearFifo(...@@ -261,10 +261,10 @@ pub fn LinearFifo(
261 _ = data;261 _ = data;
262 @panic("TODO");262 @panic("TODO");
263 }263 }
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 {
265 const fifo: *Self = @alignCast(@ptrCast(ctx));265 const fifo: *Self = @alignCast(@ptrCast(ctx));
266 _ = fifo;266 _ = fifo;
267 _ = data;267 _ = limit;
268 @panic("TODO");268 @panic("TODO");
269 }269 }
270270
lib/std/http/Server.zig+112-160
...@@ -875,49 +875,86 @@ pub const Response = struct {...@@ -875,49 +875,86 @@ pub const Response = struct {
875875
876 /// When using content-length, asserts that the amount of data sent matches876 /// When using content-length, asserts that the amount of data sent matches
877 /// the value sent in the header, then calls `flush`.877 /// the value sent in the header, then calls `flush`.
878 ///
878 /// Otherwise, transfer-encoding: chunked is being used, and it writes the879 /// 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 ///
880 /// Respects the value of `elide_body` to omit all data after the headers.883 /// 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`
881 pub fn end(r: *Response) WriteError!void {890 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 {
882 switch (r.transfer_encoding) {908 switch (r.transfer_encoding) {
883 .content_length => |len| {909 .content_length => |len| assert(len == 0), // Trips when end() called before all bytes written.
884 assert(len == 0); // Trips when end() called before all bytes written.910 .none => {},
885 try flushContentLength(r);911 .chunked => try endChunked(r, .{}),
886 },
887 .none => {
888 try flushContentLength(r);
889 },
890 .chunked => {
891 try flushChunked(r, &.{});
892 },
893 }912 }
894 r.* = undefined;
895 }913 }
896914
897 pub const EndChunkedOptions = struct {915 pub const EndChunkedOptions = struct {
898 trailers: []const http.Header = &.{},916 trailers: []const http.Header = &.{},
899 };917 };
900918
919 /// Writes the end-of-stream message and any optional trailers.
920 ///
921 /// Does not flush.
922 ///
901 /// Asserts that the Response is using transfer-encoding: chunked.923 /// Asserts that the Response is using transfer-encoding: chunked.
902 /// Writes the end-of-stream message and any optional trailers, then924 ///
903 /// flushes the stream to the system.
904 /// Respects the value of `elide_body` to omit all data after the headers.925 /// 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`
906 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {930 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {
907 assert(r.transfer_encoding == .chunked);931 const chunked = &r.transfer_encoding.chunked;
908 try flushChunked(r, options.trailers);932 if (r.elide_body) return;
909 r.* = undefined;933 const bw = r.server_output;
910 }934 switch (chunked.*) {
911935 .offset => |offset| {
912 /// If using content-length, asserts that writing these bytes to the client936 const chunk_len = bw.end - offset - chunk_header_template.len;
913 /// would not exceed the content-length value sent in the HTTP header.937 writeHex(bw.buffer[offset..][0..chunk_len_digits], chunk_len);
914 /// May return 0, which does not indicate end of stream. The caller decides938 try bw.writeAll("\r\n");
915 /// when the end of stream occurs by calling `end`.939 },
916 pub fn write(r: *Response, bytes: []const u8) WriteError!usize {940 .chunk_len => |chunk_len| switch (chunk_len) {
917 switch (r.transfer_encoding) {941 0 => {},
918 .content_length, .none => return contentLengthWriteSplat(r, &.{bytes}, 1),942 1 => try bw.writeByte('\n'),
919 .chunked => return chunkedWriteSplat(r, &.{bytes}, 1),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");
920 }956 }
957 r.* = undefined;
921 }958 }
922959
923 fn contentLengthWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) WriteError!usize {960 fn contentLengthWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) WriteError!usize {
...@@ -1017,32 +1054,30 @@ pub const Response = struct {...@@ -1017,32 +1054,30 @@ pub const Response = struct {
1017 chunked.* = .{ .chunk_len = data_len + 2 - n };1054 chunked.* = .{ .chunk_len = data_len + 2 - n };
1018 return n;1055 return n;
1019 },1056 },
1020 .chunk_len => |chunk_len| {1057 .chunk_len => |chunk_len| l: switch (chunk_len) {
1021 l: switch (chunk_len) {1058 0 => {
1022 0 => {1059 const header_buf = try bw.writableArray(chunk_header_template.len);
1023 const header_buf = try bw.writableArray(chunk_header_template.len);1060 const off = bw.end;
1024 const off = bw.end;1061 @memcpy(header_buf, chunk_header_template);
1025 @memcpy(header_buf, chunk_header_template);1062 chunked.* = .{ .offset = off };
1026 chunked.* = .{ .offset = off };1063 continue :state .{ .offset = off };
1027 continue :state .{ .offset = off };1064 },
1028 },1065 1 => {
1029 1 => {1066 try bw.writeByte('\n');
1030 try bw.writeByte('\n');1067 chunked.chunk_len = 0;
1031 chunked.chunk_len = 0;1068 continue :l 0;
1032 continue :l 0;1069 },
1033 },1070 2 => {
1034 2 => {1071 try bw.writeByte('\r');
1035 try bw.writeByte('\r');1072 chunked.chunk_len = 1;
1036 chunked.chunk_len = 1;1073 continue :l 1;
1037 continue :l 1;1074 },
1038 },1075 else => {
1039 else => {1076 const new_limit = limit.min(.limited(chunk_len - 2));
1040 const new_limit = limit.min(.limited(chunk_len - 2));1077 const n = try bw.writeFile(file, offset, new_limit, headers_and_trailers, headers_len);
1041 const n = try bw.writeFile(file, offset, new_limit, headers_and_trailers, headers_len);1078 chunked.chunk_len = chunk_len - n;
1042 chunked.chunk_len = chunk_len - n;1079 return n;
1043 return n;1080 },
1044 },
1045 }
1046 },1081 },
1047 }1082 }
1048 }1083 }
...@@ -1068,31 +1103,29 @@ pub const Response = struct {...@@ -1068,31 +1103,29 @@ pub const Response = struct {
1068 chunked.* = .{ .chunk_len = data_len + 2 - n };1103 chunked.* = .{ .chunk_len = data_len + 2 - n };
1069 return n;1104 return n;
1070 },1105 },
1071 .chunk_len => |chunk_len| {1106 .chunk_len => |chunk_len| l: switch (chunk_len) {
1072 l: switch (chunk_len) {1107 0 => {
1073 0 => {1108 const header_buf = try bw.writableArray(chunk_header_template.len);
1074 const header_buf = try bw.writableArray(chunk_header_template.len);1109 const offset = bw.end;
1075 const offset = bw.end;1110 @memcpy(header_buf, chunk_header_template);
1076 @memcpy(header_buf, chunk_header_template);1111 chunked.* = .{ .offset = offset };
1077 chunked.* = .{ .offset = offset };1112 continue :state .{ .offset = offset };
1078 continue :state .{ .offset = offset };1113 },
1079 },1114 1 => {
1080 1 => {1115 try bw.writeByte('\n');
1081 try bw.writeByte('\n');1116 chunked.chunk_len = 0;
1082 chunked.chunk_len = 0;1117 continue :l 0;
1083 continue :l 0;1118 },
1084 },1119 2 => {
1085 2 => {1120 try bw.writeByte('\r');
1086 try bw.writeByte('\r');1121 chunked.chunk_len = 1;
1087 chunked.chunk_len = 1;1122 continue :l 1;
1088 continue :l 1;1123 },
1089 },1124 else => {
1090 else => {1125 const n = try bw.writeSplatLimit(data, splat, .limited(chunk_len - 2));
1091 const n = try bw.writeSplatLimit(data, splat, .limited(chunk_len - 2));1126 chunked.chunk_len = chunk_len - n;
1092 chunked.chunk_len = chunk_len - n;1127 return n;
1093 return n;1128 },
1094 },
1095 }
1096 },1129 },
1097 }1130 }
1098 }1131 }
...@@ -1112,87 +1145,6 @@ pub const Response = struct {...@@ -1112,87 +1145,6 @@ pub const Response = struct {
1112 }1145 }
1113 }1146 }
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
1196 pub fn writer(r: *Response) std.io.Writer {1148 pub fn writer(r: *Response) std.io.Writer {
1197 return .{1149 return .{
1198 .context = r,1150 .context = r,