authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-27 20:18:20-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:29-07:00
log74c56376ee2dfb6100fd8da6cb03425b0e48a779
tree26849cc3a5360ad915ab21cdc616a383866d1381
parentda303bdaf1ae8717df2d4ede9e7dfb215636ae33

std: update http.WebSocket to new API


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(
894pub fn reader(c: *Client) Reader {894pub 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}
904900
...@@ -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}
12271223
1228fn 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
1237fn 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
1246fn failRead(c: *Client, err: ReadError) error{ReadFailed} {1224fn 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}
901901
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.
902pub const Reader = struct {911pub 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 }
953962
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");
7pub const HeadParser = @import("http/HeadParser.zig");7pub const HeadParser = @import("http/HeadParser.zig");
8pub const ChunkParser = @import("http/ChunkParser.zig");8pub const ChunkParser = @import("http/ChunkParser.zig");
9pub const HeaderIterator = @import("http/HeaderIterator.zig");9pub const HeaderIterator = @import("http/HeaderIterator.zig");
10pub const WebSocket = @import("http/WebSocket.zig");
1110
12pub const Version = enum {11pub 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 }
537536
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 }
714713
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 {
734733
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,
813812
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;
12421240
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 to57 /// 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 };
6066
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);
328335
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 "";
334341
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;
412409
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 it411 @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 }
429413
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 }
438418
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 }
446427
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;
450438
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 }
462450
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 than456
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 };
472485
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", this528 /// In the case that the request contains "expect: 100-continue", this
474 /// function writes the continuation header, which means it can fail with a529 /// function writes the continuation header, which means it can fail with a
475 /// write error. After sending the continuation header, it sets the530 /// 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 }
492555
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 body573 /// 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
611pub 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
3const builtin = @import("builtin");
4const std = @import("std");
5const WebSocket = @This();
6const assert = std.debug.assert;
7const native_endian = builtin.cpu.arch.endian();
8
9key: []const u8,
10request: *std.http.Server.Request,
11recv_fifo: std.fifo.LinearFifo(u8, .Slice),
12reader: std.io.BufferedReader,
13body_writer: std.http.BodyWriter,
14/// Number of bytes that have been peeked but not discarded yet.
15outstanding_len: usize,
16
17pub const InitError = error{WebSocketUpgradeMissingKey} ||
18 std.http.Server.Request.ReaderError;
19
20pub 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
78pub 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
86pub const Header1 = packed struct(u8) {
87 payload_len: enum(u7) {
88 len16 = 126,
89 len64 = 127,
90 _,
91 },
92 mask: bool,
93};
94
95pub 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
107pub const ReadSmallTextMessageError = error{
108 ConnectionClose,
109 UnexpectedOpCode,
110 MessageTooBig,
111 MissingMaskBit,
112} || RecvError;
113
114pub 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`.
122pub 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
165const RecvError = std.http.Server.Request.ReadError || error{EndOfStream};
166
167fn 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
186fn 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
194pub 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
201pub 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;
6const testing = std.testing;6const testing = std.testing;
7const BufferedWriter = std.io.BufferedWriter;7const BufferedWriter = std.io.BufferedWriter;
8const Reader = std.io.Reader;8const Reader = std.io.Reader;
9const Writer = std.io.Writer;
9const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
10const ArrayList = std.ArrayListUnmanaged;11const ArrayList = std.ArrayListUnmanaged;
12const Limit = std.io.Limit;
1113
12const BufferedReader = @This();14const BufferedReader = @This();
1315
...@@ -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}
6466
65/// Equivalent semantics to `std.io.Reader.VTable.read`.67/// Equivalent semantics to `std.io.Reader.VTable.read`.
66pub fn read(br: *BufferedReader, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {68pub 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}
6971
70/// Equivalent semantics to `std.io.Reader.VTable.discard`.72/// Equivalent semantics to `std.io.Reader.VTable.discard`.
71pub fn discard(br: *BufferedReader, limit: Reader.Limit) Reader.Error!usize {73pub fn discard(br: *BufferedReader, limit: Limit) Reader.Error!usize {
72 return passthruDiscard(br, limit);74 return passthruDiscard(br, limit);
73}75}
7476
...@@ -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}
9193
92/// "Pump" data from the reader to the writer.94/// "Pump" data from the reader to the writer.
93pub fn readAll(br: *BufferedReader, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!void {95pub 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}
114116
115/// Equivalent to `readVec` but reads at most `limit` bytes.117/// Equivalent to `readVec` but reads at most `limit` bytes.
116pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Reader.Limit) Reader.Error!usize {118pub 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}
167169
168fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {170fn 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}
178180
179fn passthruDiscard(context: ?*anyopaque, limit: Reader.Limit) Reader.Error!usize {181fn 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}
197196
...@@ -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}
202201
202fn 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
230fn 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
239fn 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 as268/// 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`
478pub fn readRemainingAlloc(r: Reader, gpa: Allocator, limit: Reader.Limit) Reader.LimitedAllocError![]u8 {543pub 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.
683pub fn readDelimiter(br: *BufferedReader, bw: *BufferedWriter, delimiter: u8) Reader.RwError!usize {748pub 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();
5const Writer = std.io.Writer;5const Writer = std.io.Writer;
6const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
7const testing = std.testing;7const testing = std.testing;
8const Limit = std.io.Limit;
89
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 {
4243
43const fixed_vtable: Writer.VTable = .{44const fixed_vtable: Writer.VTable = .{
44 .writeSplat = fixedWriteSplat,45 .writeSplat = fixedWriteSplat,
45 .writeFile = Writer.failingWriteFile,46 .writeFile = Writer.unimplementedWriteFile,
46};47};
4748
48/// Replaces the `BufferedWriter` with one that writes to `buffer` and returns49/// 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}
8485
85pub fn flushLimit(bw: *BufferedWriter, limit: Writer.Limit) Writer.Error!void {86pub 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 the655 /// 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` is658 /// 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;
5const BufferedReader = std.io.BufferedReader;5const BufferedReader = std.io.BufferedReader;
6const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
7const ArrayList = std.ArrayListUnmanaged;7const ArrayList = std.ArrayListUnmanaged;
8const Limit = std.io.Limit;
89
9pub const Limited = @import("Reader/Limited.zig");10pub const Limited = @import("Reader/Limited.zig");
1011
...@@ -25,21 +26,7 @@ pub const VTable = struct {...@@ -25,21 +26,7 @@ pub const VTable = struct {
25 /// Implementations are encouraged to utilize mandatory minimum buffer26 /// 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,
4330
44 /// Consumes bytes from the internally tracked stream position without31 /// 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 buffer41 /// 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};
5951
60pub const RwError = error{52pub 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};
6961
70pub const Error = error{62pub 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};
8779
88/// TODO: no pub80pub fn read(r: Reader, bw: *BufferedWriter, limit: Limit) StreamError!usize {
89pub const Limit = std.io.Limit;
90
91pub 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}
9887
99pub fn readVec(r: Reader, data: []const []u8) Error!usize {88pub fn discard(r: Reader, limit: Limit) DiscardError!usize {
100 return r.vtable.readVec(r.context, data);
101}
102
103pub 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}
224207
225fn endingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize {208fn 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}
231214
232fn endingReadVec(context: ?*anyopaque, data: []const []u8) Error!usize {215fn endingDiscard(context: ?*anyopaque, limit: Limit) DiscardError!usize {
233 _ = context;
234 _ = data;
235 return error.EndOfStream;
236}
237
238fn 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}
243220
244fn failingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize {221fn 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}
250227
251fn failingReadVec(context: ?*anyopaque, data: []const []u8) Error!usize {228fn failingDiscard(context: ?*anyopaque, limit: Limit) DiscardError!usize {
252 _ = context;
253 _ = data;
254 return error.ReadFailed;
255}
256
257fn 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 }
320290
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 }
329299
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 }
339309
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 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;2const assert = std.debug.assert;
3const Writer = @This();3const Writer = @This();
4const Limit = std.io.Limit;
45
5pub const Null = @import("Writer/Null.zig");6pub const Null = @import("Writer/Null.zig");
67
...@@ -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 may49 /// 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` is54 /// 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};
7073
71/// TODO: no pub
72pub const Limit = std.io.Limit;
73
74pub const Offset = enum(u64) {74pub 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.