authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-18 18:14:00-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:27-07:00
logf3332677825f3173de568d643cbaf68f1e1472ed
tree31232ea5d95e8556f826a90f34e2fbad29287be8
parent98f463ad599bebd46fbc1e4f8ff365ef781bfe5f

update std.http.Server to new API

and rename std.io.BufferedWriter.writableSlice to writableSliceGreedy and make writableSlice and writableArray advance the buffer end position introduce std.io.BufferedWriter.writeSplatLimit but it's unimplemented

14 files changed, 343 insertions(+), 218 deletions(-)

lib/compiler/std-docs.zig+11-8
......@@ -90,8 +90,15 @@ pub fn main() !void {
9090fn accept(context: *Context, connection: std.net.Server.Connection) void {
9191 defer connection.stream.close();
9292
93 var read_buffer: [8000]u8 = undefined;
94 var server = std.http.Server.init(connection, &read_buffer);
93 var recv_buffer: [8000]u8 = undefined;
94 var send_buffer: [4000]u8 = undefined;
95 var connection_br: std.io.BufferedReader = undefined;
96 var stream_reader = connection.stream.reader();
97 connection_br.init(stream_reader.interface(), &recv_buffer);
98 var stream_writer = connection.stream.writer();
99 var connection_bw = stream_writer.interface().buffered(&send_buffer);
100 var server = std.http.Server.init(&connection_br, &connection_bw);
101
95102 while (server.state == .ready) {
96103 var request = server.receiveHead() catch |err| switch (err) {
97104 error.HttpConnectionClosing => return,
......@@ -160,9 +167,7 @@ fn serveDocsFile(
160167 defer file.close();
161168 const content_length = std.math.cast(usize, (try file.stat()).size) orelse return error.FileTooBig;
162169
163 var send_buffer: [4000]u8 = undefined;
164 var response = request.respondStreaming(.{
165 .send_buffer = &send_buffer,
170 var response = try request.respondStreaming(.{
166171 .content_length = content_length,
167172 .respond_options = .{
168173 .extra_headers = &.{
......@@ -182,9 +187,7 @@ fn serveDocsFile(
182187fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
183188 const gpa = context.gpa;
184189
185 var send_buffer: [0x4000]u8 = undefined;
186 var response = request.respondStreaming(.{
187 .send_buffer = &send_buffer,
190 var response = try request.respondStreaming(.{
188191 .respond_options = .{
189192 .extra_headers = &.{
190193 .{ .name = "content-type", .value = "application/x-tar" },
lib/std/compress/flate/inflate.zig+2-2
......@@ -349,8 +349,8 @@ pub fn Inflate(comptime container: Container, comptime Lookahead: type) type {
349349 limit: std.io.Reader.Limit,
350350 ) std.io.Reader.RwError!usize {
351351 const self: *Self = @alignCast(@ptrCast(context));
352 const out = try bw.writableSlice(1);
353 const in = self.get(limit.min(out.len)) catch |err| switch (err) {
352 const out = try bw.writableSliceGreedy(1);
353 const in = self.get(limit.minInt(out.len)) catch |err| switch (err) {
354354 error.EndOfStream => return error.EndOfStream,
355355 error.ReadFailed => return error.ReadFailed,
356356 else => |e| {
lib/std/crypto/tls/Client.zig+3-3
......@@ -925,7 +925,7 @@ fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.i
925925 const c: *Client = @alignCast(@ptrCast(context));
926926 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
927927 const output = &c.output;
928 const ciphertext_buf = try output.writableSlice(min_buffer_len);
928 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
929929 var total_clear: usize = 0;
930930 var ciphertext_end: usize = 0;
931931 for (sliced_data) |buf| {
......@@ -943,7 +943,7 @@ fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.i
943943/// attack.
944944pub fn end(c: *Client) std.io.Writer.Error!void {
945945 const output = &c.output;
946 const ciphertext_buf = try output.writableSlice(min_buffer_len);
946 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
947947 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);
948948 output.advance(prepared.cleartext_len);
949949 return prepared.ciphertext_end;
......@@ -1063,7 +1063,7 @@ fn read(
10631063 bw: *std.io.BufferedWriter,
10641064 limit: std.io.Reader.Limit,
10651065) std.io.Reader.RwError!std.io.Reader.Status {
1066 const buf = limit.slice(try bw.writableSlice(1));
1066 const buf = limit.slice(try bw.writableSliceGreedy(1));
10671067 const status = try readVec(context, &.{buf});
10681068 bw.advance(status.len);
10691069 return status;
lib/std/fs/File.zig+1-1
......@@ -983,7 +983,7 @@ pub const Reader = struct {
983983 }
984984 return 0;
985985 };
986 const new_limit: std.io.Reader.Limit = .limited(limit.min(size - pos));
986 const new_limit = limit.min(.limited(size - pos));
987987 const n = bw.writeFile(file, .init(pos), new_limit, &.{}, 0) catch |err| switch (err) {
988988 error.WriteFailed => return error.WriteFailed,
989989 error.Unseekable => {
lib/std/http/Server.zig+243-167
......@@ -14,6 +14,7 @@ const Server = @This();
1414/// The reader's buffer must be large enough to store the client's entire HTTP
1515/// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`.
1616in: *std.io.BufferedReader,
17/// Data from the HTTP server to the HTTP client.
1718out: *std.io.BufferedWriter,
1819/// Keeps track of whether the Server is ready to accept a new request on the
1920/// same connection, and makes invalid API usage cause assertion failures
......@@ -479,12 +480,6 @@ pub const Request = struct {
479480 }
480481
481482 pub const RespondStreamingOptions = struct {
482 /// An externally managed slice of memory used to batch bytes before
483 /// sending. `respondStreaming` asserts this is large enough to store
484 /// the full HTTP response head.
485 ///
486 /// Must outlive the returned Response.
487 send_buffer: []u8,
488483 /// If provided, the response will use the content-length header;
489484 /// otherwise it will use transfer-encoding: chunked.
490485 content_length: ?u64 = null,
......@@ -492,7 +487,7 @@ pub const Request = struct {
492487 respond_options: RespondOptions = .{},
493488 };
494489
495 /// The header is buffered but not sent until Response.flush is called.
490 /// The header is buffered but not sent until `Response.flush` is called.
496491 ///
497492 /// If the request contains a body and the connection is to be reused,
498493 /// discards the request body, leaving the Server in the `ready` state. If
......@@ -504,69 +499,63 @@ pub const Request = struct {
504499 /// that flag and skipping any expensive work that would otherwise need to
505500 /// be done to satisfy the request.
506501 ///
507 /// Asserts `send_buffer` is large enough to store the entire response header.
508502 /// Asserts status is not `continue`.
509 pub fn respondStreaming(request: *Request, options: RespondStreamingOptions) Response {
503 pub fn respondStreaming(request: *Request, options: RespondStreamingOptions) std.io.Writer.Error!Response {
510504 const o = options.respond_options;
511505 assert(o.status != .@"continue");
512506 const transfer_encoding_none = (o.transfer_encoding orelse .chunked) == .none;
513507 const server_keep_alive = !transfer_encoding_none and o.keep_alive;
514508 const keep_alive = request.discardBody(server_keep_alive);
515509 const phrase = o.reason orelse o.status.phrase() orelse "";
516
517 var h = std.ArrayListUnmanaged(u8).initBuffer(options.send_buffer);
510 const out = request.server.out;
518511
519512 const elide_body = if (request.head.expect != null) eb: {
520513 // reader() and hence discardBody() above sets expect to null if it
521514 // is handled. So the fact that it is not null here means unhandled.
522 h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");
523 if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n");
524 h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");
515 try out.writeAll("HTTP/1.1 417 Expectation Failed\r\n");
516 if (!keep_alive) try out.writeAll("connection: close\r\n");
517 try out.writeAll("content-length: 0\r\n\r\n");
525518 break :eb true;
526519 } else eb: {
527 h.printAssumeCapacity("{s} {d} {s}\r\n", .{
520 try out.print("{s} {d} {s}\r\n", .{
528521 @tagName(o.version), @intFromEnum(o.status), phrase,
529522 });
530523
531524 switch (o.version) {
532 .@"HTTP/1.0" => if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"),
533 .@"HTTP/1.1" => if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"),
525 .@"HTTP/1.0" => if (keep_alive) try out.writeAll("connection: keep-alive\r\n"),
526 .@"HTTP/1.1" => if (!keep_alive) try out.writeAll("connection: close\r\n"),
534527 }
535528
536529 if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
537 .chunked => h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n"),
530 .chunked => try out.writeAll("transfer-encoding: chunked\r\n"),
538531 .none => {},
539532 } else if (options.content_length) |len| {
540 h.printAssumeCapacity("content-length: {d}\r\n", .{len});
533 try out.print("content-length: {d}\r\n", .{len});
541534 } else {
542 h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n");
535 try out.writeAll("transfer-encoding: chunked\r\n");
543536 }
544537
545538 for (o.extra_headers) |header| {
546539 assert(header.name.len != 0);
547 h.appendSliceAssumeCapacity(header.name);
548 h.appendSliceAssumeCapacity(": ");
549 h.appendSliceAssumeCapacity(header.value);
550 h.appendSliceAssumeCapacity("\r\n");
540 try out.writeAll(header.name);
541 try out.writeAll(": ");
542 try out.writeAll(header.value);
543 try out.writeAll("\r\n");
551544 }
552545
553 h.appendSliceAssumeCapacity("\r\n");
546 try out.writeAll("\r\n");
554547 break :eb request.head.method == .HEAD;
555548 };
556549
557550 return .{
558 .out = request.server.out,
559 .send_buffer = options.send_buffer,
560 .send_buffer_start = 0,
561 .send_buffer_end = h.items.len,
551 .server_output = request.server.out,
562552 .transfer_encoding = if (o.transfer_encoding) |te| switch (te) {
563 .chunked => .chunked,
553 .chunked => .{ .chunked = .init },
564554 .none => .none,
565555 } else if (options.content_length) |len| .{
566556 .content_length = len,
567 } else .chunked,
557 } else .{ .chunked = .init },
568558 .elide_body = elide_body,
569 .chunk_len = 0,
570559 };
571560 }
572561
......@@ -836,20 +825,32 @@ pub const Request = struct {
836825};
837826
838827pub const Response = struct {
839 out: *std.io.BufferedWriter,
840 send_buffer: []u8,
841 /// Index of the first byte in `send_buffer`.
842 /// This is 0 unless a short write happens in `write`.
843 send_buffer_start: usize,
844 /// Index of the last byte + 1 in `send_buffer`.
845 send_buffer_end: usize,
828 /// HTTP protocol to the client.
829 ///
830 /// This is the underlying stream; use `buffered` to create a
831 /// `BufferedWriter` for this `Response`.
832 server_output: *std.io.BufferedWriter,
846833 /// `null` means transfer-encoding: chunked.
847834 /// As a debugging utility, counts down to zero as bytes are written.
848835 transfer_encoding: TransferEncoding,
849836 elide_body: bool,
850 /// Indicates how much of the end of the `send_buffer` corresponds to a
851 /// chunk. This amount of data will be wrapped by an HTTP chunk header.
852 chunk_len: usize,
837 err: Error!void = {},
838
839 pub const Error = error{
840 /// Attempted to write a file to the stream, an expensive operation
841 /// that should be avoided when `elide_body` is true.
842 UnableToElideBody,
843 };
844 pub const WriteError = std.io.Writer.Error;
845
846 /// How many zeroes to reserve for hex-encoded chunk length.
847 const chunk_len_digits = 8;
848 const max_chunk_len: usize = std.math.pow(usize, 16, chunk_len_digits) - 1;
849 const chunk_header_template = ("0" ** chunk_len_digits) ++ "\r\n";
850
851 comptime {
852 assert(max_chunk_len == std.math.maxInt(u32));
853 }
853854
854855 pub const TransferEncoding = union(enum) {
855856 /// End of connection signals the end of the stream.
......@@ -857,7 +858,19 @@ pub const Response = struct {
857858 /// As a debugging utility, counts down to zero as bytes are written.
858859 content_length: u64,
859860 /// Each chunk is wrapped in a header and trailer.
860 chunked,
861 chunked: Chunked,
862
863 pub const Chunked = union(enum) {
864 /// Index of the hex-encoded chunk length in the chunk header
865 /// within the buffer of `Response.server_output`.
866 offset: usize,
867 /// We are in the middle of a chunk and this is how many bytes are
868 /// left until the next header. This includes +2 for "\r"\n", and
869 /// is zero for the beginning of the stream.
870 chunk_len: usize,
871
872 pub const init: Chunked = .{ .chunk_len = 0 };
873 };
861874 };
862875
863876 /// When using content-length, asserts that the amount of data sent matches
......@@ -865,17 +878,17 @@ pub const Response = struct {
865878 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
866879 /// end-of-stream message, then flushes the stream to the system.
867880 /// Respects the value of `elide_body` to omit all data after the headers.
868 pub fn end(r: *Response) std.io.Writer.Error!void {
881 pub fn end(r: *Response) WriteError!void {
869882 switch (r.transfer_encoding) {
870883 .content_length => |len| {
871884 assert(len == 0); // Trips when end() called before all bytes written.
872 try flush_cl(r);
885 try flushContentLength(r);
873886 },
874887 .none => {
875 try flush_cl(r);
888 try flushContentLength(r);
876889 },
877890 .chunked => {
878 try flush_chunked(r, &.{});
891 try flushChunked(r, &.{});
879892 },
880893 }
881894 r.* = undefined;
......@@ -890,9 +903,9 @@ pub const Response = struct {
890903 /// flushes the stream to the system.
891904 /// Respects the value of `elide_body` to omit all data after the headers.
892905 /// Asserts there are at most 25 trailers.
893 pub fn endChunked(r: *Response, options: EndChunkedOptions) std.io.Writer.Error!void {
906 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {
894907 assert(r.transfer_encoding == .chunked);
895 try flush_chunked(r, options.trailers);
908 try flushChunked(r, options.trailers);
896909 r.* = undefined;
897910 }
898911
......@@ -900,163 +913,222 @@ pub const Response = struct {
900913 /// would not exceed the content-length value sent in the HTTP header.
901914 /// May return 0, which does not indicate end of stream. The caller decides
902915 /// when the end of stream occurs by calling `end`.
903 pub fn write(r: *Response, bytes: []const u8) std.io.Writer.Error!usize {
916 pub fn write(r: *Response, bytes: []const u8) WriteError!usize {
904917 switch (r.transfer_encoding) {
905 .content_length, .none => return cl_writeSplat(r, &.{bytes}, 1),
906 .chunked => return chunked_writeSplat(r, &.{bytes}, 1),
918 .content_length, .none => return contentLengthWriteSplat(r, &.{bytes}, 1),
919 .chunked => return chunkedWriteSplat(r, &.{bytes}, 1),
920 }
921 }
922
923 fn contentLengthWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) WriteError!usize {
924 const r: *Response = @alignCast(@ptrCast(context));
925 const n = if (r.elide_body) countSplat(data, splat) else try r.server_output.writeSplat(data, splat);
926 r.transfer_encoding.content_length -= n;
927 return n;
928 }
929
930 fn noneWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) WriteError!usize {
931 const r: *Response = @alignCast(@ptrCast(context));
932 if (r.elide_body) return countSplat(data, splat);
933 return r.server_output.writeSplat(data, splat);
934 }
935
936 fn countSplat(data: []const []const u8, splat: usize) usize {
937 if (data.len == 0) return 0;
938 var total: usize = 0;
939 for (data[0 .. data.len - 1]) |buf| total += buf.len;
940 total += data[data.len - 1].len * splat;
941 return total;
942 }
943
944 fn elideWriteFile(
945 r: *Response,
946 offset: std.io.Writer.Offset,
947 limit: std.io.Writer.Limit,
948 headers_and_trailers: []const []const u8,
949 ) WriteError!usize {
950 if (offset != .none) {
951 if (countWriteFile(limit, headers_and_trailers)) |n| {
952 return n;
953 }
907954 }
955 r.err = error.UnableToElideBody;
956 return error.WriteFailed;
908957 }
909958
910 fn cl_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {
911 _ = splat;
912 return cl_write(context, data[0]); // TODO: try to send all the data
959 /// Returns `null` if size cannot be computed without making any syscalls.
960 fn countWriteFile(limit: std.io.Writer.Limit, headers_and_trailers: []const []const u8) ?usize {
961 var total: usize = limit.toInt() orelse return null;
962 for (headers_and_trailers) |buf| total += buf.len;
963 return total;
913964 }
914965
915 fn cl_writeFile(
966 fn noneWriteFile(
916967 context: ?*anyopaque,
917968 file: std.fs.File,
918969 offset: std.io.Writer.Offset,
919970 limit: std.io.Writer.Limit,
920971 headers_and_trailers: []const []const u8,
921972 headers_len: usize,
922 ) std.io.Writer.Error!usize {
923 _ = context;
924 _ = file;
925 _ = offset;
926 _ = limit;
927 _ = headers_and_trailers;
928 _ = headers_len;
929 @panic("TODO");
930 }
931
932 fn cl_write(context: ?*anyopaque, bytes: []const u8) std.io.Writer.Error!usize {
973 ) std.io.Writer.FileError!usize {
974 if (limit == .nothing) return noneWriteSplat(context, headers_and_trailers, 1);
933975 const r: *Response = @alignCast(@ptrCast(context));
934
935 var trash: u64 = std.math.maxInt(u64);
936 const len = switch (r.transfer_encoding) {
937 .content_length => |*len| len,
938 else => &trash,
939 };
940
941 if (r.elide_body) {
942 len.* -= bytes.len;
943 return bytes.len;
944 }
945
946 if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
947 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
948 var iovecs: [2][]const u8 = .{
949 r.send_buffer[r.send_buffer_start..][0..send_buffer_len],
950 bytes,
951 };
952 const n = try r.out.writeVec(&iovecs);
953
954 if (n >= send_buffer_len) {
955 // It was enough to reset the buffer.
956 r.send_buffer_start = 0;
957 r.send_buffer_end = 0;
958 const bytes_n = n - send_buffer_len;
959 len.* -= bytes_n;
960 return bytes_n;
961 }
962
963 // It didn't even make it through the existing buffer, let
964 // alone the new bytes provided.
965 r.send_buffer_start += n;
966 return 0;
967 }
968
969 // All bytes can be stored in the remaining space of the buffer.
970 @memcpy(r.send_buffer[r.send_buffer_end..][0..bytes.len], bytes);
971 r.send_buffer_end += bytes.len;
972 len.* -= bytes.len;
973 return bytes.len;
976 if (r.elide_body) return elideWriteFile(r, offset, limit, headers_and_trailers);
977 return r.server_output.writeFile(file, offset, limit, headers_and_trailers, headers_len);
974978 }
975979
976 fn chunked_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {
977 _ = splat;
978 return chunked_write(context, data[0]); // TODO: try to send all the data
980 fn contentLengthWriteFile(
981 context: ?*anyopaque,
982 file: std.fs.File,
983 offset: std.io.Writer.Offset,
984 limit: std.io.Writer.Limit,
985 headers_and_trailers: []const []const u8,
986 headers_len: usize,
987 ) std.io.Writer.FileError!usize {
988 if (limit == .nothing) return contentLengthWriteSplat(context, headers_and_trailers, 1);
989 const r: *Response = @alignCast(@ptrCast(context));
990 if (r.elide_body) return elideWriteFile(r, offset, limit, headers_and_trailers);
991 const n = try r.server_output.writeFile(file, offset, limit, headers_and_trailers, headers_len);
992 r.transfer_encoding.content_length -= n;
993 return n;
979994 }
980995
981 fn chunked_writeFile(
996 fn chunkedWriteFile(
982997 context: ?*anyopaque,
983998 file: std.fs.File,
984999 offset: std.io.Writer.Offset,
9851000 limit: std.io.Writer.Limit,
9861001 headers_and_trailers: []const []const u8,
9871002 headers_len: usize,
988 ) std.io.Writer.Error!usize {
989 _ = context;
990 _ = file;
991 _ = offset;
992 _ = limit;
993 _ = headers_and_trailers;
994 _ = headers_len;
995 @panic("TODO"); // TODO lower to a call to writeFile on the output
1003 ) std.io.Writer.FileError!usize {
1004 if (limit == .nothing) return chunkedWriteSplat(context, headers_and_trailers, 1);
1005 const r: *Response = @alignCast(@ptrCast(context));
1006 if (r.elide_body) return elideWriteFile(r, offset, limit, headers_and_trailers);
1007 const data_len = countWriteFile(limit, headers_and_trailers) orelse @panic("TODO");
1008 const bw = r.server_output;
1009 const chunked = &r.transfer_encoding.chunked;
1010 state: switch (chunked.*) {
1011 .offset => |off| {
1012 // TODO: is it better perf to read small files into the buffer?
1013 const buffered_len = bw.end - off - chunk_header_template.len;
1014 const chunk_len = data_len + buffered_len;
1015 writeHex(bw.buffer[off..][0..chunk_len_digits], chunk_len);
1016 const n = try bw.writeFile(file, offset, limit, headers_and_trailers, headers_len);
1017 chunked.* = .{ .chunk_len = data_len + 2 - n };
1018 return n;
1019 },
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 }
1046 },
1047 }
9961048 }
9971049
998 fn chunked_write(context: ?*anyopaque, bytes: []const u8) std.io.Writer.Error!usize {
1050 fn chunkedWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) WriteError!usize {
9991051 const r: *Response = @alignCast(@ptrCast(context));
1000 assert(r.transfer_encoding == .chunked);
1052 const data_len = countSplat(data, splat);
1053 if (r.elide_body) return data_len;
10011054
1002 if (r.elide_body)
1003 return bytes.len;
1004
1005 if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
1006 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
1007 const chunk_len = r.chunk_len + bytes.len;
1008 var header_buf: [18]u8 = undefined;
1009 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{chunk_len}) catch unreachable;
1010
1011 var iovecs: [5][]const u8 = .{
1012 r.send_buffer[r.send_buffer_start .. send_buffer_len - r.chunk_len],
1013 chunk_header,
1014 r.send_buffer[r.send_buffer_end - r.chunk_len ..][0..r.chunk_len],
1015 bytes,
1016 "\r\n",
1017 };
1018 // TODO make this writev instead of writevAll, which involves
1019 // complicating the logic of this function.
1020 try r.out.writeVecAll(&iovecs);
1021 r.send_buffer_start = 0;
1022 r.send_buffer_end = 0;
1023 r.chunk_len = 0;
1024 return bytes.len;
1025 }
1055 const bw = r.server_output;
1056 const chunked = &r.transfer_encoding.chunked;
10261057
1027 // All bytes can be stored in the remaining space of the buffer.
1028 @memcpy(r.send_buffer[r.send_buffer_end..][0..bytes.len], bytes);
1029 r.send_buffer_end += bytes.len;
1030 r.chunk_len += bytes.len;
1031 return bytes.len;
1058 state: switch (chunked.*) {
1059 .offset => |offset| {
1060 if (bw.unusedCapacitySlice().len >= data_len) {
1061 assert(data_len == (bw.writeSplat(data, splat) catch unreachable));
1062 return data_len;
1063 }
1064 const buffered_len = bw.end - offset - chunk_header_template.len;
1065 const chunk_len = data_len + buffered_len;
1066 writeHex(bw.buffer[offset..][0..chunk_len_digits], chunk_len);
1067 const n = try bw.writeSplat(data, splat);
1068 chunked.* = .{ .chunk_len = data_len + 2 - n };
1069 return n;
1070 },
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 }
1096 },
1097 }
10321098 }
10331099
1034 /// If using content-length, asserts that writing these bytes to the client
1035 /// would not exceed the content-length value sent in the HTTP header.
1036 pub fn writeAll(r: *Response, bytes: []const u8) std.io.Writer.Error!void {
1037 var index: usize = 0;
1038 while (index < bytes.len) {
1039 index += try write(r, bytes[index..]);
1100 /// Writes an integer as base 16 to `buf`, right-aligned, assuming the
1101 /// buffer has already been filled with zeroes.
1102 fn writeHex(buf: []u8, x: usize) void {
1103 assert(std.mem.allEqual(u8, buf, '0'));
1104 const base = 16;
1105 var index: usize = buf.len;
1106 var a = x;
1107 while (a > 0) {
1108 const digit = a % base;
1109 index -= 1;
1110 buf[index] = std.fmt.digitToChar(@intCast(digit), .lower);
1111 a /= base;
10401112 }
10411113 }
10421114
10431115 /// Sends all buffered data to the client.
10441116 /// This is redundant after calling `end`.
10451117 /// Respects the value of `elide_body` to omit all data after the headers.
1046 pub fn flush(r: *Response) std.io.Writer.Error!void {
1118 pub fn flush(r: *Response) Error!void {
10471119 switch (r.transfer_encoding) {
1048 .none, .content_length => return flush_cl(r),
1049 .chunked => return flush_chunked(r, null),
1120 .none, .content_length => return flushContentLength(r),
1121 .chunked => return flushChunked(r, null),
10501122 }
10511123 }
10521124
1053 fn flush_cl(r: *Response) std.io.Writer.Error!void {
1125 fn flushContentLength(r: *Response) Error!void {
10541126 try r.out.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]);
10551127 r.send_buffer_start = 0;
10561128 r.send_buffer_end = 0;
10571129 }
10581130
1059 fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) std.io.Writer.Error!void {
1131 fn flushChunked(r: *Response, end_trailers: ?[]const http.Header) Error!void {
10601132 const max_trailers = 25;
10611133 if (end_trailers) |trailers| assert(trailers.len <= max_trailers);
10621134 assert(r.transfer_encoding == .chunked);
......@@ -1123,17 +1195,21 @@ pub const Response = struct {
11231195
11241196 pub fn writer(r: *Response) std.io.Writer {
11251197 return .{
1198 .context = r,
11261199 .vtable = switch (r.transfer_encoding) {
1127 .none, .content_length => &.{
1128 .writeSplat = cl_writeSplat,
1129 .writeFile = cl_writeFile,
1200 .none => &.{
1201 .writeSplat = noneWriteSplat,
1202 .writeFile = noneWriteFile,
1203 },
1204 .content_length => &.{
1205 .writeSplat = contentLengthWriteSplat,
1206 .writeFile = contentLengthWriteFile,
11301207 },
11311208 .chunked => &.{
1132 .writeSplat = chunked_writeSplat,
1133 .writeFile = chunked_writeFile,
1209 .writeSplat = chunkedWriteSplat,
1210 .writeFile = chunkedWriteFile,
11341211 },
11351212 },
1136 .context = r,
11371213 };
11381214 }
11391215};
lib/std/io/BufferedWriter.zig+48-5
......@@ -84,12 +84,29 @@ pub fn unusedCapacitySlice(bw: *const BufferedWriter) []u8 {
8484}
8585
8686/// Asserts the provided buffer has total capacity enough for `len`.
87pub fn writableArray(bw: *BufferedWriter, comptime len: usize) anyerror!*[len]u8 {
88 return (try bw.writableSlice(len))[0..len];
87///
88/// Advances the buffer end position by `len`.
89pub fn writableArray(bw: *BufferedWriter, comptime len: usize) Writer.Error!*[len]u8 {
90 const big_slice = try bw.writableSliceGreedy(len);
91 advance(bw, len);
92 return big_slice[0..len];
93}
94
95/// Asserts the provided buffer has total capacity enough for `len`.
96///
97/// Advances the buffer end position by `len`.
98pub fn writableSlice(bw: *BufferedWriter, len: usize) Writer.Error![]u8 {
99 const big_slice = try bw.writableSliceGreedy(len);
100 advance(bw, len);
101 return big_slice[0..len];
89102}
90103
91104/// Asserts the provided buffer has total capacity enough for `minimum_length`.
92pub fn writableSlice(bw: *BufferedWriter, minimum_length: usize) Writer.Error![]u8 {
105///
106/// Does not `advance` the buffer end position.
107///
108/// If `minimum_length` is zero, this is equivalent to `unusedCapacitySlice`.
109pub fn writableSliceGreedy(bw: *BufferedWriter, minimum_length: usize) Writer.Error![]u8 {
93110 assert(bw.buffer.len >= minimum_length);
94111 const cap_slice = bw.buffer[bw.end..];
95112 if (cap_slice.len >= minimum_length) {
......@@ -111,7 +128,10 @@ pub fn writableSlice(bw: *BufferedWriter, minimum_length: usize) Writer.Error![]
111128 return bw.buffer[bw.end..];
112129}
113130
114/// After calling `writableSlice`, this function tracks how many bytes were written to it.
131/// After calling `writableSliceGreedy`, this function tracks how many bytes
132/// were written to it.
133///
134/// This is not needed when using `writableSlice` or `writableArray`.
115135pub fn advance(bw: *BufferedWriter, n: usize) void {
116136 const new_end = bw.end + n;
117137 assert(new_end <= bw.buffer.len);
......@@ -135,14 +155,34 @@ pub fn writeVecAll(bw: *BufferedWriter, data: [][]const u8) Writer.Error!void {
135155 }
136156}
137157
158/// If the number of bytes to write based on `data` and `splat` fits inside
159/// `unusedCapacitySlice`, this function is guaranteed to not fail, not call
160/// into the underlying writer, and return the full number of bytes.
138161pub fn writeSplat(bw: *BufferedWriter, data: []const []const u8, splat: usize) Writer.Error!usize {
139162 return passthruWriteSplat(bw, data, splat);
140163}
141164
165/// If the total number of bytes of `data` fits inside `unusedCapacitySlice`,
166/// this function is guaranteed to not fail, not call into the underlying
167/// writer, and return the total bytes inside `data`.
142168pub fn writeVec(bw: *BufferedWriter, data: []const []const u8) Writer.Error!usize {
143169 return passthruWriteSplat(bw, data, 1);
144170}
145171
172/// Equivalent to `writeSplat` but writes at most `limit` bytes.
173pub fn writeSplatLimit(
174 bw: *BufferedWriter,
175 data: []const []const u8,
176 splat: usize,
177 limit: Writer.Limit,
178) Writer.Error!usize {
179 _ = bw;
180 _ = data;
181 _ = splat;
182 _ = limit;
183 @panic("TODO");
184}
185
146186fn passthruWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Error!usize {
147187 const bw: *BufferedWriter = @alignCast(@ptrCast(context));
148188 const buffer = bw.buffer;
......@@ -435,6 +475,9 @@ pub fn writeArraySwap(bw: *BufferedWriter, Elem: type, array: []const Elem) Writ
435475 @panic("TODO");
436476}
437477
478/// Unlike `writeSplat` and `writeVec`, this function will call into the
479/// underlying writer even if there is enough buffer capacity for the file
480/// contents.
438481pub fn writeFile(
439482 bw: *BufferedWriter,
440483 file: std.fs.File,
......@@ -1400,7 +1443,7 @@ fn writeMultipleOf7Leb128(bw: *BufferedWriter, value: anytype) Writer.Error!void
14001443 comptime assert(value_info.bits % 7 == 0);
14011444 var remaining = value;
14021445 while (true) {
1403 const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try bw.writableSlice(1));
1446 const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try bw.writableSliceGreedy(1));
14041447 for (buffer, 1..) |*byte, len| {
14051448 const more = switch (value_info.signedness) {
14061449 .signed => remaining >> 6 != remaining >> (value_info.bits - 1),
lib/std/io/Reader.zig+5-1
......@@ -77,7 +77,11 @@ pub const Limit = enum(usize) {
7777 return @enumFromInt(n);
7878 }
7979
80 pub fn min(l: Limit, n: usize) usize {
80 pub fn min(a: Limit, b: Limit) Limit {
81 return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b)));
82 }
83
84 pub fn minInt(l: Limit, n: usize) usize {
8185 return @min(n, @intFromEnum(l));
8286 }
8387
lib/std/io/Writer.zig+5-3
......@@ -33,10 +33,12 @@ pub const VTable = struct {
3333 writeFile: *const fn (
3434 ctx: ?*anyopaque,
3535 file: std.fs.File,
36 /// If this is `none`, `file` will be streamed. Otherwise, it will be
37 /// read positionally without affecting the seek position.
36 /// If this is `none`, `file` will be streamed, affecting the seek
37 /// position. Otherwise, it will be read positionally without affecting
38 /// the seek position.
3839 offset: Offset,
39 /// Maximum amount of bytes to read from the file.
40 /// Maximum amount of bytes to read from the file. Implementations may
41 /// assume that the file size does not exceed this amount.
4042 limit: Limit,
4143 /// Headers and trailers must be passed together so that in case `len` is
4244 /// zero, they can be forwarded directly to `VTable.writeVec`.
lib/std/math/big/int.zig+2-2
......@@ -2344,11 +2344,11 @@ pub const Const = struct {
23442344
23452345 const max_str_len = self.sizeInBaseUpperBound(base);
23462346 const limbs_len = calcToStringLimbsBufferLen(self.limbs.len, base);
2347 if (bw.writableSlice(max_str_len + @alignOf(Limb) - 1 + @sizeOf(Limb) * limbs_len)) |buf| {
2347 if (bw.writableSliceGreedy(max_str_len + @alignOf(Limb) - 1 + @sizeOf(Limb) * limbs_len)) |buf| {
23482348 const limbs: [*]Limb = @alignCast(@ptrCast(std.mem.alignPointer(buf[max_str_len..].ptr, @alignOf(Limb))));
23492349 bw.advance(self.toString(buf[0..max_str_len], base, case, limbs[0..limbs_len]));
23502350 return;
2351 } else |_| if (bw.writableSlice(max_str_len)) |buf| {
2351 } else |_| if (bw.writableSliceGreedy(max_str_len)) |buf| {
23522352 const available_len = 64;
23532353 var limbs: [calcToStringLimbsBufferLen(available_len, base)]Limb = undefined;
23542354 if (limbs.len >= limbs_len) {
lib/std/net.zig+13-6
......@@ -750,7 +750,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {
750750 );
751751 errdefer Stream.close(.{ .handle = sockfd });
752752
753 var addr = try std.net.Address.initUnix(path);
753 var addr = try Address.initUnix(path);
754754 try posix.connect(sockfd, &addr.any, addr.getOsSockLen());
755755
756756 return .{ .handle = sockfd };
......@@ -1859,7 +1859,7 @@ pub const Stream = struct {
18591859 bw: *std.io.BufferedWriter,
18601860 limit: std.io.Reader.Limit,
18611861 ) std.io.Reader.Error!usize {
1862 const buf = limit.slice(try bw.writableSlice(1));
1862 const buf = limit.slice(try bw.writableSliceGreedy(1));
18631863 const status = try windows_readVec(context, &.{buf});
18641864 bw.advance(status.len);
18651865 return status;
......@@ -2080,7 +2080,11 @@ pub const Stream = struct {
20802080 return switch (native_os) {
20812081 .windows => .{ .impl = stream },
20822082 else => .{ .impl = .{
2083 .fr = std.fs.File.reader(.{ .handle = stream.handle }),
2083 .fr = .{
2084 .file = .{ .handle = stream.handle },
2085 .mode = .streaming,
2086 .seek_err = error.Unseekable,
2087 },
20842088 .err = {},
20852089 } },
20862090 };
......@@ -2090,7 +2094,10 @@ pub const Stream = struct {
20902094 return switch (native_os) {
20912095 .windows => .{ .impl = stream },
20922096 else => .{ .impl = .{
2093 .fw = std.fs.File.writer(.{ .handle = stream.handle }),
2097 .fw = .{
2098 .file = .{ .handle = stream.handle },
2099 .mode = .streaming,
2100 },
20942101 .err = {},
20952102 } },
20962103 };
......@@ -2101,10 +2108,10 @@ pub const Stream = struct {
21012108
21022109pub const Server = struct {
21032110 listen_address: Address,
2104 stream: std.net.Stream,
2111 stream: Stream,
21052112
21062113 pub const Connection = struct {
2107 stream: std.net.Stream,
2114 stream: Stream,
21082115 address: Address,
21092116 };
21102117
src/codegen.zig+3-6
......@@ -386,8 +386,7 @@ pub fn generateSymbolInner(
386386 const abi_size = math.cast(usize, ty.abiSize(zcu)) orelse return error.Overflow;
387387 var space: Value.BigIntSpace = undefined;
388388 const int_val = val.toBigInt(&space, zcu);
389 int_val.writeTwosComplement((try bw.writableSlice(abi_size))[0..abi_size], endian);
390 bw.advance(abi_size);
389 int_val.writeTwosComplement((try bw.writableSlice(abi_size)), endian);
391390 },
392391 .err => |err| {
393392 const int = try pt.getErrorValue(err.name);
......@@ -498,7 +497,7 @@ pub fn generateSymbolInner(
498497 .vector_type => |vector_type| {
499498 const abi_size = math.cast(usize, ty.abiSize(zcu)) orelse return error.Overflow;
500499 if (vector_type.child == .bool_type) {
501 const buffer = (try bw.writableSlice(abi_size))[0..abi_size];
500 const buffer = try bw.writableSlice(abi_size);
502501 @memset(buffer, 0xaa);
503502 var index: usize = 0;
504503 const len = math.cast(usize, vector_type.len) orelse return error.Overflow;
......@@ -535,7 +534,6 @@ pub fn generateSymbolInner(
535534 },
536535 }) byte.* |= mask else byte.* &= ~mask;
537536 }
538 bw.advance(abi_size);
539537 } else {
540538 switch (aggregate.storage) {
541539 .bytes => |bytes| try bw.writeAll(bytes.toSlice(vector_type.len, ip)),
......@@ -592,7 +590,7 @@ pub fn generateSymbolInner(
592590 .@"packed" => {
593591 const abi_size = math.cast(usize, ty.abiSize(zcu)) orelse return error.Overflow;
594592 const current_end, const current_count = .{ bw.end, bw.count };
595 const buffer = (try bw.writableSlice(abi_size))[0..abi_size];
593 const buffer = try bw.writableSlice(abi_size);
596594 @memset(buffer, 0);
597595 var bits: u16 = 0;
598596
......@@ -628,7 +626,6 @@ pub fn generateSymbolInner(
628626 }
629627 bits += @intCast(Type.fromInterned(field_ty).bitSize(zcu));
630628 }
631 bw.advance(abi_size);
632629 },
633630 .auto, .@"extern" => {
634631 const struct_begin = bw.count;
src/link/Dwarf.zig+6-10
......@@ -659,8 +659,7 @@ const Unit = struct {
659659 .eq => {
660660 // no length will ever work, so undercount and futz with the leb encoding to make up the missing byte
661661 op_len_bytes += 1;
662 std.leb.writeUnsignedExtended((bw.writableSlice(op_len_bytes) catch unreachable)[0..op_len_bytes], len - extended_op_bytes - op_len_bytes);
663 bw.advance(op_len_bytes);
662 std.leb.writeUnsignedExtended((bw.writableSlice(op_len_bytes) catch unreachable), len - extended_op_bytes - op_len_bytes);
664663 break;
665664 },
666665 .gt => op_len_bytes += 1,
......@@ -849,8 +848,7 @@ const Entry = struct {
849848 .eq => {
850849 // no length will ever work, so undercount and futz with the leb encoding to make up the missing byte
851850 block_len_bytes += 1;
852 std.leb.writeUnsignedExtended((try bw.writableSlice(block_len_bytes))[0..block_len_bytes], len - abbrev_code_bytes - block_len_bytes);
853 bw.advance(block_len_bytes);
851 std.leb.writeUnsignedExtended((try bw.writableSlice(block_len_bytes)), len - abbrev_code_bytes - block_len_bytes);
854852 break;
855853 },
856854 .gt => block_len_bytes += 1,
......@@ -870,10 +868,9 @@ const Entry = struct {
870868 // no length will ever work, so undercount and futz with the leb encoding to make up the missing byte
871869 op_len_bytes += 1;
872870 std.leb.writeUnsignedExtended(
873 (bw.writableSlice(op_len_bytes) catch unreachable)[0..op_len_bytes],
871 (bw.writableSlice(op_len_bytes) catch unreachable),
874872 len - extended_op_bytes - op_len_bytes,
875873 );
876 bw.advance(op_len_bytes);
877874 break;
878875 },
879876 .gt => op_len_bytes += 1,
......@@ -2009,7 +2006,7 @@ pub const WipNav = struct {
20092006 .signed => abbrev_code.sdata,
20102007 .unsigned => abbrev_code.udata,
20112008 });
2012 _ = try dibw.writableSlice(std.math.divCeil(usize, bits, 7) catch unreachable);
2009 _ = try dibw.writableSliceGreedy(std.math.divCeil(usize, bits, 7) catch unreachable);
20132010 var bit: usize = 0;
20142011 var carry: u1 = 1;
20152012 while (bit < bits) {
......@@ -2033,7 +2030,7 @@ pub const WipNav = struct {
20332030 const bytes = @max(ty.abiSize(zcu), std.math.divCeil(usize, bits, 8) catch unreachable);
20342031 try dibw.writeLeb128(bytes);
20352032 big_int.writeTwosComplement(
2036 try dibw.writableSlice(@intCast(bytes)),
2033 try dibw.writableSliceGreedy(@intCast(bytes)),
20372034 wip_nav.dwarf.endian,
20382035 );
20392036 dibw.advance(@intCast(bytes));
......@@ -6083,8 +6080,7 @@ fn writeInt(dwarf: *Dwarf, buf: []u8, int: u64) void {
60836080}
60846081
60856082fn writeIntTo(dwarf: *Dwarf, bw: *std.io.BufferedWriter, len: usize, int: u64) !void {
6086 dwarf.writeInt((try bw.writableSlice(len))[0..len], int);
6087 bw.advance(len);
6083 dwarf.writeInt(try bw.writableSlice(len), int);
60886084}
60896085
60906086fn resolveReloc(dwarf: *Dwarf, source: u64, target: u64, size: u32) RelocError!void {
src/link/Wasm/Flush.zig-3
......@@ -1254,7 +1254,6 @@ const vec_section_header_size = section_header_size + size_header_size;
12541254fn reserveVecSectionHeader(bw: *std.io.BufferedWriter) std.io.Writer.Error!u32 {
12551255 const offset = bw.count;
12561256 _ = try bw.writableSlice(vec_section_header_size);
1257 bw.advance(vec_section_header_size);
12581257 return @intCast(offset);
12591258}
12601259
......@@ -1275,7 +1274,6 @@ const section_header_size = 1 + size_header_size;
12751274fn reserveSectionHeader(bw: *std.io.BufferedWriter) std.io.Writer.Error!u32 {
12761275 const offset = bw.count;
12771276 _ = try bw.writableSlice(section_header_size);
1278 bw.advance(section_header_size);
12791277 return @intCast(offset);
12801278}
12811279
......@@ -1290,7 +1288,6 @@ const size_header_size = 5;
12901288fn reserveSizeHeader(bw: *std.io.BufferedWriter) std.io.Writer.Error!u32 {
12911289 const offset = bw.count;
12921290 _ = try bw.writableSlice(size_header_size);
1293 bw.advance(size_header_size);
12941291 return @intCast(offset);
12951292}
12961293
src/link/riscv.zig+1-1
......@@ -38,7 +38,7 @@ pub fn writeAddend(
3838 bw: *std.io.BufferedWriter,
3939) std.io.Writer.Error!void {
4040 const n = @divExact(@bitSizeOf(Int), 8);
41 var V: Int = mem.readInt(Int, (try bw.writableSlice(n))[0..n], .little);
41 var V: Int = mem.readInt(Int, (try bw.writableSliceGreedy(n))[0..n], .little);
4242 const addend: Int = @truncate(value);
4343 switch (op) {
4444 .add => V +|= addend, // TODO: I think saturating arithmetic is correct here