| author | |
| committer | |
| log | 74c56376ee2dfb6100fd8da6cb03425b0e48a779 |
| tree | 26849cc3a5360ad915ab21cdc616a383866d1381 |
| parent | da303bdaf1ae8717df2d4ede9e7dfb215636ae33 |
9 files changed, 449 insertions(+), 420 deletions(-)
lib/std/crypto/tls/Client.zig+1-23| ... | @@ -894,11 +894,7 @@ pub fn init( | ... | @@ -894,11 +894,7 @@ pub fn init( |
| 894 | pub fn reader(c: *Client) Reader { | 894 | pub fn reader(c: *Client) Reader { |
| 895 | return .{ | 895 | return .{ |
| 896 | .context = c, | 896 | .context = c, |
| 897 | .vtable = &.{ | 897 | .vtable = &.{ .read = read }, |
| 898 | .read = read, | ||
| 899 | .readVec = readVec, | ||
| 900 | .discard = discard, | ||
| 901 | }, | ||
| 902 | }; | 898 | }; |
| 903 | } | 899 | } |
| 904 | 900 | ||
| ... | @@ -1225,24 +1221,6 @@ fn read(context: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit) R | ... | @@ -1225,24 +1221,6 @@ fn read(context: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit) R |
| 1225 | } | 1221 | } |
| 1226 | } | 1222 | } |
| 1227 | 1223 | ||
| 1228 | fn readVec(context: ?*anyopaque, data: []const []u8) Reader.Error!usize { | ||
| 1229 | var bw: std.io.BufferedWriter = undefined; | ||
| 1230 | bw.initVec(data); | ||
| 1231 | return read(context, &bw, .countVec(data)) catch |err| switch (err) { | ||
| 1232 | error.WriteFailed => unreachable, | ||
| 1233 | else => |e| return e, | ||
| 1234 | }; | ||
| 1235 | } | ||
| 1236 | |||
| 1237 | fn discard(context: ?*anyopaque, limit: Reader.Limit) Reader.Error!usize { | ||
| 1238 | var null_writer: Writer.Null = undefined; | ||
| 1239 | var bw = null_writer.writer().unbuffered(); | ||
| 1240 | return read(context, &bw, limit) catch |err| switch (err) { | ||
| 1241 | error.WriteFailed => unreachable, | ||
| 1242 | else => |e| return e, | ||
| 1243 | }; | ||
| 1244 | } | ||
| 1245 | |||
| 1246 | fn failRead(c: *Client, err: ReadError) error{ReadFailed} { | 1224 | fn failRead(c: *Client, err: ReadError) error{ReadFailed} { |
| 1247 | c.read_err = err; | 1225 | c.read_err = err; |
| 1248 | return error.ReadFailed; | 1226 | return error.ReadFailed; |
lib/std/fs/File.zig+37-2| ... | @@ -899,6 +899,15 @@ pub fn writeFileAll(self: File, in_file: File, options: BufferedWriter.WriteFile | ... | @@ -899,6 +899,15 @@ pub fn writeFileAll(self: File, in_file: File, options: BufferedWriter.WriteFile |
| 899 | }; | 899 | }; |
| 900 | } | 900 | } |
| 901 | 901 | ||
| 902 | /// Memoizes key information about a file handle such as: | ||
| 903 | /// * The size from calling stat, or the error that occurred therein. | ||
| 904 | /// * The current seek position. | ||
| 905 | /// * The error that occurred when trying to seek. | ||
| 906 | /// * Whether reading should be done positionally or streaming. | ||
| 907 | /// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`) | ||
| 908 | /// versus plain variants (e.g. `read`). | ||
| 909 | /// | ||
| 910 | /// Fulfills the `std.io.Reader` interface. | ||
| 902 | pub const Reader = struct { | 911 | pub const Reader = struct { |
| 903 | file: File, | 912 | file: File, |
| 904 | err: ?ReadError = null, | 913 | err: ?ReadError = null, |
| ... | @@ -951,14 +960,40 @@ pub const Reader = struct { | ... | @@ -951,14 +960,40 @@ pub const Reader = struct { |
| 951 | }; | 960 | }; |
| 952 | } | 961 | } |
| 953 | 962 | ||
| 963 | pub fn seekBy(r: *Reader, offset: i64) SeekError!void { | ||
| 964 | switch (r.mode) { | ||
| 965 | .positional, .positional_reading => { | ||
| 966 | r.pos += offset; | ||
| 967 | }, | ||
| 968 | .streaming, .streaming_reading => { | ||
| 969 | const seek_err = r.seek_err orelse e: { | ||
| 970 | if (posix.lseek_CUR(r.file.handle, offset)) |_| { | ||
| 971 | r.pos += offset; | ||
| 972 | return; | ||
| 973 | } else |err| { | ||
| 974 | r.seek_err = err; | ||
| 975 | break :e err; | ||
| 976 | } | ||
| 977 | }; | ||
| 978 | if (offset < 0) return seek_err; | ||
| 979 | var remaining = offset; | ||
| 980 | while (remaining > 0) { | ||
| 981 | const n = discard(r, .limited(remaining)) catch |err| switch (err) {}; | ||
| 982 | r.pos += n; | ||
| 983 | remaining -= n; | ||
| 984 | } | ||
| 985 | }, | ||
| 986 | } | ||
| 987 | } | ||
| 988 | |||
| 954 | pub fn seekTo(r: *Reader, offset: u64) SeekError!void { | 989 | pub fn seekTo(r: *Reader, offset: u64) SeekError!void { |
| 955 | // TODO if the offset is after the current offset, seek by discarding. | ||
| 956 | if (r.seek_err) |err| return err; | ||
| 957 | switch (r.mode) { | 990 | switch (r.mode) { |
| 958 | .positional, .positional_reading => { | 991 | .positional, .positional_reading => { |
| 959 | r.pos = offset; | 992 | r.pos = offset; |
| 960 | }, | 993 | }, |
| 961 | .streaming, .streaming_reading => { | 994 | .streaming, .streaming_reading => { |
| 995 | if (offset >= r.pos) return Reader.seekBy(r, offset - r.pos); | ||
| 996 | if (r.seek_err) |err| return err; | ||
| 962 | posix.lseek_SET(r.file.handle, offset) catch |err| { | 997 | posix.lseek_SET(r.file.handle, offset) catch |err| { |
| 963 | r.seek_err = err; | 998 | r.seek_err = err; |
| 964 | return err; | 999 | return err; |
lib/std/http.zig+8-10| ... | @@ -7,7 +7,6 @@ pub const Server = @import("http/Server.zig"); | ... | @@ -7,7 +7,6 @@ pub const Server = @import("http/Server.zig"); |
| 7 | pub const HeadParser = @import("http/HeadParser.zig"); | 7 | pub const HeadParser = @import("http/HeadParser.zig"); |
| 8 | pub const ChunkParser = @import("http/ChunkParser.zig"); | 8 | pub const ChunkParser = @import("http/ChunkParser.zig"); |
| 9 | pub const HeaderIterator = @import("http/HeaderIterator.zig"); | 9 | pub const HeaderIterator = @import("http/HeaderIterator.zig"); |
| 10 | pub const WebSocket = @import("http/WebSocket.zig"); | ||
| 11 | 10 | ||
| 12 | pub const Version = enum { | 11 | pub const Version = enum { |
| 13 | @"HTTP/1.0", | 12 | @"HTTP/1.0", |
| ... | @@ -508,7 +507,7 @@ pub const Reader = struct { | ... | @@ -508,7 +507,7 @@ pub const Reader = struct { |
| 508 | fn contentLengthRead( | 507 | fn contentLengthRead( |
| 509 | ctx: ?*anyopaque, | 508 | ctx: ?*anyopaque, |
| 510 | bw: *std.io.BufferedWriter, | 509 | bw: *std.io.BufferedWriter, |
| 511 | limit: std.io.Reader.Limit, | 510 | limit: std.io.Limit, |
| 512 | ) std.io.Reader.RwError!usize { | 511 | ) std.io.Reader.RwError!usize { |
| 513 | const reader: *Reader = @alignCast(@ptrCast(ctx)); | 512 | const reader: *Reader = @alignCast(@ptrCast(ctx)); |
| 514 | const remaining_content_length = &reader.state.body_remaining_content_length; | 513 | const remaining_content_length = &reader.state.body_remaining_content_length; |
| ... | @@ -535,7 +534,7 @@ pub const Reader = struct { | ... | @@ -535,7 +534,7 @@ pub const Reader = struct { |
| 535 | return n; | 534 | return n; |
| 536 | } | 535 | } |
| 537 | 536 | ||
| 538 | fn contentLengthDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize { | 537 | fn contentLengthDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize { |
| 539 | const reader: *Reader = @alignCast(@ptrCast(ctx)); | 538 | const reader: *Reader = @alignCast(@ptrCast(ctx)); |
| 540 | const remaining_content_length = &reader.state.body_remaining_content_length; | 539 | const remaining_content_length = &reader.state.body_remaining_content_length; |
| 541 | const remaining = remaining_content_length.*; | 540 | const remaining = remaining_content_length.*; |
| ... | @@ -551,7 +550,7 @@ pub const Reader = struct { | ... | @@ -551,7 +550,7 @@ pub const Reader = struct { |
| 551 | fn chunkedRead( | 550 | fn chunkedRead( |
| 552 | ctx: ?*anyopaque, | 551 | ctx: ?*anyopaque, |
| 553 | bw: *std.io.BufferedWriter, | 552 | bw: *std.io.BufferedWriter, |
| 554 | limit: std.io.Reader.Limit, | 553 | limit: std.io.Limit, |
| 555 | ) std.io.Reader.RwError!usize { | 554 | ) std.io.Reader.RwError!usize { |
| 556 | const reader: *Reader = @alignCast(@ptrCast(ctx)); | 555 | const reader: *Reader = @alignCast(@ptrCast(ctx)); |
| 557 | const chunk_len_ptr = switch (reader.state) { | 556 | const chunk_len_ptr = switch (reader.state) { |
| ... | @@ -576,7 +575,7 @@ pub const Reader = struct { | ... | @@ -576,7 +575,7 @@ pub const Reader = struct { |
| 576 | fn chunkedReadEndless( | 575 | fn chunkedReadEndless( |
| 577 | reader: *Reader, | 576 | reader: *Reader, |
| 578 | bw: *std.io.BufferedWriter, | 577 | bw: *std.io.BufferedWriter, |
| 579 | limit: std.io.Reader.Limit, | 578 | limit: std.io.Limit, |
| 580 | chunk_len_ptr: *RemainingChunkLen, | 579 | chunk_len_ptr: *RemainingChunkLen, |
| 581 | ) (BodyError || std.io.Reader.RwError)!usize { | 580 | ) (BodyError || std.io.Reader.RwError)!usize { |
| 582 | const in = reader.in; | 581 | const in = reader.in; |
| ... | @@ -712,7 +711,7 @@ pub const Reader = struct { | ... | @@ -712,7 +711,7 @@ pub const Reader = struct { |
| 712 | return amt_read; | 711 | return amt_read; |
| 713 | } | 712 | } |
| 714 | 713 | ||
| 715 | fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize { | 714 | fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize { |
| 716 | const reader: *Reader = @alignCast(@ptrCast(ctx)); | 715 | const reader: *Reader = @alignCast(@ptrCast(ctx)); |
| 717 | const chunk_len_ptr = switch (reader.state) { | 716 | const chunk_len_ptr = switch (reader.state) { |
| 718 | .ready => return error.EndOfStream, | 717 | .ready => return error.EndOfStream, |
| ... | @@ -734,7 +733,7 @@ pub const Reader = struct { | ... | @@ -734,7 +733,7 @@ pub const Reader = struct { |
| 734 | 733 | ||
| 735 | fn chunkedDiscardEndless( | 734 | fn chunkedDiscardEndless( |
| 736 | reader: *Reader, | 735 | reader: *Reader, |
| 737 | limit: std.io.Reader.Limit, | 736 | limit: std.io.Limit, |
| 738 | chunk_len_ptr: *RemainingChunkLen, | 737 | chunk_len_ptr: *RemainingChunkLen, |
| 739 | ) (BodyError || std.io.Reader.Error)!usize { | 738 | ) (BodyError || std.io.Reader.Error)!usize { |
| 740 | const in = reader.in; | 739 | const in = reader.in; |
| ... | @@ -812,8 +811,8 @@ pub const Decompressor = struct { | ... | @@ -812,8 +811,8 @@ pub const Decompressor = struct { |
| 812 | buffered_reader: std.io.BufferedReader, | 811 | buffered_reader: std.io.BufferedReader, |
| 813 | 812 | ||
| 814 | pub const Compression = union(enum) { | 813 | pub const Compression = union(enum) { |
| 815 | deflate: std.compress.zlib.Decompressor, | 814 | deflate: std.compress.flate.Decompressor, |
| 816 | gzip: std.compress.gzip.Decompressor, | 815 | gzip: std.compress.flate.Decompressor, |
| 817 | zstd: std.compress.zstd.Decompress, | 816 | zstd: std.compress.zstd.Decompress, |
| 818 | none: void, | 817 | none: void, |
| 819 | }; | 818 | }; |
| ... | @@ -1238,7 +1237,6 @@ test { | ... | @@ -1238,7 +1237,6 @@ test { |
| 1238 | _ = Method; | 1237 | _ = Method; |
| 1239 | _ = ChunkParser; | 1238 | _ = ChunkParser; |
| 1240 | _ = HeadParser; | 1239 | _ = HeadParser; |
| 1241 | _ = WebSocket; | ||
| 1242 | 1240 | ||
| 1243 | if (builtin.os.tag != .wasi) { | 1241 | if (builtin.os.tag != .wasi) { |
| 1244 | _ = Client; | 1242 | _ = Client; |
lib/std/http/Server.zig+288-63| ... | @@ -57,6 +57,12 @@ pub const Request = struct { | ... | @@ -57,6 +57,12 @@ pub const Request = struct { |
| 57 | /// Pointers in this struct are invalidated with the next call to | 57 | /// Pointers in this struct are invalidated with the next call to |
| 58 | /// `receiveHead`. | 58 | /// `receiveHead`. |
| 59 | head: Head, | 59 | head: Head, |
| 60 | respond_err: ?RespondError, | ||
| 61 | |||
| 62 | pub const RespondError = error{ | ||
| 63 | /// The request contained an `expect` header with an unrecognized value. | ||
| 64 | HttpExpectationFailed, | ||
| 65 | }; | ||
| 60 | 66 | ||
| 61 | pub const Head = struct { | 67 | pub const Head = struct { |
| 62 | method: http.Method, | 68 | method: http.Method, |
| ... | @@ -306,7 +312,7 @@ pub const Request = struct { | ... | @@ -306,7 +312,7 @@ pub const Request = struct { |
| 306 | request: *Request, | 312 | request: *Request, |
| 307 | content: []const u8, | 313 | content: []const u8, |
| 308 | options: RespondOptions, | 314 | options: RespondOptions, |
| 309 | ) std.io.Writer.Error!void { | 315 | ) ExpectContinueError!void { |
| 310 | try respondUnflushed(request, content, options); | 316 | try respondUnflushed(request, content, options); |
| 311 | try request.server.out.flush(); | 317 | try request.server.out.flush(); |
| 312 | } | 318 | } |
| ... | @@ -315,7 +321,7 @@ pub const Request = struct { | ... | @@ -315,7 +321,7 @@ pub const Request = struct { |
| 315 | request: *Request, | 321 | request: *Request, |
| 316 | content: []const u8, | 322 | content: []const u8, |
| 317 | options: RespondOptions, | 323 | options: RespondOptions, |
| 318 | ) std.io.Writer.Error!void { | 324 | ) ExpectContinueError!void { |
| 319 | assert(options.status != .@"continue"); | 325 | assert(options.status != .@"continue"); |
| 320 | if (std.debug.runtime_safety) { | 326 | if (std.debug.runtime_safety) { |
| 321 | for (options.extra_headers) |header| { | 327 | for (options.extra_headers) |header| { |
| ... | @@ -325,6 +331,7 @@ pub const Request = struct { | ... | @@ -325,6 +331,7 @@ pub const Request = struct { |
| 325 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); | 331 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); |
| 326 | } | 332 | } |
| 327 | } | 333 | } |
| 334 | try writeExpectContinue(request); | ||
| 328 | 335 | ||
| 329 | const transfer_encoding_none = (options.transfer_encoding orelse .chunked) == .none; | 336 | const transfer_encoding_none = (options.transfer_encoding orelse .chunked) == .none; |
| 330 | const server_keep_alive = !transfer_encoding_none and options.keep_alive; | 337 | const server_keep_alive = !transfer_encoding_none and options.keep_alive; |
| ... | @@ -333,17 +340,6 @@ pub const Request = struct { | ... | @@ -333,17 +340,6 @@ pub const Request = struct { |
| 333 | const phrase = options.reason orelse options.status.phrase() orelse ""; | 340 | const phrase = options.reason orelse options.status.phrase() orelse ""; |
| 334 | 341 | ||
| 335 | const out = request.server.out; | 342 | const out = request.server.out; |
| 336 | if (request.head.expect != null) { | ||
| 337 | // reader() and hence discardBody() above sets expect to null if it | ||
| 338 | // is handled. So the fact that it is not null here means unhandled. | ||
| 339 | var vecs: [3][]const u8 = .{ | ||
| 340 | "HTTP/1.1 417 Expectation Failed\r\n", | ||
| 341 | if (keep_alive) "" else "connection: close\r\n", | ||
| 342 | "content-length: 0\r\n\r\n", | ||
| 343 | }; | ||
| 344 | try out.writeVecAll(&vecs); | ||
| 345 | return; | ||
| 346 | } | ||
| 347 | try out.print("{s} {d} {s}\r\n", .{ | 343 | try out.print("{s} {d} {s}\r\n", .{ |
| 348 | @tagName(options.version), @intFromEnum(options.status), phrase, | 344 | @tagName(options.version), @intFromEnum(options.status), phrase, |
| 349 | }); | 345 | }); |
| ... | @@ -402,6 +398,7 @@ pub const Request = struct { | ... | @@ -402,6 +398,7 @@ pub const Request = struct { |
| 402 | /// | 398 | /// |
| 403 | /// Asserts status is not `continue`. | 399 | /// Asserts status is not `continue`. |
| 404 | pub fn respondStreaming(request: *Request, options: RespondStreamingOptions) std.io.Writer.Error!http.BodyWriter { | 400 | pub fn respondStreaming(request: *Request, options: RespondStreamingOptions) std.io.Writer.Error!http.BodyWriter { |
| 401 | try writeExpectContinue(request); | ||
| 405 | const o = options.respond_options; | 402 | const o = options.respond_options; |
| 406 | assert(o.status != .@"continue"); | 403 | assert(o.status != .@"continue"); |
| 407 | const transfer_encoding_none = (o.transfer_encoding orelse .chunked) == .none; | 404 | const transfer_encoding_none = (o.transfer_encoding orelse .chunked) == .none; |
| ... | @@ -410,43 +407,34 @@ pub const Request = struct { | ... | @@ -410,43 +407,34 @@ pub const Request = struct { |
| 410 | const phrase = o.reason orelse o.status.phrase() orelse ""; | 407 | const phrase = o.reason orelse o.status.phrase() orelse ""; |
| 411 | const out = request.server.out; | 408 | const out = request.server.out; |
| 412 | 409 | ||
| 413 | const elide_body = if (request.head.expect != null) eb: { | 410 | try out.print("{s} {d} {s}\r\n", .{ |
| 414 | // reader() and hence discardBody() above sets expect to null if it | 411 | @tagName(o.version), @intFromEnum(o.status), phrase, |
| 415 | // is handled. So the fact that it is not null here means unhandled. | 412 | }); |
| 416 | try out.writeAll("HTTP/1.1 417 Expectation Failed\r\n"); | ||
| 417 | if (!keep_alive) try out.writeAll("connection: close\r\n"); | ||
| 418 | try out.writeAll("content-length: 0\r\n\r\n"); | ||
| 419 | break :eb true; | ||
| 420 | } else eb: { | ||
| 421 | try out.print("{s} {d} {s}\r\n", .{ | ||
| 422 | @tagName(o.version), @intFromEnum(o.status), phrase, | ||
| 423 | }); | ||
| 424 | |||
| 425 | switch (o.version) { | ||
| 426 | .@"HTTP/1.0" => if (keep_alive) try out.writeAll("connection: keep-alive\r\n"), | ||
| 427 | .@"HTTP/1.1" => if (!keep_alive) try out.writeAll("connection: close\r\n"), | ||
| 428 | } | ||
| 429 | 413 | ||
| 430 | if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) { | 414 | switch (o.version) { |
| 431 | .chunked => try out.writeAll("transfer-encoding: chunked\r\n"), | 415 | .@"HTTP/1.0" => if (keep_alive) try out.writeAll("connection: keep-alive\r\n"), |
| 432 | .none => {}, | 416 | .@"HTTP/1.1" => if (!keep_alive) try out.writeAll("connection: close\r\n"), |
| 433 | } else if (options.content_length) |len| { | 417 | } |
| 434 | try out.print("content-length: {d}\r\n", .{len}); | ||
| 435 | } else { | ||
| 436 | try out.writeAll("transfer-encoding: chunked\r\n"); | ||
| 437 | } | ||
| 438 | 418 | ||
| 439 | for (o.extra_headers) |header| { | 419 | if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) { |
| 440 | assert(header.name.len != 0); | 420 | .chunked => try out.writeAll("transfer-encoding: chunked\r\n"), |
| 441 | try out.writeAll(header.name); | 421 | .none => {}, |
| 442 | try out.writeAll(": "); | 422 | } else if (options.content_length) |len| { |
| 443 | try out.writeAll(header.value); | 423 | try out.print("content-length: {d}\r\n", .{len}); |
| 444 | try out.writeAll("\r\n"); | 424 | } else { |
| 445 | } | 425 | try out.writeAll("transfer-encoding: chunked\r\n"); |
| 426 | } | ||
| 446 | 427 | ||
| 428 | for (o.extra_headers) |header| { | ||
| 429 | assert(header.name.len != 0); | ||
| 430 | try out.writeAll(header.name); | ||
| 431 | try out.writeAll(": "); | ||
| 432 | try out.writeAll(header.value); | ||
| 447 | try out.writeAll("\r\n"); | 433 | try out.writeAll("\r\n"); |
| 448 | break :eb request.head.method == .HEAD; | 434 | } |
| 449 | }; | 435 | |
| 436 | try out.writeAll("\r\n"); | ||
| 437 | const elide_body = request.head.method == .HEAD; | ||
| 450 | 438 | ||
| 451 | return .{ | 439 | return .{ |
| 452 | .http_protocol_output = request.server.out, | 440 | .http_protocol_output = request.server.out, |
| ... | @@ -460,36 +448,126 @@ pub const Request = struct { | ... | @@ -460,36 +448,126 @@ pub const Request = struct { |
| 460 | }; | 448 | }; |
| 461 | } | 449 | } |
| 462 | 450 | ||
| 463 | pub const ReaderError = error{ | 451 | pub const UpgradeRequest = union(enum) { |
| 464 | /// Failed to write "100-continue" to the stream. | 452 | websocket: ?[]const u8, |
| 465 | WriteFailed, | 453 | other: []const u8, |
| 466 | /// Failed to write "100-continue" to the stream because it ended. | 454 | none, |
| 467 | EndOfStream, | 455 | }; |
| 468 | /// The client sent an expect HTTP header value other than | 456 | |
| 469 | /// "100-continue". | 457 | pub fn upgradeRequested(request: *const Request) UpgradeRequest { |
| 470 | HttpExpectationFailed, | 458 | switch (request.head.version) { |
| 459 | .@"HTTP/1.0" => return null, | ||
| 460 | .@"HTTP/1.1" => if (request.head.method != .GET) return null, | ||
| 461 | } | ||
| 462 | |||
| 463 | var sec_websocket_key: ?[]const u8 = null; | ||
| 464 | var upgrade_name: ?[]const u8 = null; | ||
| 465 | var it = request.iterateHeaders(); | ||
| 466 | while (it.next()) |header| { | ||
| 467 | if (std.ascii.eqlIgnoreCase(header.name, "sec-websocket-key")) { | ||
| 468 | sec_websocket_key = header.value; | ||
| 469 | } else if (std.ascii.eqlIgnoreCase(header.name, "upgrade")) { | ||
| 470 | upgrade_name = header.value; | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | const name = upgrade_name orelse return .none; | ||
| 475 | if (std.ascii.eqlIgnoreCase(name, "websocket")) return .{ .websocket = sec_websocket_key }; | ||
| 476 | return .{ .other = name }; | ||
| 477 | } | ||
| 478 | |||
| 479 | pub const WebSocketOptions = struct { | ||
| 480 | /// The value from `UpgradeRequest.websocket` (sec-websocket-key header value). | ||
| 481 | key: []const u8, | ||
| 482 | reason: ?[]const u8 = null, | ||
| 483 | extra_headers: []const http.Header = &.{}, | ||
| 471 | }; | 484 | }; |
| 472 | 485 | ||
| 486 | /// The header is not guaranteed to be sent until `WebSocket.flush` is | ||
| 487 | /// called on the returned struct. | ||
| 488 | pub fn respondWebSocket(request: *Request, options: WebSocketOptions) std.io.Writer.Error!WebSocket { | ||
| 489 | if (request.head.expect != null) return error.HttpExpectationFailed; | ||
| 490 | |||
| 491 | const out = request.server.out; | ||
| 492 | const version: http.Version = .@"HTTP/1.1"; | ||
| 493 | const status: http.Status = .switching_protocols; | ||
| 494 | const phrase = options.reason orelse status.phrase() orelse ""; | ||
| 495 | |||
| 496 | assert(request.head.version == version); | ||
| 497 | assert(request.head.method == .GET); | ||
| 498 | |||
| 499 | var sha1 = std.crypto.hash.Sha1.init(.{}); | ||
| 500 | sha1.update(options.key); | ||
| 501 | sha1.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); | ||
| 502 | var digest: [std.crypto.hash.Sha1.digest_length]u8 = undefined; | ||
| 503 | sha1.final(&digest); | ||
| 504 | try out.print("{s} {d} {s}\r\n", .{ @tagName(version), @intFromEnum(status), phrase }); | ||
| 505 | try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: "); | ||
| 506 | const base64_digest = try out.writableArray(28); | ||
| 507 | assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len); | ||
| 508 | out.advance(base64_digest.len); | ||
| 509 | try out.writeAll("\r\n"); | ||
| 510 | |||
| 511 | for (options.extra_headers) |header| { | ||
| 512 | assert(header.name.len != 0); | ||
| 513 | try out.writeAll(header.name); | ||
| 514 | try out.writeAll(": "); | ||
| 515 | try out.writeAll(header.value); | ||
| 516 | try out.writeAll("\r\n"); | ||
| 517 | } | ||
| 518 | |||
| 519 | try out.writeAll("\r\n"); | ||
| 520 | |||
| 521 | return .{ | ||
| 522 | .input = request.server.reader.in, | ||
| 523 | .output = request.server.out, | ||
| 524 | .key = options.key, | ||
| 525 | }; | ||
| 526 | } | ||
| 527 | |||
| 473 | /// In the case that the request contains "expect: 100-continue", this | 528 | /// In the case that the request contains "expect: 100-continue", this |
| 474 | /// function writes the continuation header, which means it can fail with a | 529 | /// function writes the continuation header, which means it can fail with a |
| 475 | /// write error. After sending the continuation header, it sets the | 530 | /// write error. After sending the continuation header, it sets the |
| 476 | /// request's expect field to `null`. | 531 | /// request's expect field to `null`. |
| 477 | /// | 532 | /// |
| 478 | /// Asserts that this function is only called once. | 533 | /// Asserts that this function is only called once. |
| 479 | pub fn reader(request: *Request) ReaderError!std.io.Reader { | 534 | /// |
| 535 | /// See `readerExpectNone` for an infallible alternative that cannot write | ||
| 536 | /// to the server output stream. | ||
| 537 | pub fn readerExpectContinue(request: *Request) ExpectContinueError!std.io.Reader { | ||
| 538 | const flush = request.head.expect != null; | ||
| 539 | try writeExpectContinue(request); | ||
| 540 | if (flush) try request.server.out.flush(); | ||
| 541 | return readerExpectNone(request); | ||
| 542 | } | ||
| 543 | |||
| 544 | /// Asserts the expect header is `null`. The caller must handle the | ||
| 545 | /// expectation manually and then set the value to `null` prior to calling | ||
| 546 | /// this function. | ||
| 547 | /// | ||
| 548 | /// Asserts that this function is only called once. | ||
| 549 | pub fn readerExpectNone(request: *Request) std.io.Reader { | ||
| 480 | assert(request.server.reader.state == .received_head); | 550 | assert(request.server.reader.state == .received_head); |
| 481 | if (request.head.expect) |expect| { | 551 | assert(request.head.expect == null); |
| 482 | if (mem.eql(u8, expect, "100-continue")) { | ||
| 483 | try request.server.out.writeAll("HTTP/1.1 100 Continue\r\n\r\n"); | ||
| 484 | request.head.expect = null; | ||
| 485 | } else { | ||
| 486 | return error.HttpExpectationFailed; | ||
| 487 | } | ||
| 488 | } | ||
| 489 | if (!request.head.method.requestHasBody()) return .ending; | 552 | if (!request.head.method.requestHasBody()) return .ending; |
| 490 | return request.server.reader.bodyReader(request.head.transfer_encoding, request.head.content_length); | 553 | return request.server.reader.bodyReader(request.head.transfer_encoding, request.head.content_length); |
| 491 | } | 554 | } |
| 492 | 555 | ||
| 556 | pub const ExpectContinueError = error{ | ||
| 557 | /// Failed to write "HTTP/1.1 100 Continue\r\n\r\n" to the stream. | ||
| 558 | WriteFailed, | ||
| 559 | /// The client sent an expect HTTP header value other than | ||
| 560 | /// "100-continue". | ||
| 561 | HttpExpectationFailed, | ||
| 562 | }; | ||
| 563 | |||
| 564 | pub fn writeExpectContinue(request: *Request) ExpectContinueError!void { | ||
| 565 | const expect = request.head.expect orelse return; | ||
| 566 | if (!mem.eql(u8, expect, "100-continue")) return error.HttpExpectationFailed; | ||
| 567 | try request.server.out.writeAll("HTTP/1.1 100 Continue\r\n\r\n"); | ||
| 568 | request.head.expect = null; | ||
| 569 | } | ||
| 570 | |||
| 493 | /// Returns whether the connection should remain persistent. | 571 | /// Returns whether the connection should remain persistent. |
| 494 | /// | 572 | /// |
| 495 | /// If it would fail, it instead sets the Server state to receiving body | 573 | /// If it would fail, it instead sets the Server state to receiving body |
| ... | @@ -528,3 +606,150 @@ pub const Request = struct { | ... | @@ -528,3 +606,150 @@ pub const Request = struct { |
| 528 | return false; | 606 | return false; |
| 529 | } | 607 | } |
| 530 | }; | 608 | }; |
| 609 | |||
| 610 | /// See https://tools.ietf.org/html/rfc6455 | ||
| 611 | pub const WebSocket = struct { | ||
| 612 | key: []const u8, | ||
| 613 | input: *std.io.BufferedReader, | ||
| 614 | output: *std.io.BufferedWriter, | ||
| 615 | |||
| 616 | pub const Header0 = packed struct(u8) { | ||
| 617 | opcode: Opcode, | ||
| 618 | rsv3: u1 = 0, | ||
| 619 | rsv2: u1 = 0, | ||
| 620 | rsv1: u1 = 0, | ||
| 621 | fin: bool, | ||
| 622 | }; | ||
| 623 | |||
| 624 | pub const Header1 = packed struct(u8) { | ||
| 625 | payload_len: enum(u7) { | ||
| 626 | len16 = 126, | ||
| 627 | len64 = 127, | ||
| 628 | _, | ||
| 629 | }, | ||
| 630 | mask: bool, | ||
| 631 | }; | ||
| 632 | |||
| 633 | pub const Opcode = enum(u4) { | ||
| 634 | continuation = 0, | ||
| 635 | text = 1, | ||
| 636 | binary = 2, | ||
| 637 | connection_close = 8, | ||
| 638 | ping = 9, | ||
| 639 | /// "A Pong frame MAY be sent unsolicited. This serves as a unidirectional | ||
| 640 | /// heartbeat. A response to an unsolicited Pong frame is not expected." | ||
| 641 | pong = 10, | ||
| 642 | _, | ||
| 643 | }; | ||
| 644 | |||
| 645 | pub const ReadSmallTextMessageError = error{ | ||
| 646 | ConnectionClose, | ||
| 647 | UnexpectedOpCode, | ||
| 648 | MessageTooBig, | ||
| 649 | MissingMaskBit, | ||
| 650 | }; | ||
| 651 | |||
| 652 | pub const SmallMessage = struct { | ||
| 653 | /// Can be text, binary, or ping. | ||
| 654 | opcode: Opcode, | ||
| 655 | data: []u8, | ||
| 656 | }; | ||
| 657 | |||
| 658 | /// Reads the next message from the WebSocket stream, failing if the | ||
| 659 | /// message does not fit into the input buffer. The returned memory points | ||
| 660 | /// into the input buffer and is invalidated on the next read. | ||
| 661 | pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage { | ||
| 662 | const in = ws.input; | ||
| 663 | while (true) { | ||
| 664 | const h0 = in.takeStruct(Header0); | ||
| 665 | const h1 = in.takeStruct(Header1); | ||
| 666 | |||
| 667 | switch (h0.opcode) { | ||
| 668 | .text, .binary, .pong, .ping => {}, | ||
| 669 | .connection_close => return error.ConnectionClose, | ||
| 670 | .continuation => return error.UnexpectedOpCode, | ||
| 671 | _ => return error.UnexpectedOpCode, | ||
| 672 | } | ||
| 673 | |||
| 674 | if (!h0.fin) return error.MessageTooBig; | ||
| 675 | if (!h1.mask) return error.MissingMaskBit; | ||
| 676 | |||
| 677 | const len: usize = switch (h1.payload_len) { | ||
| 678 | .len16 => try in.takeInt(u16, .big), | ||
| 679 | .len64 => std.math.cast(usize, try in.takeInt(u64, .big)) orelse return error.MessageTooBig, | ||
| 680 | else => @intFromEnum(h1.payload_len), | ||
| 681 | }; | ||
| 682 | if (len > in.buffer.len) return error.MessageTooBig; | ||
| 683 | const mask: u32 = @bitCast((try in.takeArray(4)).*); | ||
| 684 | const payload = try in.take(len); | ||
| 685 | |||
| 686 | // Skip pongs. | ||
| 687 | if (h0.opcode == .pong) continue; | ||
| 688 | |||
| 689 | // The last item may contain a partial word of unused data. | ||
| 690 | const floored_len = (payload.len / 4) * 4; | ||
| 691 | const u32_payload: []align(1) u32 = @ptrCast(payload[0..floored_len]); | ||
| 692 | for (u32_payload) |*elem| elem.* ^= mask; | ||
| 693 | const mask_bytes: []const u8 = @ptrCast(&mask); | ||
| 694 | for (payload[floored_len..], mask_bytes[0 .. payload.len - floored_len]) |*leftover, m| | ||
| 695 | leftover.* ^= m; | ||
| 696 | |||
| 697 | return .{ | ||
| 698 | .opcode = h0.opcode, | ||
| 699 | .data = payload, | ||
| 700 | }; | ||
| 701 | } | ||
| 702 | } | ||
| 703 | |||
| 704 | pub fn writeMessage(ws: *WebSocket, data: []const u8, op: Opcode) std.io.Writer.Error!void { | ||
| 705 | try writeMessageVecUnflushed(ws, &.{data}, op); | ||
| 706 | try ws.output.flush(); | ||
| 707 | } | ||
| 708 | |||
| 709 | pub fn writeMessageUnflushed(ws: *WebSocket, data: []const u8, op: Opcode) std.io.Writer.Error!void { | ||
| 710 | try writeMessageVecUnflushed(ws, &.{data}, op); | ||
| 711 | } | ||
| 712 | |||
| 713 | pub fn writeMessageVec(ws: *WebSocket, data: []const []const u8, op: Opcode) std.io.Writer.Error!void { | ||
| 714 | try writeMessageVecUnflushed(ws, data, op); | ||
| 715 | try ws.output.flush(); | ||
| 716 | } | ||
| 717 | |||
| 718 | pub fn writeMessageVecUnflushed(ws: *WebSocket, data: []const []const u8, op: Opcode) std.io.Writer.Error!void { | ||
| 719 | const total_len = l: { | ||
| 720 | var total_len: u64 = 0; | ||
| 721 | for (data) |iovec| total_len += iovec.len; | ||
| 722 | break :l total_len; | ||
| 723 | }; | ||
| 724 | const out = ws.output; | ||
| 725 | try out.writeStruct(@as(Header0, .{ | ||
| 726 | .opcode = op, | ||
| 727 | .fin = true, | ||
| 728 | })); | ||
| 729 | switch (total_len) { | ||
| 730 | 0...125 => try out.writeStruct(@as(Header1, .{ | ||
| 731 | .payload_len = @enumFromInt(total_len), | ||
| 732 | .mask = false, | ||
| 733 | })), | ||
| 734 | 126...0xffff => { | ||
| 735 | try out.writeStruct(@as(Header1, .{ | ||
| 736 | .payload_len = .len16, | ||
| 737 | .mask = false, | ||
| 738 | })); | ||
| 739 | try out.writeInt(u16, @intCast(total_len), .big); | ||
| 740 | }, | ||
| 741 | else => { | ||
| 742 | try out.writeStruct(@as(Header1, .{ | ||
| 743 | .payload_len = .len64, | ||
| 744 | .mask = false, | ||
| 745 | })); | ||
| 746 | try out.writeInt(u64, total_len, .big); | ||
| 747 | }, | ||
| 748 | } | ||
| 749 | try out.writeVecAll(data); | ||
| 750 | } | ||
| 751 | |||
| 752 | pub fn flush(ws: *WebSocket) std.io.Writer.Error!void { | ||
| 753 | try ws.output.flush(); | ||
| 754 | } | ||
| 755 | }; |
lib/std/http/WebSocket.zig deleted-243| ... | @@ -1,243 +0,0 @@ | ||
| 1 | //! See https://tools.ietf.org/html/rfc6455 | ||
| 2 | |||
| 3 | const builtin = @import("builtin"); | ||
| 4 | const std = @import("std"); | ||
| 5 | const WebSocket = @This(); | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | const native_endian = builtin.cpu.arch.endian(); | ||
| 8 | |||
| 9 | key: []const u8, | ||
| 10 | request: *std.http.Server.Request, | ||
| 11 | recv_fifo: std.fifo.LinearFifo(u8, .Slice), | ||
| 12 | reader: std.io.BufferedReader, | ||
| 13 | body_writer: std.http.BodyWriter, | ||
| 14 | /// Number of bytes that have been peeked but not discarded yet. | ||
| 15 | outstanding_len: usize, | ||
| 16 | |||
| 17 | pub const InitError = error{WebSocketUpgradeMissingKey} || | ||
| 18 | std.http.Server.Request.ReaderError; | ||
| 19 | |||
| 20 | pub fn init( | ||
| 21 | ws: *WebSocket, | ||
| 22 | request: *std.http.Server.Request, | ||
| 23 | recv_buffer: []align(4) u8, | ||
| 24 | ) InitError!bool { | ||
| 25 | switch (request.head.version) { | ||
| 26 | .@"HTTP/1.0" => return false, | ||
| 27 | .@"HTTP/1.1" => if (request.head.method != .GET) return false, | ||
| 28 | } | ||
| 29 | |||
| 30 | var sec_websocket_key: ?[]const u8 = null; | ||
| 31 | var upgrade_websocket: bool = false; | ||
| 32 | var it = request.iterateHeaders(); | ||
| 33 | while (it.next()) |header| { | ||
| 34 | if (std.ascii.eqlIgnoreCase(header.name, "sec-websocket-key")) { | ||
| 35 | sec_websocket_key = header.value; | ||
| 36 | } else if (std.ascii.eqlIgnoreCase(header.name, "upgrade")) { | ||
| 37 | if (!std.ascii.eqlIgnoreCase(header.value, "websocket")) | ||
| 38 | return false; | ||
| 39 | upgrade_websocket = true; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | if (!upgrade_websocket) | ||
| 43 | return false; | ||
| 44 | |||
| 45 | const key = sec_websocket_key orelse return error.WebSocketUpgradeMissingKey; | ||
| 46 | |||
| 47 | var sha1 = std.crypto.hash.Sha1.init(.{}); | ||
| 48 | sha1.update(key); | ||
| 49 | sha1.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); | ||
| 50 | var digest: [std.crypto.hash.Sha1.digest_length]u8 = undefined; | ||
| 51 | sha1.final(&digest); | ||
| 52 | var base64_digest: [28]u8 = undefined; | ||
| 53 | assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len); | ||
| 54 | |||
| 55 | request.head.content_length = std.math.maxInt(u64); | ||
| 56 | |||
| 57 | ws.* = .{ | ||
| 58 | .key = key, | ||
| 59 | .recv_fifo = .init(recv_buffer), | ||
| 60 | .reader = (try request.reader()).unbuffered(), | ||
| 61 | .body_writer = try request.respondStreaming(.{ | ||
| 62 | .respond_options = .{ | ||
| 63 | .status = .switching_protocols, | ||
| 64 | .extra_headers = &.{ | ||
| 65 | .{ .name = "upgrade", .value = "websocket" }, | ||
| 66 | .{ .name = "connection", .value = "upgrade" }, | ||
| 67 | .{ .name = "sec-websocket-accept", .value = &base64_digest }, | ||
| 68 | }, | ||
| 69 | .transfer_encoding = .none, | ||
| 70 | }, | ||
| 71 | }), | ||
| 72 | .request = request, | ||
| 73 | .outstanding_len = 0, | ||
| 74 | }; | ||
| 75 | return true; | ||
| 76 | } | ||
| 77 | |||
| 78 | pub const Header0 = packed struct(u8) { | ||
| 79 | opcode: Opcode, | ||
| 80 | rsv3: u1 = 0, | ||
| 81 | rsv2: u1 = 0, | ||
| 82 | rsv1: u1 = 0, | ||
| 83 | fin: bool, | ||
| 84 | }; | ||
| 85 | |||
| 86 | pub const Header1 = packed struct(u8) { | ||
| 87 | payload_len: enum(u7) { | ||
| 88 | len16 = 126, | ||
| 89 | len64 = 127, | ||
| 90 | _, | ||
| 91 | }, | ||
| 92 | mask: bool, | ||
| 93 | }; | ||
| 94 | |||
| 95 | pub const Opcode = enum(u4) { | ||
| 96 | continuation = 0, | ||
| 97 | text = 1, | ||
| 98 | binary = 2, | ||
| 99 | connection_close = 8, | ||
| 100 | ping = 9, | ||
| 101 | /// "A Pong frame MAY be sent unsolicited. This serves as a unidirectional | ||
| 102 | /// heartbeat. A response to an unsolicited Pong frame is not expected." | ||
| 103 | pong = 10, | ||
| 104 | _, | ||
| 105 | }; | ||
| 106 | |||
| 107 | pub const ReadSmallTextMessageError = error{ | ||
| 108 | ConnectionClose, | ||
| 109 | UnexpectedOpCode, | ||
| 110 | MessageTooBig, | ||
| 111 | MissingMaskBit, | ||
| 112 | } || RecvError; | ||
| 113 | |||
| 114 | pub const SmallMessage = struct { | ||
| 115 | /// Can be text, binary, or ping. | ||
| 116 | opcode: Opcode, | ||
| 117 | data: []u8, | ||
| 118 | }; | ||
| 119 | |||
| 120 | /// Reads the next message from the WebSocket stream, failing if the message does not fit | ||
| 121 | /// into `recv_buffer`. | ||
| 122 | pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage { | ||
| 123 | while (true) { | ||
| 124 | const header_bytes = (try recv(ws, 2))[0..2]; | ||
| 125 | const h0: Header0 = @bitCast(header_bytes[0]); | ||
| 126 | const h1: Header1 = @bitCast(header_bytes[1]); | ||
| 127 | |||
| 128 | switch (h0.opcode) { | ||
| 129 | .text, .binary, .pong, .ping => {}, | ||
| 130 | .connection_close => return error.ConnectionClose, | ||
| 131 | .continuation => return error.UnexpectedOpCode, | ||
| 132 | _ => return error.UnexpectedOpCode, | ||
| 133 | } | ||
| 134 | |||
| 135 | if (!h0.fin) return error.MessageTooBig; | ||
| 136 | if (!h1.mask) return error.MissingMaskBit; | ||
| 137 | |||
| 138 | const len: usize = switch (h1.payload_len) { | ||
| 139 | .len16 => try recvReadInt(ws, u16), | ||
| 140 | .len64 => std.math.cast(usize, try recvReadInt(ws, u64)) orelse return error.MessageTooBig, | ||
| 141 | else => @intFromEnum(h1.payload_len), | ||
| 142 | }; | ||
| 143 | if (len > ws.recv_fifo.buf.len) return error.MessageTooBig; | ||
| 144 | |||
| 145 | const mask: u32 = @bitCast((try recv(ws, 4))[0..4].*); | ||
| 146 | const payload = try recv(ws, len); | ||
| 147 | |||
| 148 | // Skip pongs. | ||
| 149 | if (h0.opcode == .pong) continue; | ||
| 150 | |||
| 151 | // The last item may contain a partial word of unused data. | ||
| 152 | const floored_len = (payload.len / 4) * 4; | ||
| 153 | const u32_payload: []align(1) u32 = @alignCast(std.mem.bytesAsSlice(u32, payload[0..floored_len])); | ||
| 154 | for (u32_payload) |*elem| elem.* ^= mask; | ||
| 155 | const mask_bytes = std.mem.asBytes(&mask)[0 .. payload.len - floored_len]; | ||
| 156 | for (payload[floored_len..], mask_bytes) |*leftover, m| leftover.* ^= m; | ||
| 157 | |||
| 158 | return .{ | ||
| 159 | .opcode = h0.opcode, | ||
| 160 | .data = payload, | ||
| 161 | }; | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | const RecvError = std.http.Server.Request.ReadError || error{EndOfStream}; | ||
| 166 | |||
| 167 | fn recv(ws: *WebSocket, len: usize) RecvError![]u8 { | ||
| 168 | ws.recv_fifo.discard(ws.outstanding_len); | ||
| 169 | assert(len <= ws.recv_fifo.buf.len); | ||
| 170 | if (len > ws.recv_fifo.count) { | ||
| 171 | const small_buf = ws.recv_fifo.writableSlice(0); | ||
| 172 | const needed = len - ws.recv_fifo.count; | ||
| 173 | const buf = if (small_buf.len >= needed) small_buf else b: { | ||
| 174 | ws.recv_fifo.realign(); | ||
| 175 | break :b ws.recv_fifo.writableSlice(0); | ||
| 176 | }; | ||
| 177 | const n = try @as(RecvError!usize, @errorCast(ws.reader.readAtLeast(buf, needed))); | ||
| 178 | if (n < needed) return error.EndOfStream; | ||
| 179 | ws.recv_fifo.update(n); | ||
| 180 | } | ||
| 181 | ws.outstanding_len = len; | ||
| 182 | // TODO: improve the std lib API so this cast isn't necessary. | ||
| 183 | return @constCast(ws.recv_fifo.readableSliceOfLen(len)); | ||
| 184 | } | ||
| 185 | |||
| 186 | fn recvReadInt(ws: *WebSocket, comptime I: type) !I { | ||
| 187 | const unswapped: I = @bitCast((try recv(ws, @sizeOf(I)))[0..@sizeOf(I)].*); | ||
| 188 | return switch (native_endian) { | ||
| 189 | .little => @byteSwap(unswapped), | ||
| 190 | .big => unswapped, | ||
| 191 | }; | ||
| 192 | } | ||
| 193 | |||
| 194 | pub fn writeMessage(ws: *WebSocket, message: []const u8, opcode: Opcode) std.io.Writer.Error!void { | ||
| 195 | const iovecs: [1]std.posix.iovec_const = .{ | ||
| 196 | .{ .base = message.ptr, .len = message.len }, | ||
| 197 | }; | ||
| 198 | return writeMessagev(ws, &iovecs, opcode); | ||
| 199 | } | ||
| 200 | |||
| 201 | pub fn writeMessagev(ws: *WebSocket, message: []const std.posix.iovec_const, opcode: Opcode) std.io.Writer.Error!void { | ||
| 202 | const total_len = l: { | ||
| 203 | var total_len: u64 = 0; | ||
| 204 | for (message) |iovec| total_len += iovec.len; | ||
| 205 | break :l total_len; | ||
| 206 | }; | ||
| 207 | |||
| 208 | var header_buf: [2 + 8]u8 = undefined; | ||
| 209 | header_buf[0] = @bitCast(@as(Header0, .{ | ||
| 210 | .opcode = opcode, | ||
| 211 | .fin = true, | ||
| 212 | })); | ||
| 213 | const header = switch (total_len) { | ||
| 214 | 0...125 => blk: { | ||
| 215 | header_buf[1] = @bitCast(@as(Header1, .{ | ||
| 216 | .payload_len = @enumFromInt(total_len), | ||
| 217 | .mask = false, | ||
| 218 | })); | ||
| 219 | break :blk header_buf[0..2]; | ||
| 220 | }, | ||
| 221 | 126...0xffff => blk: { | ||
| 222 | header_buf[1] = @bitCast(@as(Header1, .{ | ||
| 223 | .payload_len = .len16, | ||
| 224 | .mask = false, | ||
| 225 | })); | ||
| 226 | std.mem.writeInt(u16, header_buf[2..4], @intCast(total_len), .big); | ||
| 227 | break :blk header_buf[0..4]; | ||
| 228 | }, | ||
| 229 | else => blk: { | ||
| 230 | header_buf[1] = @bitCast(@as(Header1, .{ | ||
| 231 | .payload_len = .len64, | ||
| 232 | .mask = false, | ||
| 233 | })); | ||
| 234 | std.mem.writeInt(u64, header_buf[2..10], total_len, .big); | ||
| 235 | break :blk header_buf[0..10]; | ||
| 236 | }, | ||
| 237 | }; | ||
| 238 | |||
| 239 | var bw = ws.body_writer.writer().unbuffered(); | ||
| 240 | try bw.writeAll(header); | ||
| 241 | for (message) |iovec| try bw.writeAll(iovec.base[0..iovec.len]); | ||
| 242 | try bw.flush(); | ||
| 243 | } | ||
lib/std/io/BufferedReader.zig+84-19| ... | @@ -6,8 +6,10 @@ const assert = std.debug.assert; | ... | @@ -6,8 +6,10 @@ const assert = std.debug.assert; |
| 6 | const testing = std.testing; | 6 | const testing = std.testing; |
| 7 | const BufferedWriter = std.io.BufferedWriter; | 7 | const BufferedWriter = std.io.BufferedWriter; |
| 8 | const Reader = std.io.Reader; | 8 | const Reader = std.io.Reader; |
| 9 | const Writer = std.io.Writer; | ||
| 9 | const Allocator = std.mem.Allocator; | 10 | const Allocator = std.mem.Allocator; |
| 10 | const ArrayList = std.ArrayListUnmanaged; | 11 | const ArrayList = std.ArrayListUnmanaged; |
| 12 | const Limit = std.io.Limit; | ||
| 11 | 13 | ||
| 12 | const BufferedReader = @This(); | 14 | const BufferedReader = @This(); |
| 13 | 15 | ||
| ... | @@ -63,12 +65,12 @@ pub fn readVec(br: *BufferedReader, data: []const []u8) Reader.Error!usize { | ... | @@ -63,12 +65,12 @@ pub fn readVec(br: *BufferedReader, data: []const []u8) Reader.Error!usize { |
| 63 | } | 65 | } |
| 64 | 66 | ||
| 65 | /// Equivalent semantics to `std.io.Reader.VTable.read`. | 67 | /// Equivalent semantics to `std.io.Reader.VTable.read`. |
| 66 | pub fn read(br: *BufferedReader, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize { | 68 | pub fn read(br: *BufferedReader, bw: *BufferedWriter, limit: Limit) Reader.StreamError!usize { |
| 67 | return passthruRead(br, bw, limit); | 69 | return passthruRead(br, bw, limit); |
| 68 | } | 70 | } |
| 69 | 71 | ||
| 70 | /// Equivalent semantics to `std.io.Reader.VTable.discard`. | 72 | /// Equivalent semantics to `std.io.Reader.VTable.discard`. |
| 71 | pub fn discard(br: *BufferedReader, limit: Reader.Limit) Reader.Error!usize { | 73 | pub fn discard(br: *BufferedReader, limit: Limit) Reader.Error!usize { |
| 72 | return passthruDiscard(br, limit); | 74 | return passthruDiscard(br, limit); |
| 73 | } | 75 | } |
| 74 | 76 | ||
| ... | @@ -90,7 +92,7 @@ pub fn readVecAll(br: *BufferedReader, data: [][]u8) Reader.Error!void { | ... | @@ -90,7 +92,7 @@ pub fn readVecAll(br: *BufferedReader, data: [][]u8) Reader.Error!void { |
| 90 | } | 92 | } |
| 91 | 93 | ||
| 92 | /// "Pump" data from the reader to the writer. | 94 | /// "Pump" data from the reader to the writer. |
| 93 | pub fn readAll(br: *BufferedReader, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!void { | 95 | pub fn readAll(br: *BufferedReader, bw: *BufferedWriter, limit: Limit) Reader.StreamError!void { |
| 94 | var remaining = limit; | 96 | var remaining = limit; |
| 95 | while (remaining.nonzero()) { | 97 | while (remaining.nonzero()) { |
| 96 | const n = try br.read(bw, remaining); | 98 | const n = try br.read(bw, remaining); |
| ... | @@ -113,8 +115,8 @@ pub fn readRemaining(br: *BufferedReader, bw: *BufferedWriter) Reader.RwRemainin | ... | @@ -113,8 +115,8 @@ pub fn readRemaining(br: *BufferedReader, bw: *BufferedWriter) Reader.RwRemainin |
| 113 | } | 115 | } |
| 114 | 116 | ||
| 115 | /// Equivalent to `readVec` but reads at most `limit` bytes. | 117 | /// Equivalent to `readVec` but reads at most `limit` bytes. |
| 116 | pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Reader.Limit) Reader.Error!usize { | 118 | pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Limit) Reader.Error!usize { |
| 117 | assert(@intFromEnum(Reader.Limit.unlimited) == std.math.maxInt(usize)); | 119 | assert(@intFromEnum(Limit.unlimited) == std.math.maxInt(usize)); |
| 118 | var remaining = @intFromEnum(limit); | 120 | var remaining = @intFromEnum(limit); |
| 119 | for (data, 0..) |buf, i| { | 121 | for (data, 0..) |buf, i| { |
| 120 | const buffered = br.buffer[br.seek..br.end]; | 122 | const buffered = br.buffer[br.seek..br.end]; |
| ... | @@ -165,7 +167,7 @@ pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Reader.Limit | ... | @@ -165,7 +167,7 @@ pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Reader.Limit |
| 165 | return @intFromEnum(limit) - remaining; | 167 | return @intFromEnum(limit) - remaining; |
| 166 | } | 168 | } |
| 167 | 169 | ||
| 168 | fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize { | 170 | fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) Reader.StreamError!usize { |
| 169 | const br: *BufferedReader = @alignCast(@ptrCast(context)); | 171 | const br: *BufferedReader = @alignCast(@ptrCast(context)); |
| 170 | const buffer = limit.slice(br.buffer[br.seek..br.end]); | 172 | const buffer = limit.slice(br.buffer[br.seek..br.end]); |
| 171 | if (buffer.len > 0) { | 173 | if (buffer.len > 0) { |
| ... | @@ -176,22 +178,19 @@ fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) | ... | @@ -176,22 +178,19 @@ fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) |
| 176 | return br.unbuffered_reader.read(bw, limit); | 178 | return br.unbuffered_reader.read(bw, limit); |
| 177 | } | 179 | } |
| 178 | 180 | ||
| 179 | fn passthruDiscard(context: ?*anyopaque, limit: Reader.Limit) Reader.Error!usize { | 181 | fn passthruDiscard(context: ?*anyopaque, limit: Limit) Reader.Error!usize { |
| 180 | const br: *BufferedReader = @alignCast(@ptrCast(context)); | 182 | const br: *BufferedReader = @alignCast(@ptrCast(context)); |
| 181 | const buffered_len = br.end - br.seek; | 183 | const buffered_len = br.end - br.seek; |
| 182 | if (limit.toInt()) |n| { | 184 | const remaining: Limit = if (limit.toInt()) |n| l: { |
| 183 | if (buffered_len >= n) { | 185 | if (buffered_len >= n) { |
| 184 | br.seek += n; | 186 | br.seek += n; |
| 185 | return n; | 187 | return n; |
| 186 | } | 188 | } |
| 187 | br.seek = 0; | 189 | break :l .limited(n - buffered_len); |
| 188 | br.end = 0; | 190 | } else .unlimited; |
| 189 | const additional = try br.unbuffered_reader.discard(.limited(n - buffered_len)); | ||
| 190 | return n + additional; | ||
| 191 | } | ||
| 192 | const n = try br.unbuffered_reader.discard(.unlimited); | ||
| 193 | br.seek = 0; | 191 | br.seek = 0; |
| 194 | br.end = 0; | 192 | br.end = 0; |
| 193 | const n = if (br.unbuffered_reader.discard) |f| try f(remaining) else try br.defaultDiscard(remaining); | ||
| 195 | return buffered_len + n; | 194 | return buffered_len + n; |
| 196 | } | 195 | } |
| 197 | 196 | ||
| ... | @@ -200,6 +199,72 @@ fn passthruReadVec(context: ?*anyopaque, data: []const []u8) Reader.Error!usize | ... | @@ -200,6 +199,72 @@ fn passthruReadVec(context: ?*anyopaque, data: []const []u8) Reader.Error!usize |
| 200 | return readVecLimit(br, data, .unlimited); | 199 | return readVecLimit(br, data, .unlimited); |
| 201 | } | 200 | } |
| 202 | 201 | ||
| 202 | fn defaultDiscard(br: *BufferedReader, limit: Limit) Reader.Error!usize { | ||
| 203 | assert(br.seek == 0); | ||
| 204 | assert(br.end == 0); | ||
| 205 | var bw: BufferedWriter = .{ | ||
| 206 | .unbuffered_writer = .{ | ||
| 207 | .context = undefined, | ||
| 208 | .vtable = &.{ | ||
| 209 | .writeSplat = defaultDiscardWriteSplat, | ||
| 210 | .writeFile = defaultDiscardWriteFile, | ||
| 211 | }, | ||
| 212 | }, | ||
| 213 | .buffer = br.buffer, | ||
| 214 | }; | ||
| 215 | const n = br.read(&bw, limit) catch |err| switch (err) { | ||
| 216 | error.WriteFailed => unreachable, | ||
| 217 | error.ReadFailed => return error.ReadFailed, | ||
| 218 | error.EndOfStream => return error.EndOfStream, | ||
| 219 | }; | ||
| 220 | if (n > @intFromEnum(limit)) { | ||
| 221 | const over_amt = n - @intFromEnum(limit); | ||
| 222 | assert(over_amt <= bw.buffer.end); // limit may be exceeded only by an amount within buffer capacity. | ||
| 223 | br.seek = bw.end - over_amt; | ||
| 224 | br.end = bw.end; | ||
| 225 | return @intFromEnum(limit); | ||
| 226 | } | ||
| 227 | return n; | ||
| 228 | } | ||
| 229 | |||
| 230 | fn defaultDiscardWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Error!usize { | ||
| 231 | _ = context; | ||
| 232 | const headers = data[0 .. data.len - 1]; | ||
| 233 | const pattern = data[headers.len..]; | ||
| 234 | var written: usize = pattern.len * splat; | ||
| 235 | for (headers) |bytes| written += bytes.len; | ||
| 236 | return written; | ||
| 237 | } | ||
| 238 | |||
| 239 | fn defaultDiscardWriteFile( | ||
| 240 | context: ?*anyopaque, | ||
| 241 | file_reader: *std.fs.File.Reader, | ||
| 242 | limit: Limit, | ||
| 243 | headers_and_trailers: []const []const u8, | ||
| 244 | headers_len: usize, | ||
| 245 | ) Writer.FileError!usize { | ||
| 246 | _ = context; | ||
| 247 | if (file_reader.getSize()) |size| { | ||
| 248 | const remaining = size - file_reader.pos; | ||
| 249 | const seek_amt = limit.minInt(remaining); | ||
| 250 | // Error is observable on `file_reader` instance, and is safe to ignore | ||
| 251 | // depending on the caller's needs. Caller can make that decision. | ||
| 252 | file_reader.seekForward(seek_amt) catch {}; | ||
| 253 | var n: usize = seek_amt; | ||
| 254 | for (headers_and_trailers[0..headers_len]) |bytes| n += bytes.len; | ||
| 255 | if (seek_amt == remaining) { | ||
| 256 | // Since we made it all the way through the file, the trailers are | ||
| 257 | // also included. | ||
| 258 | for (headers_and_trailers[headers_len..]) |bytes| n += bytes.len; | ||
| 259 | } | ||
| 260 | return n; | ||
| 261 | } else |_| { | ||
| 262 | // Error is observable on `file_reader` instance, and it is better to | ||
| 263 | // treat the file as a pipe. | ||
| 264 | return error.Unimplemented; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 203 | /// Returns the next `len` bytes from `unbuffered_reader`, filling the buffer as | 268 | /// Returns the next `len` bytes from `unbuffered_reader`, filling the buffer as |
| 204 | /// necessary. | 269 | /// necessary. |
| 205 | /// | 270 | /// |
| ... | @@ -475,7 +540,7 @@ pub fn readSliceAlloc(br: *BufferedReader, allocator: Allocator, len: usize) Rea | ... | @@ -475,7 +540,7 @@ pub fn readSliceAlloc(br: *BufferedReader, allocator: Allocator, len: usize) Rea |
| 475 | /// | 540 | /// |
| 476 | /// See also: | 541 | /// See also: |
| 477 | /// * `readRemainingArrayList` | 542 | /// * `readRemainingArrayList` |
| 478 | pub fn readRemainingAlloc(r: Reader, gpa: Allocator, limit: Reader.Limit) Reader.LimitedAllocError![]u8 { | 543 | pub fn readRemainingAlloc(r: Reader, gpa: Allocator, limit: Limit) Reader.LimitedAllocError![]u8 { |
| 479 | var buffer: ArrayList(u8) = .empty; | 544 | var buffer: ArrayList(u8) = .empty; |
| 480 | defer buffer.deinit(gpa); | 545 | defer buffer.deinit(gpa); |
| 481 | try readRemainingArrayList(r, gpa, null, &buffer, limit); | 546 | try readRemainingArrayList(r, gpa, null, &buffer, limit); |
| ... | @@ -499,7 +564,7 @@ pub fn readRemainingArrayList( | ... | @@ -499,7 +564,7 @@ pub fn readRemainingArrayList( |
| 499 | gpa: Allocator, | 564 | gpa: Allocator, |
| 500 | comptime alignment: ?std.mem.Alignment, | 565 | comptime alignment: ?std.mem.Alignment, |
| 501 | list: *std.ArrayListAlignedUnmanaged(u8, alignment), | 566 | list: *std.ArrayListAlignedUnmanaged(u8, alignment), |
| 502 | limit: Reader.Limit, | 567 | limit: Limit, |
| 503 | ) Reader.LimitedAllocError!void { | 568 | ) Reader.LimitedAllocError!void { |
| 504 | const buffer = br.buffer; | 569 | const buffer = br.buffer; |
| 505 | const buffered = buffer[br.seek..br.end]; | 570 | const buffered = buffer[br.seek..br.end]; |
| ... | @@ -680,7 +745,7 @@ pub fn peekDelimiterExclusive(br: *BufferedReader, delimiter: u8) DelimiterError | ... | @@ -680,7 +745,7 @@ pub fn peekDelimiterExclusive(br: *BufferedReader, delimiter: u8) DelimiterError |
| 680 | /// found. Does not write the delimiter itself. | 745 | /// found. Does not write the delimiter itself. |
| 681 | /// | 746 | /// |
| 682 | /// Returns number of bytes streamed. | 747 | /// Returns number of bytes streamed. |
| 683 | pub fn readDelimiter(br: *BufferedReader, bw: *BufferedWriter, delimiter: u8) Reader.RwError!usize { | 748 | pub fn readDelimiter(br: *BufferedReader, bw: *BufferedWriter, delimiter: u8) Reader.StreamError!usize { |
| 684 | const amount, const to = try br.readAny(bw, delimiter, .unlimited); | 749 | const amount, const to = try br.readAny(bw, delimiter, .unlimited); |
| 685 | return switch (to) { | 750 | return switch (to) { |
| 686 | .delimiter => amount, | 751 | .delimiter => amount, |
| ... | @@ -722,7 +787,7 @@ pub fn readDelimiterLimit( | ... | @@ -722,7 +787,7 @@ pub fn readDelimiterLimit( |
| 722 | br: *BufferedReader, | 787 | br: *BufferedReader, |
| 723 | bw: *BufferedWriter, | 788 | bw: *BufferedWriter, |
| 724 | delimiter: u8, | 789 | delimiter: u8, |
| 725 | limit: Reader.Limit, | 790 | limit: Limit, |
| 726 | ) StreamDelimiterLimitedError!usize { | 791 | ) StreamDelimiterLimitedError!usize { |
| 727 | const amount, const to = try br.readAny(bw, delimiter, limit); | 792 | const amount, const to = try br.readAny(bw, delimiter, limit); |
| 728 | return switch (to) { | 793 | return switch (to) { |
| ... | @@ -736,7 +801,7 @@ fn readAny( | ... | @@ -736,7 +801,7 @@ fn readAny( |
| 736 | br: *BufferedReader, | 801 | br: *BufferedReader, |
| 737 | bw: *BufferedWriter, | 802 | bw: *BufferedWriter, |
| 738 | delimiter: ?u8, | 803 | delimiter: ?u8, |
| 739 | limit: Reader.Limit, | 804 | limit: Limit, |
| 740 | ) Reader.RwRemainingError!struct { usize, enum { delimiter, limit, end } } { | 805 | ) Reader.RwRemainingError!struct { usize, enum { delimiter, limit, end } } { |
| 741 | var amount: usize = 0; | 806 | var amount: usize = 0; |
| 742 | var remaining = limit; | 807 | var remaining = limit; |
lib/std/io/BufferedWriter.zig+9-8| ... | @@ -5,6 +5,7 @@ const native_endian = @import("builtin").target.cpu.arch.endian(); | ... | @@ -5,6 +5,7 @@ const native_endian = @import("builtin").target.cpu.arch.endian(); |
| 5 | const Writer = std.io.Writer; | 5 | const Writer = std.io.Writer; |
| 6 | const Allocator = std.mem.Allocator; | 6 | const Allocator = std.mem.Allocator; |
| 7 | const testing = std.testing; | 7 | const testing = std.testing; |
| 8 | const Limit = std.io.Limit; | ||
| 8 | 9 | ||
| 9 | /// Underlying stream to send bytes to. | 10 | /// Underlying stream to send bytes to. |
| 10 | /// | 11 | /// |
| ... | @@ -42,7 +43,7 @@ pub fn writer(bw: *BufferedWriter) Writer { | ... | @@ -42,7 +43,7 @@ pub fn writer(bw: *BufferedWriter) Writer { |
| 42 | 43 | ||
| 43 | const fixed_vtable: Writer.VTable = .{ | 44 | const fixed_vtable: Writer.VTable = .{ |
| 44 | .writeSplat = fixedWriteSplat, | 45 | .writeSplat = fixedWriteSplat, |
| 45 | .writeFile = Writer.failingWriteFile, | 46 | .writeFile = Writer.unimplementedWriteFile, |
| 46 | }; | 47 | }; |
| 47 | 48 | ||
| 48 | /// Replaces the `BufferedWriter` with one that writes to `buffer` and returns | 49 | /// Replaces the `BufferedWriter` with one that writes to `buffer` and returns |
| ... | @@ -82,7 +83,7 @@ pub fn flush(bw: *BufferedWriter) Writer.Error!void { | ... | @@ -82,7 +83,7 @@ pub fn flush(bw: *BufferedWriter) Writer.Error!void { |
| 82 | bw.end = 0; | 83 | bw.end = 0; |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 85 | pub fn flushLimit(bw: *BufferedWriter, limit: Writer.Limit) Writer.Error!void { | 86 | pub fn flushLimit(bw: *BufferedWriter, limit: Limit) Writer.Error!void { |
| 86 | const buffer = limit.slice(bw.buffer[0..bw.end]); | 87 | const buffer = limit.slice(bw.buffer[0..bw.end]); |
| 87 | var index: usize = 0; | 88 | var index: usize = 0; |
| 88 | while (index < buffer.len) index += try bw.unbuffered_writer.writeVec(&.{buffer[index..]}); | 89 | while (index < buffer.len) index += try bw.unbuffered_writer.writeVec(&.{buffer[index..]}); |
| ... | @@ -228,7 +229,7 @@ pub fn writeSplatLimit( | ... | @@ -228,7 +229,7 @@ pub fn writeSplatLimit( |
| 228 | bw: *BufferedWriter, | 229 | bw: *BufferedWriter, |
| 229 | data: []const []const u8, | 230 | data: []const []const u8, |
| 230 | splat: usize, | 231 | splat: usize, |
| 231 | limit: Writer.Limit, | 232 | limit: Limit, |
| 232 | ) Writer.Error!usize { | 233 | ) Writer.Error!usize { |
| 233 | _ = bw; | 234 | _ = bw; |
| 234 | _ = data; | 235 | _ = data; |
| ... | @@ -544,7 +545,7 @@ pub fn writeFile( | ... | @@ -544,7 +545,7 @@ pub fn writeFile( |
| 544 | bw: *BufferedWriter, | 545 | bw: *BufferedWriter, |
| 545 | file: std.fs.File, | 546 | file: std.fs.File, |
| 546 | offset: Writer.Offset, | 547 | offset: Writer.Offset, |
| 547 | limit: Writer.Limit, | 548 | limit: Limit, |
| 548 | headers_and_trailers: []const []const u8, | 549 | headers_and_trailers: []const []const u8, |
| 549 | headers_len: usize, | 550 | headers_len: usize, |
| 550 | ) Writer.FileError!usize { | 551 | ) Writer.FileError!usize { |
| ... | @@ -560,7 +561,7 @@ pub fn writeFileReading( | ... | @@ -560,7 +561,7 @@ pub fn writeFileReading( |
| 560 | bw: *BufferedWriter, | 561 | bw: *BufferedWriter, |
| 561 | file: std.fs.File, | 562 | file: std.fs.File, |
| 562 | offset: Writer.Offset, | 563 | offset: Writer.Offset, |
| 563 | limit: Writer.Limit, | 564 | limit: Limit, |
| 564 | ) WriteFileReadingError!usize { | 565 | ) WriteFileReadingError!usize { |
| 565 | const dest = limit.slice(try bw.writableSliceGreedy(1)); | 566 | const dest = limit.slice(try bw.writableSliceGreedy(1)); |
| 566 | const n = if (offset.toInt()) |pos| try file.pread(dest, pos) else try file.read(dest); | 567 | const n = if (offset.toInt()) |pos| try file.pread(dest, pos) else try file.read(dest); |
| ... | @@ -572,7 +573,7 @@ fn passthruWriteFile( | ... | @@ -572,7 +573,7 @@ fn passthruWriteFile( |
| 572 | context: ?*anyopaque, | 573 | context: ?*anyopaque, |
| 573 | file: std.fs.File, | 574 | file: std.fs.File, |
| 574 | offset: Writer.Offset, | 575 | offset: Writer.Offset, |
| 575 | limit: Writer.Limit, | 576 | limit: Limit, |
| 576 | headers_and_trailers: []const []const u8, | 577 | headers_and_trailers: []const []const u8, |
| 577 | headers_len: usize, | 578 | headers_len: usize, |
| 578 | ) Writer.FileError!usize { | 579 | ) Writer.FileError!usize { |
| ... | @@ -653,7 +654,7 @@ pub const WriteFileOptions = struct { | ... | @@ -653,7 +654,7 @@ pub const WriteFileOptions = struct { |
| 653 | offset: Writer.Offset = .none, | 654 | offset: Writer.Offset = .none, |
| 654 | /// If the size of the source file is known, it is likely that passing the | 655 | /// If the size of the source file is known, it is likely that passing the |
| 655 | /// size here will save one syscall. | 656 | /// size here will save one syscall. |
| 656 | limit: Writer.Limit = .unlimited, | 657 | limit: Limit = .unlimited, |
| 657 | /// Headers and trailers must be passed together so that in case `len` is | 658 | /// Headers and trailers must be passed together so that in case `len` is |
| 658 | /// zero, they can be forwarded directly to `Writer.VTable.writeSplat`. | 659 | /// zero, they can be forwarded directly to `Writer.VTable.writeSplat`. |
| 659 | /// | 660 | /// |
| ... | @@ -749,7 +750,7 @@ pub fn writeFileReadingAll( | ... | @@ -749,7 +750,7 @@ pub fn writeFileReadingAll( |
| 749 | bw: *BufferedWriter, | 750 | bw: *BufferedWriter, |
| 750 | file: std.fs.File, | 751 | file: std.fs.File, |
| 751 | offset: Writer.Offset, | 752 | offset: Writer.Offset, |
| 752 | limit: Writer.Limit, | 753 | limit: Limit, |
| 753 | ) WriteFileReadingError!void { | 754 | ) WriteFileReadingError!void { |
| 754 | if (offset.toInt()) |start_pos| { | 755 | if (offset.toInt()) |start_pos| { |
| 755 | var remaining = limit; | 756 | var remaining = limit; |
lib/std/io/Reader.zig+19-49| ... | @@ -5,6 +5,7 @@ const BufferedWriter = std.io.BufferedWriter; | ... | @@ -5,6 +5,7 @@ const BufferedWriter = std.io.BufferedWriter; |
| 5 | const BufferedReader = std.io.BufferedReader; | 5 | const BufferedReader = std.io.BufferedReader; |
| 6 | const Allocator = std.mem.Allocator; | 6 | const Allocator = std.mem.Allocator; |
| 7 | const ArrayList = std.ArrayListUnmanaged; | 7 | const ArrayList = std.ArrayListUnmanaged; |
| 8 | const Limit = std.io.Limit; | ||
| 8 | 9 | ||
| 9 | pub const Limited = @import("Reader/Limited.zig"); | 10 | pub const Limited = @import("Reader/Limited.zig"); |
| 10 | 11 | ||
| ... | @@ -25,21 +26,7 @@ pub const VTable = struct { | ... | @@ -25,21 +26,7 @@ pub const VTable = struct { |
| 25 | /// Implementations are encouraged to utilize mandatory minimum buffer | 26 | /// Implementations are encouraged to utilize mandatory minimum buffer |
| 26 | /// sizes combined with short reads (returning a value less than `limit`) | 27 | /// sizes combined with short reads (returning a value less than `limit`) |
| 27 | /// in order to minimize complexity. | 28 | /// in order to minimize complexity. |
| 28 | read: *const fn (context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize, | 29 | read: *const fn (context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) StreamError!usize, |
| 29 | |||
| 30 | /// Writes bytes from the internally tracked stream position to `data`. | ||
| 31 | /// | ||
| 32 | /// Returns the number of bytes written, which will be at minimum `0` and | ||
| 33 | /// at most the sum of each data slice length. The number of bytes read, | ||
| 34 | /// including zero, does not indicate end of stream. | ||
| 35 | /// | ||
| 36 | /// The reader's internal logical seek position moves forward in accordance | ||
| 37 | /// with the number of bytes returned from this function. | ||
| 38 | /// | ||
| 39 | /// Implementations are encouraged to utilize mandatory minimum buffer | ||
| 40 | /// sizes combined with short reads (returning a value less than the total | ||
| 41 | /// buffer capacity inside `data`) in order to minimize complexity. | ||
| 42 | readVec: *const fn (context: ?*anyopaque, data: []const []u8) Error!usize, | ||
| 43 | 30 | ||
| 44 | /// Consumes bytes from the internally tracked stream position without | 31 | /// Consumes bytes from the internally tracked stream position without |
| 45 | /// providing access to them. | 32 | /// providing access to them. |
| ... | @@ -54,10 +41,15 @@ pub const VTable = struct { | ... | @@ -54,10 +41,15 @@ pub const VTable = struct { |
| 54 | /// Implementations are encouraged to utilize mandatory minimum buffer | 41 | /// Implementations are encouraged to utilize mandatory minimum buffer |
| 55 | /// sizes combined with short reads (returning a value less than `limit`) | 42 | /// sizes combined with short reads (returning a value less than `limit`) |
| 56 | /// in order to minimize complexity. | 43 | /// in order to minimize complexity. |
| 57 | discard: *const fn (context: ?*anyopaque, limit: Limit) Error!usize, | 44 | /// |
| 45 | /// If an implementation sets this to `null`, a default implementation is | ||
| 46 | /// provided which is based on calling `read`, borrowing | ||
| 47 | /// `BufferedReader.buffer` to construct a temporary `BufferedWriter` and | ||
| 48 | /// ignoring the written data. | ||
| 49 | discard: *const fn (context: ?*anyopaque, limit: Limit) DiscardError!usize = null, | ||
| 58 | }; | 50 | }; |
| 59 | 51 | ||
| 60 | pub const RwError = error{ | 52 | pub const StreamError = error{ |
| 61 | /// See the `Reader` implementation for detailed diagnostics. | 53 | /// See the `Reader` implementation for detailed diagnostics. |
| 62 | ReadFailed, | 54 | ReadFailed, |
| 63 | /// See the `Writer` implementation for detailed diagnostics. | 55 | /// See the `Writer` implementation for detailed diagnostics. |
| ... | @@ -67,7 +59,7 @@ pub const RwError = error{ | ... | @@ -67,7 +59,7 @@ pub const RwError = error{ |
| 67 | EndOfStream, | 59 | EndOfStream, |
| 68 | }; | 60 | }; |
| 69 | 61 | ||
| 70 | pub const Error = error{ | 62 | pub const DiscardError = error{ |
| 71 | /// See the `Reader` implementation for detailed diagnostics. | 63 | /// See the `Reader` implementation for detailed diagnostics. |
| 72 | ReadFailed, | 64 | ReadFailed, |
| 73 | EndOfStream, | 65 | EndOfStream, |
| ... | @@ -85,10 +77,7 @@ pub const ShortError = error{ | ... | @@ -85,10 +77,7 @@ pub const ShortError = error{ |
| 85 | ReadFailed, | 77 | ReadFailed, |
| 86 | }; | 78 | }; |
| 87 | 79 | ||
| 88 | /// TODO: no pub | 80 | pub fn read(r: Reader, bw: *BufferedWriter, limit: Limit) StreamError!usize { |
| 89 | pub const Limit = std.io.Limit; | ||
| 90 | |||
| 91 | pub fn read(r: Reader, bw: *BufferedWriter, limit: Limit) RwError!usize { | ||
| 92 | const before = bw.count; | 81 | const before = bw.count; |
| 93 | const n = try r.vtable.read(r.context, bw, limit); | 82 | const n = try r.vtable.read(r.context, bw, limit); |
| 94 | assert(n <= @intFromEnum(limit)); | 83 | assert(n <= @intFromEnum(limit)); |
| ... | @@ -96,11 +85,7 @@ pub fn read(r: Reader, bw: *BufferedWriter, limit: Limit) RwError!usize { | ... | @@ -96,11 +85,7 @@ pub fn read(r: Reader, bw: *BufferedWriter, limit: Limit) RwError!usize { |
| 96 | return n; | 85 | return n; |
| 97 | } | 86 | } |
| 98 | 87 | ||
| 99 | pub fn readVec(r: Reader, data: []const []u8) Error!usize { | 88 | pub fn discard(r: Reader, limit: Limit) DiscardError!usize { |
| 100 | return r.vtable.readVec(r.context, data); | ||
| 101 | } | ||
| 102 | |||
| 103 | pub fn discard(r: Reader, limit: Limit) Error!usize { | ||
| 104 | const n = try r.vtable.discard(r.context, limit); | 89 | const n = try r.vtable.discard(r.context, limit); |
| 105 | assert(n <= @intFromEnum(limit)); | 90 | assert(n <= @intFromEnum(limit)); |
| 106 | return n; | 91 | return n; |
| ... | @@ -188,7 +173,6 @@ pub const failing: Reader = .{ | ... | @@ -188,7 +173,6 @@ pub const failing: Reader = .{ |
| 188 | .context = undefined, | 173 | .context = undefined, |
| 189 | .vtable = &.{ | 174 | .vtable = &.{ |
| 190 | .read = failingRead, | 175 | .read = failingRead, |
| 191 | .readVec = failingReadVec, | ||
| 192 | .discard = failingDiscard, | 176 | .discard = failingDiscard, |
| 193 | }, | 177 | }, |
| 194 | }; | 178 | }; |
| ... | @@ -197,7 +181,6 @@ pub const ending: Reader = .{ | ... | @@ -197,7 +181,6 @@ pub const ending: Reader = .{ |
| 197 | .context = undefined, | 181 | .context = undefined, |
| 198 | .vtable = &.{ | 182 | .vtable = &.{ |
| 199 | .read = endingRead, | 183 | .read = endingRead, |
| 200 | .readVec = endingReadVec, | ||
| 201 | .discard = endingDiscard, | 184 | .discard = endingDiscard, |
| 202 | }, | 185 | }, |
| 203 | }; | 186 | }; |
| ... | @@ -222,39 +205,27 @@ pub fn limited(r: Reader, limit: Limit) Limited { | ... | @@ -222,39 +205,27 @@ pub fn limited(r: Reader, limit: Limit) Limited { |
| 222 | }; | 205 | }; |
| 223 | } | 206 | } |
| 224 | 207 | ||
| 225 | fn endingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize { | 208 | fn endingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) StreamError!usize { |
| 226 | _ = context; | 209 | _ = context; |
| 227 | _ = bw; | 210 | _ = bw; |
| 228 | _ = limit; | 211 | _ = limit; |
| 229 | return error.EndOfStream; | 212 | return error.EndOfStream; |
| 230 | } | 213 | } |
| 231 | 214 | ||
| 232 | fn endingReadVec(context: ?*anyopaque, data: []const []u8) Error!usize { | 215 | fn endingDiscard(context: ?*anyopaque, limit: Limit) DiscardError!usize { |
| 233 | _ = context; | ||
| 234 | _ = data; | ||
| 235 | return error.EndOfStream; | ||
| 236 | } | ||
| 237 | |||
| 238 | fn endingDiscard(context: ?*anyopaque, limit: Limit) Error!usize { | ||
| 239 | _ = context; | 216 | _ = context; |
| 240 | _ = limit; | 217 | _ = limit; |
| 241 | return error.EndOfStream; | 218 | return error.EndOfStream; |
| 242 | } | 219 | } |
| 243 | 220 | ||
| 244 | fn failingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize { | 221 | fn failingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) StreamError!usize { |
| 245 | _ = context; | 222 | _ = context; |
| 246 | _ = bw; | 223 | _ = bw; |
| 247 | _ = limit; | 224 | _ = limit; |
| 248 | return error.ReadFailed; | 225 | return error.ReadFailed; |
| 249 | } | 226 | } |
| 250 | 227 | ||
| 251 | fn failingReadVec(context: ?*anyopaque, data: []const []u8) Error!usize { | 228 | fn failingDiscard(context: ?*anyopaque, limit: Limit) DiscardError!usize { |
| 252 | _ = context; | ||
| 253 | _ = data; | ||
| 254 | return error.ReadFailed; | ||
| 255 | } | ||
| 256 | |||
| 257 | fn failingDiscard(context: ?*anyopaque, limit: Limit) Error!usize { | ||
| 258 | _ = context; | 229 | _ = context; |
| 259 | _ = limit; | 230 | _ = limit; |
| 260 | return error.ReadFailed; | 231 | return error.ReadFailed; |
| ... | @@ -308,7 +279,6 @@ pub fn Hashed(comptime Hasher: type) type { | ... | @@ -308,7 +279,6 @@ pub fn Hashed(comptime Hasher: type) type { |
| 308 | .context = this, | 279 | .context = this, |
| 309 | .vtable = &.{ | 280 | .vtable = &.{ |
| 310 | .read = @This().read, | 281 | .read = @This().read, |
| 311 | .readVec = @This().readVec, | ||
| 312 | .discard = @This().discard, | 282 | .discard = @This().discard, |
| 313 | }, | 283 | }, |
| 314 | }, | 284 | }, |
| ... | @@ -318,7 +288,7 @@ pub fn Hashed(comptime Hasher: type) type { | ... | @@ -318,7 +288,7 @@ pub fn Hashed(comptime Hasher: type) type { |
| 318 | }; | 288 | }; |
| 319 | } | 289 | } |
| 320 | 290 | ||
| 321 | fn read(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize { | 291 | fn read(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) StreamError!usize { |
| 322 | const this: *@This() = @alignCast(@ptrCast(context)); | 292 | const this: *@This() = @alignCast(@ptrCast(context)); |
| 323 | const slice = limit.slice(try bw.writableSliceGreedy(1)); | 293 | const slice = limit.slice(try bw.writableSliceGreedy(1)); |
| 324 | const n = try this.in.readVec(&.{slice}); | 294 | const n = try this.in.readVec(&.{slice}); |
| ... | @@ -327,7 +297,7 @@ pub fn Hashed(comptime Hasher: type) type { | ... | @@ -327,7 +297,7 @@ pub fn Hashed(comptime Hasher: type) type { |
| 327 | return n; | 297 | return n; |
| 328 | } | 298 | } |
| 329 | 299 | ||
| 330 | fn discard(context: ?*anyopaque, limit: Limit) Error!usize { | 300 | fn discard(context: ?*anyopaque, limit: Limit) DiscardError!usize { |
| 331 | const this: *@This() = @alignCast(@ptrCast(context)); | 301 | const this: *@This() = @alignCast(@ptrCast(context)); |
| 332 | var bw = this.hasher.writable(&.{}); | 302 | var bw = this.hasher.writable(&.{}); |
| 333 | const n = this.in.read(&bw, limit) catch |err| switch (err) { | 303 | const n = this.in.read(&bw, limit) catch |err| switch (err) { |
| ... | @@ -337,7 +307,7 @@ pub fn Hashed(comptime Hasher: type) type { | ... | @@ -337,7 +307,7 @@ pub fn Hashed(comptime Hasher: type) type { |
| 337 | return n; | 307 | return n; |
| 338 | } | 308 | } |
| 339 | 309 | ||
| 340 | fn readVec(context: ?*anyopaque, data: []const []u8) Error!usize { | 310 | fn readVec(context: ?*anyopaque, data: []const []u8) DiscardError!usize { |
| 341 | const this: *@This() = @alignCast(@ptrCast(context)); | 311 | const this: *@This() = @alignCast(@ptrCast(context)); |
| 342 | const n = try this.in.readVec(data); | 312 | const n = try this.in.readVec(data); |
| 343 | var remaining: usize = n; | 313 | var remaining: usize = n; |
lib/std/io/Writer.zig+3-3| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const assert = std.debug.assert; | 2 | const assert = std.debug.assert; |
| 3 | const Writer = @This(); | 3 | const Writer = @This(); |
| 4 | const Limit = std.io.Limit; | ||
| 4 | 5 | ||
| 5 | pub const Null = @import("Writer/Null.zig"); | 6 | pub const Null = @import("Writer/Null.zig"); |
| 6 | 7 | ||
| ... | @@ -47,6 +48,8 @@ pub const VTable = struct { | ... | @@ -47,6 +48,8 @@ pub const VTable = struct { |
| 47 | offset: Offset, | 48 | offset: Offset, |
| 48 | /// Maximum amount of bytes to read from the file. Implementations may | 49 | /// Maximum amount of bytes to read from the file. Implementations may |
| 49 | /// assume that the file size does not exceed this amount. | 50 | /// assume that the file size does not exceed this amount. |
| 51 | /// | ||
| 52 | /// `headers_and_trailers` do not count towards this limit. | ||
| 50 | limit: Limit, | 53 | limit: Limit, |
| 51 | /// Headers and trailers must be passed together so that in case `len` is | 54 | /// Headers and trailers must be passed together so that in case `len` is |
| 52 | /// zero, they can be forwarded directly to `VTable.writeVec`. | 55 | /// zero, they can be forwarded directly to `VTable.writeVec`. |
| ... | @@ -68,9 +71,6 @@ pub const FileError = std.fs.File.PReadError || error{ | ... | @@ -68,9 +71,6 @@ pub const FileError = std.fs.File.PReadError || error{ |
| 68 | Unimplemented, | 71 | Unimplemented, |
| 69 | }; | 72 | }; |
| 70 | 73 | ||
| 71 | /// TODO: no pub | ||
| 72 | pub const Limit = std.io.Limit; | ||
| 73 | |||
| 74 | pub const Offset = enum(u64) { | 74 | pub const Offset = enum(u64) { |
| 75 | zero = 0, | 75 | zero = 0, |
| 76 | /// Indicates to read the file as a stream. | 76 | /// Indicates to read the file as a stream. |