| author | |
| committer | |
| log | b428612a202a76f7a0aee18bde00c104753f3e60 |
| tree | 93f059f67c5ddee228e5e2e18a81e6bde96b4b1b |
| parent | 774df26835069039ba739828a7619393de01a5f2 |
10 files changed, 339 insertions(+), 250 deletions(-)
lib/std/Io.zig+26-19| ... | @@ -719,31 +719,38 @@ pub const Timestamp = struct { | ... | @@ -719,31 +719,38 @@ pub const Timestamp = struct { |
| 719 | /// | 719 | /// |
| 720 | /// The epoch is implementation-defined. For example NTFS/Windows uses | 720 | /// The epoch is implementation-defined. For example NTFS/Windows uses |
| 721 | /// 1601-01-01. | 721 | /// 1601-01-01. |
| 722 | realtime, | 722 | real, |
| 723 | /// A nonsettable system-wide clock that represents time since some | 723 | /// A nonsettable system-wide clock that represents time since some |
| 724 | /// unspecified point in the past. | 724 | /// unspecified point in the past. |
| 725 | /// | 725 | /// |
| 726 | /// On Linux, corresponds to how long the system has been running since | 726 | /// Monotonic: Guarantees that the time returned by consecutive calls |
| 727 | /// it booted. | 727 | /// will not go backwards, but successive calls may return identical |
| 728 | /// (not-increased) time values. | ||
| 728 | /// | 729 | /// |
| 729 | /// Not affected by discontinuous jumps in the system time (e.g., if | 730 | /// Not affected by discontinuous jumps in the system time (e.g., if |
| 730 | /// the system administrator manually changes the clock), but is | 731 | /// the system administrator manually changes the clock), but may be |
| 731 | /// affected by frequency adjustments. **This clock does not count time | 732 | /// affected by frequency adjustments. |
| 732 | /// that the system is suspended.** | ||
| 733 | /// | 733 | /// |
| 734 | /// Guarantees that the time returned by consecutive calls will not go | 734 | /// This clock expresses intent to **exclude time that the system is |
| 735 | /// backwards, but successive calls may return identical | 735 | /// suspended**. However, implementations may be unable to satisify |
| 736 | /// (not-increased) time values. | 736 | /// this, and may include that time. |
| 737 | /// | ||
| 738 | /// * On Linux, corresponds `CLOCK_MONOTONIC`. | ||
| 739 | /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`. | ||
| 740 | awake, | ||
| 741 | /// Identical to `awake` except it expresses intent to include time | ||
| 742 | /// that the system is suspended, however, it may be implemented | ||
| 743 | /// identically to `awake`. | ||
| 737 | /// | 744 | /// |
| 738 | /// May or may not include time the system is suspended, but | 745 | /// * On Linux, corresponds `CLOCK_BOOTTIME`. |
| 739 | /// implementations should exclude that time if possible. | 746 | /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`. |
| 740 | monotonic, | 747 | boot, |
| 741 | /// Identical to `monotonic` except it also includes any time that the | 748 | /// Tracks the amount of CPU in user or kernel mode used by the calling |
| 742 | /// system is suspended, if possible. However, it may be implemented | 749 | /// process. |
| 743 | /// identically to `monotonic`. | 750 | cpu_process, |
| 744 | boottime, | 751 | /// Tracks the amount of CPU in user or kernel mode used by the calling |
| 745 | process_cputime_id, | 752 | /// thread. |
| 746 | thread_cputime_id, | 753 | cpu_thread, |
| 747 | }; | 754 | }; |
| 748 | 755 | ||
| 749 | pub fn durationTo(from: Timestamp, to: Timestamp) Duration { | 756 | pub fn durationTo(from: Timestamp, to: Timestamp) Duration { |
| ... | @@ -825,7 +832,7 @@ pub const Duration = struct { | ... | @@ -825,7 +832,7 @@ pub const Duration = struct { |
| 825 | } | 832 | } |
| 826 | 833 | ||
| 827 | pub fn sleep(duration: Duration, io: Io) SleepError!void { | 834 | pub fn sleep(duration: Duration, io: Io) SleepError!void { |
| 828 | return io.vtable.sleep(io.userdata, .{ .duration = .{ .duration = duration, .clock = .monotonic } }); | 835 | return io.vtable.sleep(io.userdata, .{ .duration = .{ .duration = duration, .clock = .awake } }); |
| 829 | } | 836 | } |
| 830 | }; | 837 | }; |
| 831 | 838 |
lib/std/Io/File.zig+5| ... | @@ -319,6 +319,11 @@ pub const Reader = struct { | ... | @@ -319,6 +319,11 @@ pub const Reader = struct { |
| 319 | }; | 319 | }; |
| 320 | } | 320 | } |
| 321 | 321 | ||
| 322 | /// Takes a legacy `std.fs.File` to help with upgrading. | ||
| 323 | pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader { | ||
| 324 | return .init(.{ .handle = file.handle }, io, buffer); | ||
| 325 | } | ||
| 326 | |||
| 322 | pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { | 327 | pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { |
| 323 | return .{ | 328 | return .{ |
| 324 | .io = io, | 329 | .io = io, |
lib/std/Io/Threaded.zig+23-12| ... | @@ -1032,7 +1032,7 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Err | ... | @@ -1032,7 +1032,7 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Err |
| 1032 | // and uses the NTFS/Windows epoch, which is 1601-01-01. | 1032 | // and uses the NTFS/Windows epoch, which is 1601-01-01. |
| 1033 | return @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100; | 1033 | return @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100; |
| 1034 | }, | 1034 | }, |
| 1035 | .monotonic, .boottime => { | 1035 | .monotonic, .uptime => { |
| 1036 | // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. | 1036 | // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. |
| 1037 | return .{ .timestamp = windows.QueryPerformanceCounter() }; | 1037 | return .{ .timestamp = windows.QueryPerformanceCounter() }; |
| 1038 | }, | 1038 | }, |
| ... | @@ -1132,7 +1132,8 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { | ... | @@ -1132,7 +1132,8 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 1132 | .sec = std.math.maxInt(sec_type), | 1132 | .sec = std.math.maxInt(sec_type), |
| 1133 | .nsec = std.math.maxInt(nsec_type), | 1133 | .nsec = std.math.maxInt(nsec_type), |
| 1134 | }; | 1134 | }; |
| 1135 | if (d.clock != .monotonic) return error.UnsupportedClock; | 1135 | // TODO check which clock nanosleep uses on this host |
| 1136 | // and return error.UnsupportedClock if it does not match | ||
| 1136 | const ns = d.duration.nanoseconds; | 1137 | const ns = d.duration.nanoseconds; |
| 1137 | break :t .{ | 1138 | break :t .{ |
| 1138 | .sec = @intCast(@divFloor(ns, std.time.ns_per_s)), | 1139 | .sec = @intCast(@divFloor(ns, std.time.ns_per_s)), |
| ... | @@ -1331,11 +1332,15 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio | ... | @@ -1331,11 +1332,15 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio |
| 1331 | fn ipConnectPosix( | 1332 | fn ipConnectPosix( |
| 1332 | userdata: ?*anyopaque, | 1333 | userdata: ?*anyopaque, |
| 1333 | address: *const Io.net.IpAddress, | 1334 | address: *const Io.net.IpAddress, |
| 1334 | options: Io.net.IpAddress.BindOptions, | 1335 | options: Io.net.IpAddress.ConnectOptions, |
| 1335 | ) Io.net.IpAddress.ConnectError!Io.net.Stream { | 1336 | ) Io.net.IpAddress.ConnectError!Io.net.Stream { |
| 1337 | if (options.timeout != .none) @panic("TODO"); | ||
| 1336 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 1338 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 1337 | const family = posixAddressFamily(address); | 1339 | const family = posixAddressFamily(address); |
| 1338 | const socket_fd = try openSocketPosix(pool, family, options); | 1340 | const socket_fd = try openSocketPosix(pool, family, .{ |
| 1341 | .mode = options.mode, | ||
| 1342 | .protocol = options.protocol, | ||
| 1343 | }); | ||
| 1339 | var storage: PosixAddress = undefined; | 1344 | var storage: PosixAddress = undefined; |
| 1340 | var addr_len = addressToPosix(address, &storage); | 1345 | var addr_len = addressToPosix(address, &storage); |
| 1341 | try posixConnect(pool, socket_fd, &storage.any, addr_len); | 1346 | try posixConnect(pool, socket_fd, &storage.any, addr_len); |
| ... | @@ -1490,11 +1495,11 @@ fn netSend( | ... | @@ -1490,11 +1495,11 @@ fn netSend( |
| 1490 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 1495 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 1491 | 1496 | ||
| 1492 | const posix_flags: u32 = | 1497 | const posix_flags: u32 = |
| 1493 | @as(u32, if (flags.confirm) posix.MSG.CONFIRM else 0) | | 1498 | @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) | |
| 1494 | @as(u32, if (flags.dont_route) posix.MSG.DONTROUTE else 0) | | 1499 | @as(u32, if (flags.dont_route) posix.MSG.DONTROUTE else 0) | |
| 1495 | @as(u32, if (flags.eor) posix.MSG.EOR else 0) | | 1500 | @as(u32, if (flags.eor) posix.MSG.EOR else 0) | |
| 1496 | @as(u32, if (flags.oob) posix.MSG.OOB else 0) | | 1501 | @as(u32, if (flags.oob) posix.MSG.OOB else 0) | |
| 1497 | @as(u32, if (flags.fastopen) posix.MSG.FASTOPEN else 0) | | 1502 | @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) | |
| 1498 | posix.MSG.NOSIGNAL; | 1503 | posix.MSG.NOSIGNAL; |
| 1499 | 1504 | ||
| 1500 | var i: usize = 0; | 1505 | var i: usize = 0; |
| ... | @@ -2024,11 +2029,17 @@ fn recoverableOsBugDetected() void { | ... | @@ -2024,11 +2029,17 @@ fn recoverableOsBugDetected() void { |
| 2024 | 2029 | ||
| 2025 | fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t { | 2030 | fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t { |
| 2026 | return switch (clock) { | 2031 | return switch (clock) { |
| 2027 | .realtime => posix.CLOCK.REALTIME, | 2032 | .real => posix.CLOCK.REALTIME, |
| 2028 | .monotonic => posix.CLOCK.MONOTONIC, | 2033 | .awake => switch (builtin.os.tag) { |
| 2029 | .boottime => posix.CLOCK.BOOTTIME, | 2034 | .macos, .ios, .watchos, .tvos => posix.CLOCK.UPTIME_RAW, |
| 2030 | .process_cputime_id => posix.CLOCK.PROCESS_CPUTIME_ID, | 2035 | else => posix.CLOCK.MONOTONIC, |
| 2031 | .thread_cputime_id => posix.CLOCK.THREAD_CPUTIME_ID, | 2036 | }, |
| 2037 | .boot => switch (builtin.os.tag) { | ||
| 2038 | .macos, .ios, .watchos, .tvos => posix.CLOCK.MONOTONIC_RAW, | ||
| 2039 | else => posix.CLOCK.BOOTTIME, | ||
| 2040 | }, | ||
| 2041 | .cpu_process => posix.CLOCK.PROCESS_CPUTIME_ID, | ||
| 2042 | .cpu_thread => posix.CLOCK.THREAD_CPUTIME_ID, | ||
| 2032 | }; | 2043 | }; |
| 2033 | } | 2044 | } |
| 2034 | 2045 | ||
| ... | @@ -2036,7 +2047,7 @@ fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t { | ... | @@ -2036,7 +2047,7 @@ fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t { |
| 2036 | return switch (clock) { | 2047 | return switch (clock) { |
| 2037 | .realtime => .REALTIME, | 2048 | .realtime => .REALTIME, |
| 2038 | .monotonic => .MONOTONIC, | 2049 | .monotonic => .MONOTONIC, |
| 2039 | .boottime => .MONOTONIC, | 2050 | .uptime => .MONOTONIC, |
| 2040 | .process_cputime_id => .PROCESS_CPUTIME_ID, | 2051 | .process_cputime_id => .PROCESS_CPUTIME_ID, |
| 2041 | .thread_cputime_id => .THREAD_CPUTIME_ID, | 2052 | .thread_cputime_id => .THREAD_CPUTIME_ID, |
| 2042 | }; | 2053 | }; |
lib/std/Io/net.zig+28-9| ... | @@ -186,7 +186,7 @@ pub const IpAddress = union(enum) { | ... | @@ -186,7 +186,7 @@ pub const IpAddress = union(enum) { |
| 186 | /// Waits for a TCP connection. When using this API, `bind` does not need | 186 | /// Waits for a TCP connection. When using this API, `bind` does not need |
| 187 | /// to be called. The returned `Server` has an open `stream`. | 187 | /// to be called. The returned `Server` has an open `stream`. |
| 188 | pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server { | 188 | pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server { |
| 189 | return io.vtable.tcpListen(io.userdata, address, options); | 189 | return io.vtable.listen(io.userdata, address, options); |
| 190 | } | 190 | } |
| 191 | 191 | ||
| 192 | pub const BindError = error{ | 192 | pub const BindError = error{ |
| ... | @@ -236,6 +236,8 @@ pub const IpAddress = union(enum) { | ... | @@ -236,6 +236,8 @@ pub const IpAddress = union(enum) { |
| 236 | AddressInUse, | 236 | AddressInUse, |
| 237 | AddressUnavailable, | 237 | AddressUnavailable, |
| 238 | AddressFamilyUnsupported, | 238 | AddressFamilyUnsupported, |
| 239 | /// Insufficient memory or other resource internal to the operating system. | ||
| 240 | SystemResources, | ||
| 239 | ConnectionPending, | 241 | ConnectionPending, |
| 240 | ConnectionRefused, | 242 | ConnectionRefused, |
| 241 | ConnectionResetByPeer, | 243 | ConnectionResetByPeer, |
| ... | @@ -246,12 +248,23 @@ pub const IpAddress = union(enum) { | ... | @@ -246,12 +248,23 @@ pub const IpAddress = union(enum) { |
| 246 | /// One of the `ConnectOptions` is not supported by the Io | 248 | /// One of the `ConnectOptions` is not supported by the Io |
| 247 | /// implementation. | 249 | /// implementation. |
| 248 | OptionUnsupported, | 250 | OptionUnsupported, |
| 249 | } || Io.UnexpectedError || Io.Cancelable; | 251 | /// Per-process limit on the number of open file descriptors has been reached. |
| 252 | ProcessFdQuotaExceeded, | ||
| 253 | /// System-wide limit on the total number of open files has been reached. | ||
| 254 | SystemFdQuotaExceeded, | ||
| 255 | ProtocolUnsupportedBySystem, | ||
| 256 | ProtocolUnsupportedByAddressFamily, | ||
| 257 | SocketModeUnsupported, | ||
| 258 | } || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable; | ||
| 250 | 259 | ||
| 251 | pub const ConnectOptions = BindOptions; | 260 | pub const ConnectOptions = struct { |
| 261 | mode: Socket.Mode, | ||
| 262 | protocol: ?Protocol = null, | ||
| 263 | timeout: Io.Timeout = .none, | ||
| 264 | }; | ||
| 252 | 265 | ||
| 253 | /// Initiates a connection-oriented network stream. | 266 | /// Initiates a connection-oriented network stream. |
| 254 | pub fn connect(address: IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream { | 267 | pub fn connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream { |
| 255 | return io.vtable.ipConnect(io.userdata, address, options); | 268 | return io.vtable.ipConnect(io.userdata, address, options); |
| 256 | } | 269 | } |
| 257 | }; | 270 | }; |
| ... | @@ -997,7 +1010,7 @@ pub const Stream = struct { | ... | @@ -997,7 +1010,7 @@ pub const Stream = struct { |
| 997 | socket: Socket, | 1010 | socket: Socket, |
| 998 | 1011 | ||
| 999 | pub fn close(s: *Stream, io: Io) void { | 1012 | pub fn close(s: *Stream, io: Io) void { |
| 1000 | io.vtable.netClose(io.userdata, s.socket); | 1013 | io.vtable.netClose(io.userdata, s.socket.handle); |
| 1001 | s.* = undefined; | 1014 | s.* = undefined; |
| 1002 | } | 1015 | } |
| 1003 | 1016 | ||
| ... | @@ -1040,10 +1053,13 @@ pub const Stream = struct { | ... | @@ -1040,10 +1053,13 @@ pub const Stream = struct { |
| 1040 | return n; | 1053 | return n; |
| 1041 | } | 1054 | } |
| 1042 | 1055 | ||
| 1043 | fn readVec(io_r: *Reader, data: [][]u8) Io.Reader.Error!usize { | 1056 | fn readVec(io_r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { |
| 1044 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); | 1057 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); |
| 1045 | const io = r.io; | 1058 | const io = r.io; |
| 1046 | return io.vtable.netReadVec(io.vtable.userdata, r.stream, io_r, data); | 1059 | return io.vtable.netRead(io.userdata, r.stream, data) catch |err| { |
| 1060 | r.err = err; | ||
| 1061 | return error.ReadFailed; | ||
| 1062 | }; | ||
| 1047 | } | 1063 | } |
| 1048 | }; | 1064 | }; |
| 1049 | 1065 | ||
| ... | @@ -1078,7 +1094,10 @@ pub const Stream = struct { | ... | @@ -1078,7 +1094,10 @@ pub const Stream = struct { |
| 1078 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); | 1094 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); |
| 1079 | const io = w.io; | 1095 | const io = w.io; |
| 1080 | const buffered = io_w.buffered(); | 1096 | const buffered = io_w.buffered(); |
| 1081 | const n = try io.vtable.netWrite(io.vtable.userdata, w.stream, buffered, data, splat); | 1097 | const n = io.vtable.netWrite(io.userdata, w.stream, buffered, data, splat) catch |err| { |
| 1098 | w.err = err; | ||
| 1099 | return error.WriteFailed; | ||
| 1100 | }; | ||
| 1082 | return io_w.consume(n); | 1101 | return io_w.consume(n); |
| 1083 | } | 1102 | } |
| 1084 | }; | 1103 | }; |
| ... | @@ -1104,7 +1123,7 @@ pub const Server = struct { | ... | @@ -1104,7 +1123,7 @@ pub const Server = struct { |
| 1104 | 1123 | ||
| 1105 | /// Blocks until a client connects to the server. | 1124 | /// Blocks until a client connects to the server. |
| 1106 | pub fn accept(s: *Server, io: Io) AcceptError!Stream { | 1125 | pub fn accept(s: *Server, io: Io) AcceptError!Stream { |
| 1107 | return io.vtable.accept(io, s); | 1126 | return io.vtable.accept(io.userdata, s); |
| 1108 | } | 1127 | } |
| 1109 | }; | 1128 | }; |
| 1110 | 1129 |
lib/std/Io/net/HostName.zig+51-13| ... | @@ -19,12 +19,12 @@ bytes: []const u8, | ... | @@ -19,12 +19,12 @@ bytes: []const u8, |
| 19 | 19 | ||
| 20 | pub const max_len = 255; | 20 | pub const max_len = 255; |
| 21 | 21 | ||
| 22 | pub const InitError = error{ | 22 | pub const ValidateError = error{ |
| 23 | NameTooLong, | 23 | NameTooLong, |
| 24 | InvalidHostName, | 24 | InvalidHostName, |
| 25 | }; | 25 | }; |
| 26 | 26 | ||
| 27 | pub fn init(bytes: []const u8) InitError!HostName { | 27 | pub fn validate(bytes: []const u8) ValidateError!void { |
| 28 | if (bytes.len > max_len) return error.NameTooLong; | 28 | if (bytes.len > max_len) return error.NameTooLong; |
| 29 | if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName; | 29 | if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName; |
| 30 | for (bytes) |byte| { | 30 | for (bytes) |byte| { |
| ... | @@ -33,10 +33,34 @@ pub fn init(bytes: []const u8) InitError!HostName { | ... | @@ -33,10 +33,34 @@ pub fn init(bytes: []const u8) InitError!HostName { |
| 33 | } | 33 | } |
| 34 | return error.InvalidHostName; | 34 | return error.InvalidHostName; |
| 35 | } | 35 | } |
| 36 | } | ||
| 37 | |||
| 38 | pub fn init(bytes: []const u8) ValidateError!HostName { | ||
| 39 | try validate(bytes); | ||
| 36 | return .{ .bytes = bytes }; | 40 | return .{ .bytes = bytes }; |
| 37 | } | 41 | } |
| 38 | 42 | ||
| 39 | /// TODO add a retry field here | 43 | pub fn sameParentDomain(parent_host: HostName, child_host: HostName) bool { |
| 44 | const parent_bytes = parent_host.bytes; | ||
| 45 | const child_bytes = child_host.bytes; | ||
| 46 | if (!std.ascii.endsWithIgnoreCase(child_bytes, parent_bytes)) return false; | ||
| 47 | if (child_bytes.len == parent_bytes.len) return true; | ||
| 48 | if (parent_bytes.len > child_bytes.len) return false; | ||
| 49 | return child_bytes[child_bytes.len - parent_bytes.len - 1] == '.'; | ||
| 50 | } | ||
| 51 | |||
| 52 | test sameParentDomain { | ||
| 53 | try std.testing.expect(!sameParentDomain(try .init("foo.com"), try .init("bar.com"))); | ||
| 54 | try std.testing.expect(sameParentDomain(try .init("foo.com"), try .init("foo.com"))); | ||
| 55 | try std.testing.expect(sameParentDomain(try .init("foo.com"), try .init("bar.foo.com"))); | ||
| 56 | try std.testing.expect(!sameParentDomain(try .init("bar.foo.com"), try .init("foo.com"))); | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Domain names are case-insensitive (RFC 5890, Section 2.3.2.4) | ||
| 60 | pub fn eql(a: HostName, b: HostName) bool { | ||
| 61 | return std.ascii.eqlIgnoreCase(a.bytes, b.bytes); | ||
| 62 | } | ||
| 63 | |||
| 40 | pub const LookupOptions = struct { | 64 | pub const LookupOptions = struct { |
| 41 | port: u16, | 65 | port: u16, |
| 42 | /// Must have at least length 2. | 66 | /// Must have at least length 2. |
| ... | @@ -266,15 +290,15 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio | ... | @@ -266,15 +290,15 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio |
| 266 | var answers_remaining = answers.len; | 290 | var answers_remaining = answers.len; |
| 267 | for (answers) |*answer| answer.len = 0; | 291 | for (answers) |*answer| answer.len = 0; |
| 268 | 292 | ||
| 269 | // boottime is chosen because time the computer is suspended should count | 293 | // boot clock is chosen because time the computer is suspended should count |
| 270 | // against time spent waiting for external messages to arrive. | 294 | // against time spent waiting for external messages to arrive. |
| 271 | var now_ts = try Io.Timestamp.now(io, .boottime); | 295 | var now_ts = try Io.Timestamp.now(io, .boot); |
| 272 | const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); | 296 | const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); |
| 273 | const attempt_duration: Io.Duration = .{ | 297 | const attempt_duration: Io.Duration = .{ |
| 274 | .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts, | 298 | .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts, |
| 275 | }; | 299 | }; |
| 276 | 300 | ||
| 277 | send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try Io.Timestamp.now(io, .boottime)) { | 301 | send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try Io.Timestamp.now(io, .boot)) { |
| 278 | const max_messages = queries_buffer.len * ResolvConf.max_nameservers; | 302 | const max_messages = queries_buffer.len * ResolvConf.max_nameservers; |
| 279 | { | 303 | { |
| 280 | var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined; | 304 | var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined; |
| ... | @@ -518,7 +542,7 @@ fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u | ... | @@ -518,7 +542,7 @@ fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u |
| 518 | return n; | 542 | return n; |
| 519 | } | 543 | } |
| 520 | 544 | ||
| 521 | pub const ExpandError = error{InvalidDnsPacket} || InitError; | 545 | pub const ExpandError = error{InvalidDnsPacket} || ValidateError; |
| 522 | 546 | ||
| 523 | /// Decompresses a DNS name. | 547 | /// Decompresses a DNS name. |
| 524 | /// | 548 | /// |
| ... | @@ -618,22 +642,36 @@ pub const DnsResponse = struct { | ... | @@ -618,22 +642,36 @@ pub const DnsResponse = struct { |
| 618 | } | 642 | } |
| 619 | }; | 643 | }; |
| 620 | 644 | ||
| 621 | pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError; | 645 | pub const ConnectError = LookupError || IpAddress.ConnectError; |
| 622 | 646 | ||
| 623 | pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream { | 647 | pub fn connect( |
| 648 | host_name: HostName, | ||
| 649 | io: Io, | ||
| 650 | port: u16, | ||
| 651 | options: IpAddress.ConnectOptions, | ||
| 652 | ) ConnectError!Stream { | ||
| 624 | var addresses_buffer: [32]IpAddress = undefined; | 653 | var addresses_buffer: [32]IpAddress = undefined; |
| 654 | var canonical_name_buffer: [HostName.max_len]u8 = undefined; | ||
| 625 | 655 | ||
| 626 | const results = try lookup(host_name, .{ | 656 | const results = try lookup(host_name, io, .{ |
| 627 | .port = port, | 657 | .port = port, |
| 628 | .addresses_buffer = &addresses_buffer, | 658 | .addresses_buffer = &addresses_buffer, |
| 629 | .canonical_name_buffer = &.{}, | 659 | .canonical_name_buffer = &canonical_name_buffer, |
| 630 | }); | 660 | }); |
| 631 | const addresses = addresses_buffer[0..results.addresses_len]; | 661 | const addresses = addresses_buffer[0..results.addresses_len]; |
| 632 | 662 | ||
| 633 | if (addresses.len == 0) return error.UnknownHostName; | 663 | if (addresses.len == 0) return error.UnknownHostName; |
| 634 | 664 | ||
| 635 | for (addresses) |addr| { | 665 | // TODO instead of serially, use a Select API to send out |
| 636 | return addr.connectTcp(io) catch |err| switch (err) { | 666 | // the connections simultaneously and then keep the first |
| 667 | // successful one, canceling the rest. | ||
| 668 | |||
| 669 | // TODO On Linux this should additionally use an Io.Queue based | ||
| 670 | // DNS resolution API in order to send out a connection after | ||
| 671 | // each DNS response before waiting for the rest of them. | ||
| 672 | |||
| 673 | for (addresses) |*addr| { | ||
| 674 | return addr.connect(io, options) catch |err| switch (err) { | ||
| 637 | error.ConnectionRefused => continue, | 675 | error.ConnectionRefused => continue, |
| 638 | else => |e| return e, | 676 | else => |e| return e, |
| 639 | }; | 677 | }; |
lib/std/Io/net/test.zig+91-86| ... | @@ -7,32 +7,30 @@ const testing = std.testing; | ... | @@ -7,32 +7,30 @@ const testing = std.testing; |
| 7 | test "parse and render IP addresses at comptime" { | 7 | test "parse and render IP addresses at comptime" { |
| 8 | comptime { | 8 | comptime { |
| 9 | const ipv6addr = net.IpAddress.parse("::1", 0) catch unreachable; | 9 | const ipv6addr = net.IpAddress.parse("::1", 0) catch unreachable; |
| 10 | try std.testing.expectFmt("[::1]:0", "{f}", .{ipv6addr}); | 10 | try testing.expectFmt("[::1]:0", "{f}", .{ipv6addr}); |
| 11 | 11 | ||
| 12 | const ipv4addr = net.IpAddress.parse("127.0.0.1", 0) catch unreachable; | 12 | const ipv4addr = net.IpAddress.parse("127.0.0.1", 0) catch unreachable; |
| 13 | try std.testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr}); | 13 | try testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr}); |
| 14 | 14 | ||
| 15 | try testing.expectError(error.ParseFailed, net.IpAddress.parse("::123.123.123.123", 0)); | 15 | try testing.expectError(error.ParseFailed, net.IpAddress.parse("::123.123.123.123", 0)); |
| 16 | try testing.expectError(error.ParseFailed, net.IpAddress.parse("127.01.0.1", 0)); | 16 | try testing.expectError(error.ParseFailed, net.IpAddress.parse("127.01.0.1", 0)); |
| 17 | try testing.expectError(error.ParseFailed, net.IpAddress.resolveIp("::123.123.123.123", 0)); | ||
| 18 | try testing.expectError(error.ParseFailed, net.IpAddress.resolveIp("127.01.0.1", 0)); | ||
| 19 | } | 17 | } |
| 20 | } | 18 | } |
| 21 | 19 | ||
| 22 | test "format IPv6 address with no zero runs" { | 20 | test "format IPv6 address with no zero runs" { |
| 23 | const addr = try std.net.IpAddress.parseIp6("2001:db8:1:2:3:4:5:6", 0); | 21 | const addr = try net.IpAddress.parseIp6("2001:db8:1:2:3:4:5:6", 0); |
| 24 | try std.testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr}); | 22 | try testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr}); |
| 25 | } | 23 | } |
| 26 | 24 | ||
| 27 | test "parse IPv6 addresses and check compressed form" { | 25 | test "parse IPv6 addresses and check compressed form" { |
| 28 | try std.testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{ | 26 | try testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{ |
| 29 | try std.net.IpAddress.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0), | 27 | try net.IpAddress.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0), |
| 30 | }); | 28 | }); |
| 31 | try std.testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{ | 29 | try testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{ |
| 32 | try std.net.IpAddress.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0), | 30 | try net.IpAddress.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0), |
| 33 | }); | 31 | }); |
| 34 | try std.testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{ | 32 | try testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{ |
| 35 | try std.net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0), | 33 | try net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0), |
| 36 | }); | 34 | }); |
| 37 | } | 35 | } |
| 38 | 36 | ||
| ... | @@ -43,14 +41,14 @@ test "parse IPv6 address, check raw bytes" { | ... | @@ -43,14 +41,14 @@ test "parse IPv6 address, check raw bytes" { |
| 43 | 0x00, 0x01, 0x00, 0x00, // :0001:0000 | 41 | 0x00, 0x01, 0x00, 0x00, // :0001:0000 |
| 44 | 0x00, 0x00, 0x00, 0x02, // :0000:0002 | 42 | 0x00, 0x00, 0x00, 0x02, // :0000:0002 |
| 45 | }; | 43 | }; |
| 46 | 44 | const addr = try net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0); | |
| 47 | const addr = try std.net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0); | 45 | try testing.expectEqualSlices(u8, &expected_raw, &addr.ip6.bytes); |
| 48 | |||
| 49 | const actual_raw = addr.in6.sa.addr[0..]; | ||
| 50 | try std.testing.expectEqualSlices(u8, expected_raw[0..], actual_raw); | ||
| 51 | } | 46 | } |
| 52 | 47 | ||
| 53 | test "parse and render IPv6 addresses" { | 48 | test "parse and render IPv6 addresses" { |
| 49 | // TODO make this test parsing and rendering only, then it doesn't need I/O | ||
| 50 | const io = testing.io; | ||
| 51 | |||
| 54 | var buffer: [100]u8 = undefined; | 52 | var buffer: [100]u8 = undefined; |
| 55 | const ips = [_][]const u8{ | 53 | const ips = [_][]const u8{ |
| 56 | "FF01:0:0:0:0:0:0:FB", | 54 | "FF01:0:0:0:0:0:0:FB", |
| ... | @@ -79,12 +77,12 @@ test "parse and render IPv6 addresses" { | ... | @@ -79,12 +77,12 @@ test "parse and render IPv6 addresses" { |
| 79 | for (ips, 0..) |ip, i| { | 77 | for (ips, 0..) |ip, i| { |
| 80 | const addr = net.IpAddress.parseIp6(ip, 0) catch unreachable; | 78 | const addr = net.IpAddress.parseIp6(ip, 0) catch unreachable; |
| 81 | var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable; | 79 | var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable; |
| 82 | try std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3])); | 80 | try testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3])); |
| 83 | 81 | ||
| 84 | if (builtin.os.tag == .linux) { | 82 | if (builtin.os.tag == .linux) { |
| 85 | const addr_via_resolve = net.IpAddress.resolveIp6(ip, 0) catch unreachable; | 83 | const addr_via_resolve = net.IpAddress.resolveIp6(io, ip, 0) catch unreachable; |
| 86 | var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr_via_resolve}) catch unreachable; | 84 | var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr_via_resolve}) catch unreachable; |
| 87 | try std.testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3])); | 85 | try testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3])); |
| 88 | } | 86 | } |
| 89 | } | 87 | } |
| 90 | 88 | ||
| ... | @@ -97,21 +95,23 @@ test "parse and render IPv6 addresses" { | ... | @@ -97,21 +95,23 @@ test "parse and render IPv6 addresses" { |
| 97 | try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("1", 0)); | 95 | try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("1", 0)); |
| 98 | // TODO Make this test pass on other operating systems. | 96 | // TODO Make this test pass on other operating systems. |
| 99 | if (builtin.os.tag == .linux or comptime builtin.os.tag.isDarwin() or builtin.os.tag == .windows) { | 97 | if (builtin.os.tag == .linux or comptime builtin.os.tag.isDarwin() or builtin.os.tag == .windows) { |
| 100 | try testing.expectError(error.Incomplete, net.IpAddress.resolveIp6("ff01::fb%", 0)); | 98 | try testing.expectError(error.Incomplete, net.IpAddress.resolveIp6(io, "ff01::fb%", 0)); |
| 101 | // Assumes IFNAMESIZE will always be a multiple of 2 | 99 | // Assumes IFNAMESIZE will always be a multiple of 2 |
| 102 | try testing.expectError(error.Overflow, net.IpAddress.resolveIp6("ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0)); | 100 | try testing.expectError(error.Overflow, net.IpAddress.resolveIp6(io, "ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0)); |
| 103 | try testing.expectError(error.Overflow, net.IpAddress.resolveIp6("ff01::fb%12345678901234", 0)); | 101 | try testing.expectError(error.Overflow, net.IpAddress.resolveIp6(io, "ff01::fb%12345678901234", 0)); |
| 104 | } | 102 | } |
| 105 | } | 103 | } |
| 106 | 104 | ||
| 107 | test "invalid but parseable IPv6 scope ids" { | 105 | test "invalid but parseable IPv6 scope ids" { |
| 106 | const io = testing.io; | ||
| 107 | |||
| 108 | if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) { | 108 | if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) { |
| 109 | // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux. | 109 | // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux. |
| 110 | // TODO Make this test pass on other operating systems. | 110 | // TODO Make this test pass on other operating systems. |
| 111 | return error.SkipZigTest; | 111 | return error.SkipZigTest; |
| 112 | } | 112 | } |
| 113 | 113 | ||
| 114 | try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6("ff01::fb%123s45678901234", 0)); | 114 | try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6(io, "ff01::fb%123s45678901234", 0)); |
| 115 | } | 115 | } |
| 116 | 116 | ||
| 117 | test "parse and render IPv4 addresses" { | 117 | test "parse and render IPv4 addresses" { |
| ... | @@ -125,7 +125,7 @@ test "parse and render IPv4 addresses" { | ... | @@ -125,7 +125,7 @@ test "parse and render IPv4 addresses" { |
| 125 | }) |ip| { | 125 | }) |ip| { |
| 126 | const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable; | 126 | const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable; |
| 127 | var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable; | 127 | var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable; |
| 128 | try std.testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2])); | 128 | try testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2])); |
| 129 | } | 129 | } |
| 130 | 130 | ||
| 131 | try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0)); | 131 | try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0)); |
| ... | @@ -136,50 +136,43 @@ test "parse and render IPv4 addresses" { | ... | @@ -136,50 +136,43 @@ test "parse and render IPv4 addresses" { |
| 136 | try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0)); | 136 | try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0)); |
| 137 | } | 137 | } |
| 138 | 138 | ||
| 139 | test "parse and render UNIX addresses" { | ||
| 140 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
| 141 | if (!net.has_unix_sockets) return error.SkipZigTest; | ||
| 142 | |||
| 143 | const addr = net.Address.initUnix("/tmp/testpath") catch unreachable; | ||
| 144 | try std.testing.expectFmt("/tmp/testpath", "{f}", .{addr}); | ||
| 145 | |||
| 146 | const too_long = [_]u8{'a'} ** 200; | ||
| 147 | try testing.expectError(error.NameTooLong, net.Address.initUnix(too_long[0..])); | ||
| 148 | } | ||
| 149 | |||
| 150 | test "resolve DNS" { | 139 | test "resolve DNS" { |
| 151 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 140 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 152 | 141 | ||
| 153 | if (builtin.os.tag == .windows) { | 142 | const io = testing.io; |
| 154 | _ = try std.os.windows.WSAStartup(2, 2); | ||
| 155 | } | ||
| 156 | defer { | ||
| 157 | if (builtin.os.tag == .windows) { | ||
| 158 | std.os.windows.WSACleanup() catch unreachable; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | 143 | ||
| 162 | // Resolve localhost, this should not fail. | 144 | // Resolve localhost, this should not fail. |
| 163 | { | 145 | { |
| 164 | const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80); | 146 | const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80); |
| 165 | const localhost_v6 = try net.IpAddress.parse("::2", 80); | 147 | const localhost_v6 = try net.IpAddress.parse("::2", 80); |
| 166 | 148 | ||
| 167 | const result = try net.getAddressList(testing.allocator, "localhost", 80); | 149 | var addresses_buffer: [8]net.IpAddress = undefined; |
| 168 | defer result.deinit(); | 150 | var canon_name_buffer: [net.HostName.max_len]u8 = undefined; |
| 169 | for (result.addrs) |addr| { | 151 | const result = try net.HostName.lookup(try .init("localhost"), io, .{ |
| 170 | if (addr.eql(localhost_v4) or addr.eql(localhost_v6)) break; | 152 | .port = 80, |
| 153 | .addresses_buffer = &addresses_buffer, | ||
| 154 | .canonical_name_buffer = &canon_name_buffer, | ||
| 155 | }); | ||
| 156 | for (addresses_buffer[0..result.addresses_len]) |addr| { | ||
| 157 | if (addr.eql(&localhost_v4) or addr.eql(&localhost_v6)) break; | ||
| 171 | } else @panic("unexpected address for localhost"); | 158 | } else @panic("unexpected address for localhost"); |
| 172 | } | 159 | } |
| 173 | 160 | ||
| 174 | { | 161 | { |
| 175 | // The tests are required to work even when there is no Internet connection, | 162 | // The tests are required to work even when there is no Internet connection, |
| 176 | // so some of these errors we must accept and skip the test. | 163 | // so some of these errors we must accept and skip the test. |
| 177 | const result = net.getAddressList(testing.allocator, "example.com", 80) catch |err| switch (err) { | 164 | var addresses_buffer: [8]net.IpAddress = undefined; |
| 165 | var canon_name_buffer: [net.HostName.max_len]u8 = undefined; | ||
| 166 | const result = net.HostName.lookup(try .init("example.com"), io, .{ | ||
| 167 | .port = 80, | ||
| 168 | .addresses_buffer = &addresses_buffer, | ||
| 169 | .canonical_name_buffer = &canon_name_buffer, | ||
| 170 | }) catch |err| switch (err) { | ||
| 178 | error.UnknownHostName => return error.SkipZigTest, | 171 | error.UnknownHostName => return error.SkipZigTest, |
| 179 | error.TemporaryNameServerFailure => return error.SkipZigTest, | 172 | error.NameServerFailure => return error.SkipZigTest, |
| 180 | else => return err, | 173 | else => return err, |
| 181 | }; | 174 | }; |
| 182 | result.deinit(); | 175 | _ = result; |
| 183 | } | 176 | } |
| 184 | } | 177 | } |
| 185 | 178 | ||
| ... | @@ -187,6 +180,8 @@ test "listen on a port, send bytes, receive bytes" { | ... | @@ -187,6 +180,8 @@ test "listen on a port, send bytes, receive bytes" { |
| 187 | if (builtin.single_threaded) return error.SkipZigTest; | 180 | if (builtin.single_threaded) return error.SkipZigTest; |
| 188 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 181 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 189 | 182 | ||
| 183 | const io = testing.io; | ||
| 184 | |||
| 190 | if (builtin.os.tag == .windows) { | 185 | if (builtin.os.tag == .windows) { |
| 191 | _ = try std.os.windows.WSAStartup(2, 2); | 186 | _ = try std.os.windows.WSAStartup(2, 2); |
| 192 | } | 187 | } |
| ... | @@ -198,28 +193,28 @@ test "listen on a port, send bytes, receive bytes" { | ... | @@ -198,28 +193,28 @@ test "listen on a port, send bytes, receive bytes" { |
| 198 | 193 | ||
| 199 | // Try only the IPv4 variant as some CI builders have no IPv6 localhost | 194 | // Try only the IPv4 variant as some CI builders have no IPv6 localhost |
| 200 | // configured. | 195 | // configured. |
| 201 | const localhost = try net.IpAddress.parse("127.0.0.1", 0); | 196 | const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; |
| 202 | 197 | ||
| 203 | var server = try localhost.listen(.{}); | 198 | var server = try localhost.listen(io, .{}); |
| 204 | defer server.deinit(); | 199 | defer server.deinit(io); |
| 205 | 200 | ||
| 206 | const S = struct { | 201 | const S = struct { |
| 207 | fn clientFn(server_address: net.IpAddress) !void { | 202 | fn clientFn(server_address: net.IpAddress) !void { |
| 208 | const socket = try net.tcpConnectToAddress(server_address); | 203 | var stream = try server_address.connect(io, .{ .mode = .stream }); |
| 209 | defer socket.close(); | 204 | defer stream.close(io); |
| 210 | 205 | ||
| 211 | var stream_writer = socket.writer(&.{}); | 206 | var stream_writer = stream.writer(io, &.{}); |
| 212 | try stream_writer.interface.writeAll("Hello world!"); | 207 | try stream_writer.interface.writeAll("Hello world!"); |
| 213 | } | 208 | } |
| 214 | }; | 209 | }; |
| 215 | 210 | ||
| 216 | const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address}); | 211 | const t = try std.Thread.spawn(.{}, S.clientFn, .{server.socket.address}); |
| 217 | defer t.join(); | 212 | defer t.join(); |
| 218 | 213 | ||
| 219 | var client = try server.accept(); | 214 | var client = try server.accept(io); |
| 220 | defer client.stream.close(); | 215 | defer client.stream.close(io); |
| 221 | var buf: [16]u8 = undefined; | 216 | var buf: [16]u8 = undefined; |
| 222 | var stream_reader = client.stream.reader(&.{}); | 217 | var stream_reader = client.stream.reader(io, &.{}); |
| 223 | const n = try stream_reader.interface().readSliceShort(&buf); | 218 | const n = try stream_reader.interface().readSliceShort(&buf); |
| 224 | 219 | ||
| 225 | try testing.expectEqual(@as(usize, 12), n); | 220 | try testing.expectEqual(@as(usize, 12), n); |
| ... | @@ -232,13 +227,15 @@ test "listen on an in use port" { | ... | @@ -232,13 +227,15 @@ test "listen on an in use port" { |
| 232 | return error.SkipZigTest; | 227 | return error.SkipZigTest; |
| 233 | } | 228 | } |
| 234 | 229 | ||
| 235 | const localhost = try net.IpAddress.parse("127.0.0.1", 0); | 230 | const io = testing.io; |
| 231 | |||
| 232 | const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; | ||
| 236 | 233 | ||
| 237 | var server1 = try localhost.listen(.{ .reuse_address = true }); | 234 | var server1 = try localhost.listen(io, .{ .reuse_address = true }); |
| 238 | defer server1.deinit(); | 235 | defer server1.deinit(io); |
| 239 | 236 | ||
| 240 | var server2 = try server1.listen_address.listen(.{ .reuse_address = true }); | 237 | var server2 = try server1.socket.address.listen(io, .{ .reuse_address = true }); |
| 241 | defer server2.deinit(); | 238 | defer server2.deinit(io); |
| 242 | } | 239 | } |
| 243 | 240 | ||
| 244 | fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void { | 241 | fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void { |
| ... | @@ -268,9 +265,11 @@ fn testClient(addr: net.IpAddress) anyerror!void { | ... | @@ -268,9 +265,11 @@ fn testClient(addr: net.IpAddress) anyerror!void { |
| 268 | fn testServer(server: *net.Server) anyerror!void { | 265 | fn testServer(server: *net.Server) anyerror!void { |
| 269 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 266 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 270 | 267 | ||
| 271 | var client = try server.accept(); | 268 | const io = testing.io; |
| 269 | |||
| 270 | var client = try server.accept(io); | ||
| 272 | 271 | ||
| 273 | const stream = client.stream.writer(); | 272 | const stream = client.stream.writer(io); |
| 274 | try stream.print("hello from server\n", .{}); | 273 | try stream.print("hello from server\n", .{}); |
| 275 | } | 274 | } |
| 276 | 275 | ||
| ... | @@ -278,6 +277,8 @@ test "listen on a unix socket, send bytes, receive bytes" { | ... | @@ -278,6 +277,8 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 278 | if (builtin.single_threaded) return error.SkipZigTest; | 277 | if (builtin.single_threaded) return error.SkipZigTest; |
| 279 | if (!net.has_unix_sockets) return error.SkipZigTest; | 278 | if (!net.has_unix_sockets) return error.SkipZigTest; |
| 280 | 279 | ||
| 280 | const io = testing.io; | ||
| 281 | |||
| 281 | if (builtin.os.tag == .windows) { | 282 | if (builtin.os.tag == .windows) { |
| 282 | _ = try std.os.windows.WSAStartup(2, 2); | 283 | _ = try std.os.windows.WSAStartup(2, 2); |
| 283 | } | 284 | } |
| ... | @@ -293,15 +294,15 @@ test "listen on a unix socket, send bytes, receive bytes" { | ... | @@ -293,15 +294,15 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 293 | const socket_addr = try net.IpAddress.initUnix(socket_path); | 294 | const socket_addr = try net.IpAddress.initUnix(socket_path); |
| 294 | defer std.fs.cwd().deleteFile(socket_path) catch {}; | 295 | defer std.fs.cwd().deleteFile(socket_path) catch {}; |
| 295 | 296 | ||
| 296 | var server = try socket_addr.listen(.{}); | 297 | var server = try socket_addr.listen(io, .{}); |
| 297 | defer server.deinit(); | 298 | defer server.deinit(io); |
| 298 | 299 | ||
| 299 | const S = struct { | 300 | const S = struct { |
| 300 | fn clientFn(path: []const u8) !void { | 301 | fn clientFn(path: []const u8) !void { |
| 301 | const socket = try net.connectUnixSocket(path); | 302 | var stream = try net.connectUnixSocket(path); |
| 302 | defer socket.close(); | 303 | defer stream.close(io); |
| 303 | 304 | ||
| 304 | var stream_writer = socket.writer(&.{}); | 305 | var stream_writer = stream.writer(io, &.{}); |
| 305 | try stream_writer.interface.writeAll("Hello world!"); | 306 | try stream_writer.interface.writeAll("Hello world!"); |
| 306 | } | 307 | } |
| 307 | }; | 308 | }; |
| ... | @@ -309,10 +310,10 @@ test "listen on a unix socket, send bytes, receive bytes" { | ... | @@ -309,10 +310,10 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 309 | const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path}); | 310 | const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path}); |
| 310 | defer t.join(); | 311 | defer t.join(); |
| 311 | 312 | ||
| 312 | var client = try server.accept(); | 313 | var client = try server.accept(io); |
| 313 | defer client.stream.close(); | 314 | defer client.stream.close(io); |
| 314 | var buf: [16]u8 = undefined; | 315 | var buf: [16]u8 = undefined; |
| 315 | var stream_reader = client.stream.reader(&.{}); | 316 | var stream_reader = client.stream.reader(io, &.{}); |
| 316 | const n = try stream_reader.interface().readSliceShort(&buf); | 317 | const n = try stream_reader.interface().readSliceShort(&buf); |
| 317 | 318 | ||
| 318 | try testing.expectEqual(@as(usize, 12), n); | 319 | try testing.expectEqual(@as(usize, 12), n); |
| ... | @@ -324,14 +325,16 @@ test "listen on a unix socket with reuse_address option" { | ... | @@ -324,14 +325,16 @@ test "listen on a unix socket with reuse_address option" { |
| 324 | // Windows doesn't implement reuse port option. | 325 | // Windows doesn't implement reuse port option. |
| 325 | if (builtin.os.tag == .windows) return error.SkipZigTest; | 326 | if (builtin.os.tag == .windows) return error.SkipZigTest; |
| 326 | 327 | ||
| 328 | const io = testing.io; | ||
| 329 | |||
| 327 | const socket_path = try generateFileName("socket.unix"); | 330 | const socket_path = try generateFileName("socket.unix"); |
| 328 | defer testing.allocator.free(socket_path); | 331 | defer testing.allocator.free(socket_path); |
| 329 | 332 | ||
| 330 | const socket_addr = try net.Address.initUnix(socket_path); | 333 | const socket_addr = try net.Address.initUnix(socket_path); |
| 331 | defer std.fs.cwd().deleteFile(socket_path) catch {}; | 334 | defer std.fs.cwd().deleteFile(socket_path) catch {}; |
| 332 | 335 | ||
| 333 | var server = try socket_addr.listen(.{ .reuse_address = true }); | 336 | var server = try socket_addr.listen(io, .{ .reuse_address = true }); |
| 334 | server.deinit(); | 337 | server.deinit(io); |
| 335 | } | 338 | } |
| 336 | 339 | ||
| 337 | fn generateFileName(base_name: []const u8) ![]const u8 { | 340 | fn generateFileName(base_name: []const u8) ![]const u8 { |
| ... | @@ -351,19 +354,21 @@ test "non-blocking tcp server" { | ... | @@ -351,19 +354,21 @@ test "non-blocking tcp server" { |
| 351 | return error.SkipZigTest; | 354 | return error.SkipZigTest; |
| 352 | } | 355 | } |
| 353 | 356 | ||
| 354 | const localhost = try net.IpAddress.parse("127.0.0.1", 0); | 357 | const io = testing.io; |
| 355 | var server = localhost.listen(.{ .force_nonblocking = true }); | 358 | |
| 356 | defer server.deinit(); | 359 | const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; |
| 360 | var server = localhost.listen(io, .{ .force_nonblocking = true }); | ||
| 361 | defer server.deinit(io); | ||
| 357 | 362 | ||
| 358 | const accept_err = server.accept(); | 363 | const accept_err = server.accept(io); |
| 359 | try testing.expectError(error.WouldBlock, accept_err); | 364 | try testing.expectError(error.WouldBlock, accept_err); |
| 360 | 365 | ||
| 361 | const socket_file = try net.tcpConnectToAddress(server.listen_address); | 366 | const socket_file = try net.tcpConnectToAddress(server.socket.address); |
| 362 | defer socket_file.close(); | 367 | defer socket_file.close(); |
| 363 | 368 | ||
| 364 | var client = try server.accept(); | 369 | var client = try server.accept(io); |
| 365 | defer client.stream.close(); | 370 | defer client.stream.close(io); |
| 366 | const stream = client.stream.writer(); | 371 | const stream = client.stream.writer(io); |
| 367 | try stream.print("hello from server\n", .{}); | 372 | try stream.print("hello from server\n", .{}); |
| 368 | 373 | ||
| 369 | var buf: [100]u8 = undefined; | 374 | var buf: [100]u8 = undefined; |
lib/std/Uri.zig+23-15| ... | @@ -1,45 +1,48 @@ | ... | @@ -1,45 +1,48 @@ |
| 1 | //! Uniform Resource Identifier (URI) parsing roughly adhering to <https://tools.ietf.org/html/rfc3986>. | 1 | //! Uniform Resource Identifier (URI) parsing roughly adhering to |
| 2 | //! Does not do perfect grammar and character class checking, but should be robust against URIs in the wild. | 2 | //! <https://tools.ietf.org/html/rfc3986>. Does not do perfect grammar and |
| 3 | //! character class checking, but should be robust against URIs in the wild. | ||
| 3 | 4 | ||
| 4 | const std = @import("std.zig"); | 5 | const std = @import("std.zig"); |
| 5 | const testing = std.testing; | 6 | const testing = std.testing; |
| 6 | const Uri = @This(); | 7 | const Uri = @This(); |
| 7 | const Allocator = std.mem.Allocator; | 8 | const Allocator = std.mem.Allocator; |
| 8 | const Writer = std.Io.Writer; | 9 | const Writer = std.Io.Writer; |
| 10 | const HostName = std.Io.net.HostName; | ||
| 9 | 11 | ||
| 10 | scheme: []const u8, | 12 | scheme: []const u8, |
| 11 | user: ?Component = null, | 13 | user: ?Component = null, |
| 12 | password: ?Component = null, | 14 | password: ?Component = null, |
| 15 | /// If non-null, already validated. | ||
| 13 | host: ?Component = null, | 16 | host: ?Component = null, |
| 14 | port: ?u16 = null, | 17 | port: ?u16 = null, |
| 15 | path: Component = Component.empty, | 18 | path: Component = Component.empty, |
| 16 | query: ?Component = null, | 19 | query: ?Component = null, |
| 17 | fragment: ?Component = null, | 20 | fragment: ?Component = null, |
| 18 | 21 | ||
| 19 | pub const host_name_max = 255; | 22 | pub const GetHostError = error{UriMissingHost}; |
| 20 | 23 | ||
| 21 | /// Returned value may point into `buffer` or be the original string. | 24 | /// Returned value may point into `buffer` or be the original string. |
| 22 | /// | 25 | /// |
| 23 | /// Suggested buffer length: `host_name_max`. | ||
| 24 | /// | ||
| 25 | /// See also: | 26 | /// See also: |
| 26 | /// * `getHostAlloc` | 27 | /// * `getHostAlloc` |
| 27 | pub fn getHost(uri: Uri, buffer: []u8) error{ UriMissingHost, UriHostTooLong }![]const u8 { | 28 | pub fn getHost(uri: Uri, buffer: *[HostName.max_len]u8) GetHostError!HostName { |
| 28 | const component = uri.host orelse return error.UriMissingHost; | 29 | const component = uri.host orelse return error.UriMissingHost; |
| 29 | return component.toRaw(buffer) catch |err| switch (err) { | 30 | const bytes = component.toRaw(buffer) catch |err| switch (err) { |
| 30 | error.NoSpaceLeft => return error.UriHostTooLong, | 31 | error.NoSpaceLeft => unreachable, // `host` already validated. |
| 31 | }; | 32 | }; |
| 33 | return .{ .bytes = bytes }; | ||
| 32 | } | 34 | } |
| 33 | 35 | ||
| 36 | pub const GetHostAllocError = GetHostError || error{OutOfMemory}; | ||
| 37 | |||
| 34 | /// Returned value may point into `buffer` or be the original string. | 38 | /// Returned value may point into `buffer` or be the original string. |
| 35 | /// | 39 | /// |
| 36 | /// See also: | 40 | /// See also: |
| 37 | /// * `getHost` | 41 | /// * `getHost` |
| 38 | pub fn getHostAlloc(uri: Uri, arena: Allocator) error{ UriMissingHost, UriHostTooLong, OutOfMemory }![]const u8 { | 42 | pub fn getHostAlloc(uri: Uri, arena: Allocator) GetHostAllocError![]const u8 { |
| 39 | const component = uri.host orelse return error.UriMissingHost; | 43 | const component = uri.host orelse return error.UriMissingHost; |
| 40 | const result = try component.toRawMaybeAlloc(arena); | 44 | const bytes = try component.toRawMaybeAlloc(arena); |
| 41 | if (result.len > host_name_max) return error.UriHostTooLong; | 45 | return .{ .bytes = bytes }; |
| 42 | return result; | ||
| 43 | } | 46 | } |
| 44 | 47 | ||
| 45 | pub const Component = union(enum) { | 48 | pub const Component = union(enum) { |
| ... | @@ -397,7 +400,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE | ... | @@ -397,7 +400,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 397 | .scheme = new_parsed.scheme, | 400 | .scheme = new_parsed.scheme, |
| 398 | .user = new_parsed.user, | 401 | .user = new_parsed.user, |
| 399 | .password = new_parsed.password, | 402 | .password = new_parsed.password, |
| 400 | .host = new_parsed.host, | 403 | .host = try validateHost(new_parsed.host), |
| 401 | .port = new_parsed.port, | 404 | .port = new_parsed.port, |
| 402 | .path = remove_dot_segments(new_path), | 405 | .path = remove_dot_segments(new_path), |
| 403 | .query = new_parsed.query, | 406 | .query = new_parsed.query, |
| ... | @@ -408,7 +411,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE | ... | @@ -408,7 +411,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 408 | .scheme = base.scheme, | 411 | .scheme = base.scheme, |
| 409 | .user = new_parsed.user, | 412 | .user = new_parsed.user, |
| 410 | .password = new_parsed.password, | 413 | .password = new_parsed.password, |
| 411 | .host = host, | 414 | .host = try validateHost(host), |
| 412 | .port = new_parsed.port, | 415 | .port = new_parsed.port, |
| 413 | .path = remove_dot_segments(new_path), | 416 | .path = remove_dot_segments(new_path), |
| 414 | .query = new_parsed.query, | 417 | .query = new_parsed.query, |
| ... | @@ -430,7 +433,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE | ... | @@ -430,7 +433,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 430 | .scheme = base.scheme, | 433 | .scheme = base.scheme, |
| 431 | .user = base.user, | 434 | .user = base.user, |
| 432 | .password = base.password, | 435 | .password = base.password, |
| 433 | .host = base.host, | 436 | .host = try validateHost(base.host), |
| 434 | .port = base.port, | 437 | .port = base.port, |
| 435 | .path = path, | 438 | .path = path, |
| 436 | .query = query, | 439 | .query = query, |
| ... | @@ -438,6 +441,11 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE | ... | @@ -438,6 +441,11 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 438 | }; | 441 | }; |
| 439 | } | 442 | } |
| 440 | 443 | ||
| 444 | fn validateHost(bytes: []const u8) []const u8 { | ||
| 445 | try HostName.validate(bytes); | ||
| 446 | return bytes; | ||
| 447 | } | ||
| 448 | |||
| 441 | /// In-place implementation of RFC 3986, Section 5.2.4. | 449 | /// In-place implementation of RFC 3986, Section 5.2.4. |
| 442 | fn remove_dot_segments(path: []u8) Component { | 450 | fn remove_dot_segments(path: []u8) Component { |
| 443 | var in_i: usize = 0; | 451 | var in_i: usize = 0; |
lib/std/fs/test.zig+1-1| ... | @@ -2281,7 +2281,7 @@ test "seekTo flushes buffered data" { | ... | @@ -2281,7 +2281,7 @@ test "seekTo flushes buffered data" { |
| 2281 | } | 2281 | } |
| 2282 | 2282 | ||
| 2283 | var read_buffer: [16]u8 = undefined; | 2283 | var read_buffer: [16]u8 = undefined; |
| 2284 | var file_reader: std.Io.File.Reader = .init(file, io, &read_buffer); | 2284 | var file_reader: std.Io.File.Reader = .initAdapted(file, io, &read_buffer); |
| 2285 | 2285 | ||
| 2286 | var buf: [4]u8 = undefined; | 2286 | var buf: [4]u8 = undefined; |
| 2287 | try file_reader.interface.readSliceAll(&buf); | 2287 | try file_reader.interface.readSliceAll(&buf); |
lib/std/http/Client.zig+55-69| ... | @@ -15,6 +15,7 @@ const assert = std.debug.assert; | ... | @@ -15,6 +15,7 @@ const assert = std.debug.assert; |
| 15 | const Io = std.Io; | 15 | const Io = std.Io; |
| 16 | const Writer = std.Io.Writer; | 16 | const Writer = std.Io.Writer; |
| 17 | const Reader = std.Io.Reader; | 17 | const Reader = std.Io.Reader; |
| 18 | const HostName = std.Io.net.HostName; | ||
| 18 | 19 | ||
| 19 | const Client = @This(); | 20 | const Client = @This(); |
| 20 | 21 | ||
| ... | @@ -69,7 +70,7 @@ pub const ConnectionPool = struct { | ... | @@ -69,7 +70,7 @@ pub const ConnectionPool = struct { |
| 69 | 70 | ||
| 70 | /// The criteria for a connection to be considered a match. | 71 | /// The criteria for a connection to be considered a match. |
| 71 | pub const Criteria = struct { | 72 | pub const Criteria = struct { |
| 72 | host: []const u8, | 73 | host: HostName, |
| 73 | port: u16, | 74 | port: u16, |
| 74 | protocol: Protocol, | 75 | protocol: Protocol, |
| 75 | }; | 76 | }; |
| ... | @@ -89,7 +90,7 @@ pub const ConnectionPool = struct { | ... | @@ -89,7 +90,7 @@ pub const ConnectionPool = struct { |
| 89 | if (connection.port != criteria.port) continue; | 90 | if (connection.port != criteria.port) continue; |
| 90 | 91 | ||
| 91 | // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4) | 92 | // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4) |
| 92 | if (!std.ascii.eqlIgnoreCase(connection.host(), criteria.host)) continue; | 93 | if (!connection.host().eql(criteria.host)) continue; |
| 93 | 94 | ||
| 94 | pool.acquireUnsafe(connection); | 95 | pool.acquireUnsafe(connection); |
| 95 | return connection; | 96 | return connection; |
| ... | @@ -118,19 +119,19 @@ pub const ConnectionPool = struct { | ... | @@ -118,19 +119,19 @@ pub const ConnectionPool = struct { |
| 118 | /// If the connection is marked as closing, it will be closed instead. | 119 | /// If the connection is marked as closing, it will be closed instead. |
| 119 | /// | 120 | /// |
| 120 | /// Threadsafe. | 121 | /// Threadsafe. |
| 121 | pub fn release(pool: *ConnectionPool, connection: *Connection) void { | 122 | pub fn release(pool: *ConnectionPool, connection: *Connection, io: Io) void { |
| 122 | pool.mutex.lock(); | 123 | pool.mutex.lock(); |
| 123 | defer pool.mutex.unlock(); | 124 | defer pool.mutex.unlock(); |
| 124 | 125 | ||
| 125 | pool.used.remove(&connection.pool_node); | 126 | pool.used.remove(&connection.pool_node); |
| 126 | 127 | ||
| 127 | if (connection.closing or pool.free_size == 0) return connection.destroy(); | 128 | if (connection.closing or pool.free_size == 0) return connection.destroy(io); |
| 128 | 129 | ||
| 129 | if (pool.free_len >= pool.free_size) { | 130 | if (pool.free_len >= pool.free_size) { |
| 130 | const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?)); | 131 | const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?)); |
| 131 | pool.free_len -= 1; | 132 | pool.free_len -= 1; |
| 132 | 133 | ||
| 133 | popped.destroy(); | 134 | popped.destroy(io); |
| 134 | } | 135 | } |
| 135 | 136 | ||
| 136 | if (connection.proxied) { | 137 | if (connection.proxied) { |
| ... | @@ -178,21 +179,21 @@ pub const ConnectionPool = struct { | ... | @@ -178,21 +179,21 @@ pub const ConnectionPool = struct { |
| 178 | /// All future operations on the connection pool will deadlock. | 179 | /// All future operations on the connection pool will deadlock. |
| 179 | /// | 180 | /// |
| 180 | /// Threadsafe. | 181 | /// Threadsafe. |
| 181 | pub fn deinit(pool: *ConnectionPool) void { | 182 | pub fn deinit(pool: *ConnectionPool, io: Io) void { |
| 182 | pool.mutex.lock(); | 183 | pool.mutex.lock(); |
| 183 | 184 | ||
| 184 | var next = pool.free.first; | 185 | var next = pool.free.first; |
| 185 | while (next) |node| { | 186 | while (next) |node| { |
| 186 | const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node)); | 187 | const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node)); |
| 187 | next = node.next; | 188 | next = node.next; |
| 188 | connection.destroy(); | 189 | connection.destroy(io); |
| 189 | } | 190 | } |
| 190 | 191 | ||
| 191 | next = pool.used.first; | 192 | next = pool.used.first; |
| 192 | while (next) |node| { | 193 | while (next) |node| { |
| 193 | const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node)); | 194 | const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node)); |
| 194 | next = node.next; | 195 | next = node.next; |
| 195 | connection.destroy(); | 196 | connection.destroy(io); |
| 196 | } | 197 | } |
| 197 | 198 | ||
| 198 | pool.* = undefined; | 199 | pool.* = undefined; |
| ... | @@ -242,19 +243,19 @@ pub const Connection = struct { | ... | @@ -242,19 +243,19 @@ pub const Connection = struct { |
| 242 | 243 | ||
| 243 | fn create( | 244 | fn create( |
| 244 | client: *Client, | 245 | client: *Client, |
| 245 | remote_host: []const u8, | 246 | remote_host: HostName, |
| 246 | port: u16, | 247 | port: u16, |
| 247 | stream: Io.net.Stream, | 248 | stream: Io.net.Stream, |
| 248 | ) error{OutOfMemory}!*Plain { | 249 | ) error{OutOfMemory}!*Plain { |
| 249 | const gpa = client.allocator; | 250 | const gpa = client.allocator; |
| 250 | const alloc_len = allocLen(client, remote_host.len); | 251 | const alloc_len = allocLen(client, remote_host.bytes.len); |
| 251 | const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len); | 252 | const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len); |
| 252 | errdefer gpa.free(base); | 253 | errdefer gpa.free(base); |
| 253 | const host_buffer = base[@sizeOf(Plain)..][0..remote_host.len]; | 254 | const host_buffer = base[@sizeOf(Plain)..][0..remote_host.bytes.len]; |
| 254 | const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size]; | 255 | const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size]; |
| 255 | const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size]; | 256 | const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size]; |
| 256 | assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len); | 257 | assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len); |
| 257 | @memcpy(host_buffer, remote_host); | 258 | @memcpy(host_buffer, remote_host.bytes); |
| 258 | const plain: *Plain = @ptrCast(base); | 259 | const plain: *Plain = @ptrCast(base); |
| 259 | plain.* = .{ | 260 | plain.* = .{ |
| 260 | .connection = .{ | 261 | .connection = .{ |
| ... | @@ -263,7 +264,7 @@ pub const Connection = struct { | ... | @@ -263,7 +264,7 @@ pub const Connection = struct { |
| 263 | .stream_reader = stream.reader(socket_read_buffer), | 264 | .stream_reader = stream.reader(socket_read_buffer), |
| 264 | .pool_node = .{}, | 265 | .pool_node = .{}, |
| 265 | .port = port, | 266 | .port = port, |
| 266 | .host_len = @intCast(remote_host.len), | 267 | .host_len = @intCast(remote_host.bytes.len), |
| 267 | .proxied = false, | 268 | .proxied = false, |
| 268 | .closing = false, | 269 | .closing = false, |
| 269 | .protocol = .plain, | 270 | .protocol = .plain, |
| ... | @@ -283,9 +284,9 @@ pub const Connection = struct { | ... | @@ -283,9 +284,9 @@ pub const Connection = struct { |
| 283 | return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size; | 284 | return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size; |
| 284 | } | 285 | } |
| 285 | 286 | ||
| 286 | fn host(plain: *Plain) []u8 { | 287 | fn host(plain: *Plain) HostName { |
| 287 | const base: [*]u8 = @ptrCast(plain); | 288 | const base: [*]u8 = @ptrCast(plain); |
| 288 | return base[@sizeOf(Plain)..][0..plain.connection.host_len]; | 289 | return .{ .bytes = base[@sizeOf(Plain)..][0..plain.connection.host_len] }; |
| 289 | } | 290 | } |
| 290 | }; | 291 | }; |
| 291 | 292 | ||
| ... | @@ -295,15 +296,15 @@ pub const Connection = struct { | ... | @@ -295,15 +296,15 @@ pub const Connection = struct { |
| 295 | 296 | ||
| 296 | fn create( | 297 | fn create( |
| 297 | client: *Client, | 298 | client: *Client, |
| 298 | remote_host: []const u8, | 299 | remote_host: HostName, |
| 299 | port: u16, | 300 | port: u16, |
| 300 | stream: Io.net.Stream, | 301 | stream: Io.net.Stream, |
| 301 | ) error{ OutOfMemory, TlsInitializationFailed }!*Tls { | 302 | ) error{ OutOfMemory, TlsInitializationFailed }!*Tls { |
| 302 | const gpa = client.allocator; | 303 | const gpa = client.allocator; |
| 303 | const alloc_len = allocLen(client, remote_host.len); | 304 | const alloc_len = allocLen(client, remote_host.bytes.len); |
| 304 | const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len); | 305 | const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len); |
| 305 | errdefer gpa.free(base); | 306 | errdefer gpa.free(base); |
| 306 | const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len]; | 307 | const host_buffer = base[@sizeOf(Tls)..][0..remote_host.bytes.len]; |
| 307 | // The TLS client wants enough buffer for the max encrypted frame | 308 | // The TLS client wants enough buffer for the max encrypted frame |
| 308 | // size, and the HTTP body reader wants enough buffer for the | 309 | // size, and the HTTP body reader wants enough buffer for the |
| 309 | // entire HTTP header. This means we need a combined upper bound. | 310 | // entire HTTP header. This means we need a combined upper bound. |
| ... | @@ -313,7 +314,7 @@ pub const Connection = struct { | ... | @@ -313,7 +314,7 @@ pub const Connection = struct { |
| 313 | const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size]; | 314 | const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size]; |
| 314 | const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size]; | 315 | const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size]; |
| 315 | assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len); | 316 | assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len); |
| 316 | @memcpy(host_buffer, remote_host); | 317 | @memcpy(host_buffer, remote_host.bytes); |
| 317 | const tls: *Tls = @ptrCast(base); | 318 | const tls: *Tls = @ptrCast(base); |
| 318 | tls.* = .{ | 319 | tls.* = .{ |
| 319 | .connection = .{ | 320 | .connection = .{ |
| ... | @@ -322,17 +323,17 @@ pub const Connection = struct { | ... | @@ -322,17 +323,17 @@ pub const Connection = struct { |
| 322 | .stream_reader = stream.reader(socket_read_buffer), | 323 | .stream_reader = stream.reader(socket_read_buffer), |
| 323 | .pool_node = .{}, | 324 | .pool_node = .{}, |
| 324 | .port = port, | 325 | .port = port, |
| 325 | .host_len = @intCast(remote_host.len), | 326 | .host_len = @intCast(remote_host.bytes.len), |
| 326 | .proxied = false, | 327 | .proxied = false, |
| 327 | .closing = false, | 328 | .closing = false, |
| 328 | .protocol = .tls, | 329 | .protocol = .tls, |
| 329 | }, | 330 | }, |
| 330 | // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true | 331 | // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true |
| 331 | .client = std.crypto.tls.Client.init( | 332 | .client = std.crypto.tls.Client.init( |
| 332 | tls.connection.stream_reader.interface(), | 333 | &tls.connection.stream_reader.interface, |
| 333 | &tls.connection.stream_writer.interface, | 334 | &tls.connection.stream_writer.interface, |
| 334 | .{ | 335 | .{ |
| 335 | .host = .{ .explicit = remote_host }, | 336 | .host = .{ .explicit = remote_host.bytes }, |
| 336 | .ca = .{ .bundle = client.ca_bundle }, | 337 | .ca = .{ .bundle = client.ca_bundle }, |
| 337 | .ssl_key_log = client.ssl_key_log, | 338 | .ssl_key_log = client.ssl_key_log, |
| 338 | .read_buffer = tls_read_buffer, | 339 | .read_buffer = tls_read_buffer, |
| ... | @@ -359,9 +360,9 @@ pub const Connection = struct { | ... | @@ -359,9 +360,9 @@ pub const Connection = struct { |
| 359 | client.write_buffer_size + client.tls_buffer_size; | 360 | client.write_buffer_size + client.tls_buffer_size; |
| 360 | } | 361 | } |
| 361 | 362 | ||
| 362 | fn host(tls: *Tls) []u8 { | 363 | fn host(tls: *Tls) HostName { |
| 363 | const base: [*]u8 = @ptrCast(tls); | 364 | const base: [*]u8 = @ptrCast(tls); |
| 364 | return base[@sizeOf(Tls)..][0..tls.connection.host_len]; | 365 | return .{ .bytes = base[@sizeOf(Tls)..][0..tls.connection.host_len] }; |
| 365 | } | 366 | } |
| 366 | }; | 367 | }; |
| 367 | 368 | ||
| ... | @@ -384,7 +385,7 @@ pub const Connection = struct { | ... | @@ -384,7 +385,7 @@ pub const Connection = struct { |
| 384 | return c.stream_reader.stream; | 385 | return c.stream_reader.stream; |
| 385 | } | 386 | } |
| 386 | 387 | ||
| 387 | pub fn host(c: *Connection) []u8 { | 388 | pub fn host(c: *Connection) HostName { |
| 388 | return switch (c.protocol) { | 389 | return switch (c.protocol) { |
| 389 | .tls => { | 390 | .tls => { |
| 390 | if (disable_tls) unreachable; | 391 | if (disable_tls) unreachable; |
| ... | @@ -400,8 +401,8 @@ pub const Connection = struct { | ... | @@ -400,8 +401,8 @@ pub const Connection = struct { |
| 400 | 401 | ||
| 401 | /// If this is called without calling `flush` or `end`, data will be | 402 | /// If this is called without calling `flush` or `end`, data will be |
| 402 | /// dropped unsent. | 403 | /// dropped unsent. |
| 403 | pub fn destroy(c: *Connection) void { | 404 | pub fn destroy(c: *Connection, io: Io) void { |
| 404 | c.getStream().close(); | 405 | c.stream_reader.stream.close(io); |
| 405 | switch (c.protocol) { | 406 | switch (c.protocol) { |
| 406 | .tls => { | 407 | .tls => { |
| 407 | if (disable_tls) unreachable; | 408 | if (disable_tls) unreachable; |
| ... | @@ -437,7 +438,7 @@ pub const Connection = struct { | ... | @@ -437,7 +438,7 @@ pub const Connection = struct { |
| 437 | const tls: *Tls = @alignCast(@fieldParentPtr("connection", c)); | 438 | const tls: *Tls = @alignCast(@fieldParentPtr("connection", c)); |
| 438 | return &tls.client.reader; | 439 | return &tls.client.reader; |
| 439 | }, | 440 | }, |
| 440 | .plain => c.stream_reader.interface(), | 441 | .plain => &c.stream_reader.interface, |
| 441 | }; | 442 | }; |
| 442 | } | 443 | } |
| 443 | 444 | ||
| ... | @@ -866,6 +867,7 @@ pub const Request = struct { | ... | @@ -866,6 +867,7 @@ pub const Request = struct { |
| 866 | 867 | ||
| 867 | /// Returns the request's `Connection` back to the pool of the `Client`. | 868 | /// Returns the request's `Connection` back to the pool of the `Client`. |
| 868 | pub fn deinit(r: *Request) void { | 869 | pub fn deinit(r: *Request) void { |
| 870 | const io = r.client.io; | ||
| 869 | if (r.connection) |connection| { | 871 | if (r.connection) |connection| { |
| 870 | connection.closing = connection.closing or switch (r.reader.state) { | 872 | connection.closing = connection.closing or switch (r.reader.state) { |
| 871 | .ready => false, | 873 | .ready => false, |
| ... | @@ -880,7 +882,7 @@ pub const Request = struct { | ... | @@ -880,7 +882,7 @@ pub const Request = struct { |
| 880 | }, | 882 | }, |
| 881 | else => true, | 883 | else => true, |
| 882 | }; | 884 | }; |
| 883 | r.client.connection_pool.release(connection); | 885 | r.client.connection_pool.release(connection, io); |
| 884 | } | 886 | } |
| 885 | r.* = undefined; | 887 | r.* = undefined; |
| 886 | } | 888 | } |
| ... | @@ -1182,6 +1184,7 @@ pub const Request = struct { | ... | @@ -1182,6 +1184,7 @@ pub const Request = struct { |
| 1182 | /// | 1184 | /// |
| 1183 | /// `aux_buf` must outlive accesses to `Request.uri`. | 1185 | /// `aux_buf` must outlive accesses to `Request.uri`. |
| 1184 | fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void { | 1186 | fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void { |
| 1187 | const io = r.client.io; | ||
| 1185 | const new_location = head.location orelse return error.HttpRedirectLocationMissing; | 1188 | const new_location = head.location orelse return error.HttpRedirectLocationMissing; |
| 1186 | if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize; | 1189 | if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize; |
| 1187 | const location = aux_buf.*[0..new_location.len]; | 1190 | const location = aux_buf.*[0..new_location.len]; |
| ... | @@ -1204,13 +1207,13 @@ pub const Request = struct { | ... | @@ -1204,13 +1207,13 @@ pub const Request = struct { |
| 1204 | const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme; | 1207 | const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme; |
| 1205 | const old_connection = r.connection.?; | 1208 | const old_connection = r.connection.?; |
| 1206 | const old_host = old_connection.host(); | 1209 | const old_host = old_connection.host(); |
| 1207 | var new_host_name_buffer: [Uri.host_name_max]u8 = undefined; | 1210 | var new_host_name_buffer: [HostName.max_len]u8 = undefined; |
| 1208 | const new_host = try new_uri.getHost(&new_host_name_buffer); | 1211 | const new_host = try new_uri.getHost(&new_host_name_buffer); |
| 1209 | const keep_privileged_headers = | 1212 | const keep_privileged_headers = |
| 1210 | std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and | 1213 | std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and |
| 1211 | sameParentDomain(old_host, new_host); | 1214 | old_host.sameParentDomain(new_host); |
| 1212 | 1215 | ||
| 1213 | r.client.connection_pool.release(old_connection); | 1216 | r.client.connection_pool.release(old_connection, io); |
| 1214 | r.connection = null; | 1217 | r.connection = null; |
| 1215 | 1218 | ||
| 1216 | if (!keep_privileged_headers) { | 1219 | if (!keep_privileged_headers) { |
| ... | @@ -1266,7 +1269,7 @@ pub const Request = struct { | ... | @@ -1266,7 +1269,7 @@ pub const Request = struct { |
| 1266 | 1269 | ||
| 1267 | pub const Proxy = struct { | 1270 | pub const Proxy = struct { |
| 1268 | protocol: Protocol, | 1271 | protocol: Protocol, |
| 1269 | host: []const u8, | 1272 | host: HostName, |
| 1270 | authorization: ?[]const u8, | 1273 | authorization: ?[]const u8, |
| 1271 | port: u16, | 1274 | port: u16, |
| 1272 | supports_connect: bool, | 1275 | supports_connect: bool, |
| ... | @@ -1277,9 +1280,10 @@ pub const Proxy = struct { | ... | @@ -1277,9 +1280,10 @@ pub const Proxy = struct { |
| 1277 | /// All pending requests must be de-initialized and all active connections released | 1280 | /// All pending requests must be de-initialized and all active connections released |
| 1278 | /// before calling this function. | 1281 | /// before calling this function. |
| 1279 | pub fn deinit(client: *Client) void { | 1282 | pub fn deinit(client: *Client) void { |
| 1283 | const io = client.io; | ||
| 1280 | assert(client.connection_pool.used.first == null); // There are still active requests. | 1284 | assert(client.connection_pool.used.first == null); // There are still active requests. |
| 1281 | 1285 | ||
| 1282 | client.connection_pool.deinit(); | 1286 | client.connection_pool.deinit(io); |
| 1283 | if (!disable_tls) client.ca_bundle.deinit(client.allocator); | 1287 | if (!disable_tls) client.ca_bundle.deinit(client.allocator); |
| 1284 | 1288 | ||
| 1285 | client.* = undefined; | 1289 | client.* = undefined; |
| ... | @@ -1385,7 +1389,7 @@ pub const basic_authorization = struct { | ... | @@ -1385,7 +1389,7 @@ pub const basic_authorization = struct { |
| 1385 | } | 1389 | } |
| 1386 | }; | 1390 | }; |
| 1387 | 1391 | ||
| 1388 | pub const ConnectTcpError = Allocator.Error || error{ | 1392 | pub const ConnectTcpError = error{ |
| 1389 | ConnectionRefused, | 1393 | ConnectionRefused, |
| 1390 | NetworkUnreachable, | 1394 | NetworkUnreachable, |
| 1391 | ConnectionTimedOut, | 1395 | ConnectionTimedOut, |
| ... | @@ -1393,17 +1397,16 @@ pub const ConnectTcpError = Allocator.Error || error{ | ... | @@ -1393,17 +1397,16 @@ pub const ConnectTcpError = Allocator.Error || error{ |
| 1393 | TemporaryNameServerFailure, | 1397 | TemporaryNameServerFailure, |
| 1394 | NameServerFailure, | 1398 | NameServerFailure, |
| 1395 | UnknownHostName, | 1399 | UnknownHostName, |
| 1396 | HostLacksNetworkAddresses, | ||
| 1397 | UnexpectedConnectFailure, | 1400 | UnexpectedConnectFailure, |
| 1398 | TlsInitializationFailed, | 1401 | TlsInitializationFailed, |
| 1399 | }; | 1402 | } || Allocator.Error || Io.Cancelable; |
| 1400 | 1403 | ||
| 1401 | /// Reuses a `Connection` if one matching `host` and `port` is already open. | 1404 | /// Reuses a `Connection` if one matching `host` and `port` is already open. |
| 1402 | /// | 1405 | /// |
| 1403 | /// Threadsafe. | 1406 | /// Threadsafe. |
| 1404 | pub fn connectTcp( | 1407 | pub fn connectTcp( |
| 1405 | client: *Client, | 1408 | client: *Client, |
| 1406 | host: []const u8, | 1409 | host: HostName, |
| 1407 | port: u16, | 1410 | port: u16, |
| 1408 | protocol: Protocol, | 1411 | protocol: Protocol, |
| 1409 | ) ConnectTcpError!*Connection { | 1412 | ) ConnectTcpError!*Connection { |
| ... | @@ -1411,16 +1414,17 @@ pub fn connectTcp( | ... | @@ -1411,16 +1414,17 @@ pub fn connectTcp( |
| 1411 | } | 1414 | } |
| 1412 | 1415 | ||
| 1413 | pub const ConnectTcpOptions = struct { | 1416 | pub const ConnectTcpOptions = struct { |
| 1414 | host: Io.net.HostName, | 1417 | host: HostName, |
| 1415 | port: u16, | 1418 | port: u16, |
| 1416 | protocol: Protocol, | 1419 | protocol: Protocol, |
| 1417 | 1420 | ||
| 1418 | proxied_host: ?[]const u8 = null, | 1421 | proxied_host: ?HostName = null, |
| 1419 | proxied_port: ?u16 = null, | 1422 | proxied_port: ?u16 = null, |
| 1423 | timeout: Io.Timeout = .none, | ||
| 1420 | }; | 1424 | }; |
| 1421 | 1425 | ||
| 1422 | pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection { | 1426 | pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection { |
| 1423 | const host = options.host_name; | 1427 | const host = options.host; |
| 1424 | const port = options.port; | 1428 | const port = options.port; |
| 1425 | const protocol = options.protocol; | 1429 | const protocol = options.protocol; |
| 1426 | 1430 | ||
| ... | @@ -1433,17 +1437,15 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp | ... | @@ -1433,17 +1437,15 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp |
| 1433 | .protocol = protocol, | 1437 | .protocol = protocol, |
| 1434 | })) |conn| return conn; | 1438 | })) |conn| return conn; |
| 1435 | 1439 | ||
| 1436 | const stream = host.connectTcp(client.io, port) catch |err| switch (err) { | 1440 | const stream = host.connect(client.io, port, .{ .mode = .stream }) catch |err| switch (err) { |
| 1437 | error.ConnectionRefused => return error.ConnectionRefused, | 1441 | error.ConnectionRefused => return error.ConnectionRefused, |
| 1438 | error.NetworkUnreachable => return error.NetworkUnreachable, | 1442 | error.NetworkUnreachable => return error.NetworkUnreachable, |
| 1439 | error.ConnectionTimedOut => return error.ConnectionTimedOut, | 1443 | error.ConnectionTimedOut => return error.ConnectionTimedOut, |
| 1440 | error.ConnectionResetByPeer => return error.ConnectionResetByPeer, | 1444 | error.ConnectionResetByPeer => return error.ConnectionResetByPeer, |
| 1441 | error.TemporaryNameServerFailure => return error.TemporaryNameServerFailure, | ||
| 1442 | error.NameServerFailure => return error.NameServerFailure, | 1445 | error.NameServerFailure => return error.NameServerFailure, |
| 1443 | error.UnknownHostName => return error.UnknownHostName, | 1446 | error.UnknownHostName => return error.UnknownHostName, |
| 1444 | error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses, | ||
| 1445 | error.Canceled => return error.Canceled, | 1447 | error.Canceled => return error.Canceled, |
| 1446 | else => return error.UnexpectedConnectFailure, | 1448 | //else => return error.UnexpectedConnectFailure, |
| 1447 | }; | 1449 | }; |
| 1448 | errdefer stream.close(); | 1450 | errdefer stream.close(); |
| 1449 | 1451 | ||
| ... | @@ -1479,7 +1481,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti | ... | @@ -1479,7 +1481,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1479 | errdefer client.allocator.destroy(conn); | 1481 | errdefer client.allocator.destroy(conn); |
| 1480 | conn.* = .{ .data = undefined }; | 1482 | conn.* = .{ .data = undefined }; |
| 1481 | 1483 | ||
| 1482 | const stream = try std.net.connectUnixSocket(path); | 1484 | const stream = try Io.net.connectUnixSocket(path); |
| 1483 | errdefer stream.close(); | 1485 | errdefer stream.close(); |
| 1484 | 1486 | ||
| 1485 | conn.data = .{ | 1487 | conn.data = .{ |
| ... | @@ -1504,9 +1506,10 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti | ... | @@ -1504,9 +1506,10 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1504 | pub fn connectProxied( | 1506 | pub fn connectProxied( |
| 1505 | client: *Client, | 1507 | client: *Client, |
| 1506 | proxy: *Proxy, | 1508 | proxy: *Proxy, |
| 1507 | proxied_host: []const u8, | 1509 | proxied_host: HostName, |
| 1508 | proxied_port: u16, | 1510 | proxied_port: u16, |
| 1509 | ) !*Connection { | 1511 | ) !*Connection { |
| 1512 | const io = client.io; | ||
| 1510 | if (!proxy.supports_connect) return error.TunnelNotSupported; | 1513 | if (!proxy.supports_connect) return error.TunnelNotSupported; |
| 1511 | 1514 | ||
| 1512 | if (client.connection_pool.findConnection(.{ | 1515 | if (client.connection_pool.findConnection(.{ |
| ... | @@ -1526,12 +1529,12 @@ pub fn connectProxied( | ... | @@ -1526,12 +1529,12 @@ pub fn connectProxied( |
| 1526 | }); | 1529 | }); |
| 1527 | errdefer { | 1530 | errdefer { |
| 1528 | connection.closing = true; | 1531 | connection.closing = true; |
| 1529 | client.connection_pool.release(connection); | 1532 | client.connection_pool.release(connection, io); |
| 1530 | } | 1533 | } |
| 1531 | 1534 | ||
| 1532 | var req = client.request(.CONNECT, .{ | 1535 | var req = client.request(.CONNECT, .{ |
| 1533 | .scheme = "http", | 1536 | .scheme = "http", |
| 1534 | .host = .{ .raw = proxied_host }, | 1537 | .host = .{ .raw = proxied_host.bytes }, |
| 1535 | .port = proxied_port, | 1538 | .port = proxied_port, |
| 1536 | }, .{ | 1539 | }, .{ |
| 1537 | .redirect_behavior = .unhandled, | 1540 | .redirect_behavior = .unhandled, |
| ... | @@ -1576,7 +1579,7 @@ pub const ConnectError = ConnectTcpError || RequestError; | ... | @@ -1576,7 +1579,7 @@ pub const ConnectError = ConnectTcpError || RequestError; |
| 1576 | /// This function is threadsafe. | 1579 | /// This function is threadsafe. |
| 1577 | pub fn connect( | 1580 | pub fn connect( |
| 1578 | client: *Client, | 1581 | client: *Client, |
| 1579 | host: []const u8, | 1582 | host: HostName, |
| 1580 | port: u16, | 1583 | port: u16, |
| 1581 | protocol: Protocol, | 1584 | protocol: Protocol, |
| 1582 | ) ConnectError!*Connection { | 1585 | ) ConnectError!*Connection { |
| ... | @@ -1586,9 +1589,7 @@ pub fn connect( | ... | @@ -1586,9 +1589,7 @@ pub fn connect( |
| 1586 | } orelse return client.connectTcp(host, port, protocol); | 1589 | } orelse return client.connectTcp(host, port, protocol); |
| 1587 | 1590 | ||
| 1588 | // Prevent proxying through itself. | 1591 | // Prevent proxying through itself. |
| 1589 | if (std.ascii.eqlIgnoreCase(proxy.host, host) and | 1592 | if (proxy.host.eql(host) and proxy.port == port and proxy.protocol == protocol) { |
| 1590 | proxy.port == port and proxy.protocol == protocol) | ||
| 1591 | { | ||
| 1592 | return client.connectTcp(host, port, protocol); | 1593 | return client.connectTcp(host, port, protocol); |
| 1593 | } | 1594 | } |
| 1594 | 1595 | ||
| ... | @@ -1608,7 +1609,6 @@ pub fn connect( | ... | @@ -1608,7 +1609,6 @@ pub fn connect( |
| 1608 | pub const RequestError = ConnectTcpError || error{ | 1609 | pub const RequestError = ConnectTcpError || error{ |
| 1609 | UnsupportedUriScheme, | 1610 | UnsupportedUriScheme, |
| 1610 | UriMissingHost, | 1611 | UriMissingHost, |
| 1611 | UriHostTooLong, | ||
| 1612 | CertificateBundleLoadFailure, | 1612 | CertificateBundleLoadFailure, |
| 1613 | }; | 1613 | }; |
| 1614 | 1614 | ||
| ... | @@ -1697,7 +1697,7 @@ pub fn request( | ... | @@ -1697,7 +1697,7 @@ pub fn request( |
| 1697 | } | 1697 | } |
| 1698 | 1698 | ||
| 1699 | const connection = options.connection orelse c: { | 1699 | const connection = options.connection orelse c: { |
| 1700 | var host_name_buffer: [Uri.host_name_max]u8 = undefined; | 1700 | var host_name_buffer: [HostName.max_len]u8 = undefined; |
| 1701 | const host_name = try uri.getHost(&host_name_buffer); | 1701 | const host_name = try uri.getHost(&host_name_buffer); |
| 1702 | break :c try client.connect(host_name, uriPort(uri, protocol), protocol); | 1702 | break :c try client.connect(host_name, uriPort(uri, protocol), protocol); |
| 1703 | }; | 1703 | }; |
| ... | @@ -1835,20 +1835,6 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult { | ... | @@ -1835,20 +1835,6 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult { |
| 1835 | return .{ .status = response.head.status }; | 1835 | return .{ .status = response.head.status }; |
| 1836 | } | 1836 | } |
| 1837 | 1837 | ||
| 1838 | pub fn sameParentDomain(parent_host: []const u8, child_host: []const u8) bool { | ||
| 1839 | if (!std.ascii.endsWithIgnoreCase(child_host, parent_host)) return false; | ||
| 1840 | if (child_host.len == parent_host.len) return true; | ||
| 1841 | if (parent_host.len > child_host.len) return false; | ||
| 1842 | return child_host[child_host.len - parent_host.len - 1] == '.'; | ||
| 1843 | } | ||
| 1844 | |||
| 1845 | test sameParentDomain { | ||
| 1846 | try testing.expect(!sameParentDomain("foo.com", "bar.com")); | ||
| 1847 | try testing.expect(sameParentDomain("foo.com", "foo.com")); | ||
| 1848 | try testing.expect(sameParentDomain("foo.com", "bar.foo.com")); | ||
| 1849 | try testing.expect(!sameParentDomain("bar.foo.com", "foo.com")); | ||
| 1850 | } | ||
| 1851 | |||
| 1852 | test { | 1838 | test { |
| 1853 | _ = Response; | 1839 | _ = Response; |
| 1854 | } | 1840 | } |
lib/std/http/test.zig+36-26| ... | @@ -53,7 +53,7 @@ test "trailers" { | ... | @@ -53,7 +53,7 @@ test "trailers" { |
| 53 | 53 | ||
| 54 | const gpa = std.testing.allocator; | 54 | const gpa = std.testing.allocator; |
| 55 | 55 | ||
| 56 | var client: http.Client = .{ .allocator = gpa }; | 56 | var client: http.Client = .{ .allocator = gpa, .io = io }; |
| 57 | defer client.deinit(); | 57 | defer client.deinit(); |
| 58 | 58 | ||
| 59 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{ | 59 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{ |
| ... | @@ -141,12 +141,13 @@ test "HTTP server handles a chunked transfer coding request" { | ... | @@ -141,12 +141,13 @@ test "HTTP server handles a chunked transfer coding request" { |
| 141 | "0\r\n" ++ | 141 | "0\r\n" ++ |
| 142 | "\r\n"; | 142 | "\r\n"; |
| 143 | 143 | ||
| 144 | const gpa = std.testing.allocator; | 144 | const host_name: net.HostName = try .init("127.0.0.1"); |
| 145 | var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); | 145 | var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream }); |
| 146 | defer stream.close(io); | 146 | defer stream.close(io); |
| 147 | var stream_writer = stream.writer(&.{}); | 147 | var stream_writer = stream.writer(io, &.{}); |
| 148 | try stream_writer.interface.writeAll(request_bytes); | 148 | try stream_writer.interface.writeAll(request_bytes); |
| 149 | 149 | ||
| 150 | const gpa = std.testing.allocator; | ||
| 150 | const expected_response = | 151 | const expected_response = |
| 151 | "HTTP/1.1 200 OK\r\n" ++ | 152 | "HTTP/1.1 200 OK\r\n" ++ |
| 152 | "connection: close\r\n" ++ | 153 | "connection: close\r\n" ++ |
| ... | @@ -154,8 +155,8 @@ test "HTTP server handles a chunked transfer coding request" { | ... | @@ -154,8 +155,8 @@ test "HTTP server handles a chunked transfer coding request" { |
| 154 | "content-type: text/plain\r\n" ++ | 155 | "content-type: text/plain\r\n" ++ |
| 155 | "\r\n" ++ | 156 | "\r\n" ++ |
| 156 | "message from server!\n"; | 157 | "message from server!\n"; |
| 157 | var stream_reader = stream.reader(&.{}); | 158 | var stream_reader = stream.reader(io, &.{}); |
| 158 | const response = try stream_reader.interface().allocRemaining(gpa, .limited(expected_response.len + 1)); | 159 | const response = try stream_reader.interface.allocRemaining(gpa, .limited(expected_response.len + 1)); |
| 159 | defer gpa.free(response); | 160 | defer gpa.free(response); |
| 160 | try expectEqualStrings(expected_response, response); | 161 | try expectEqualStrings(expected_response, response); |
| 161 | } | 162 | } |
| ... | @@ -241,7 +242,7 @@ test "echo content server" { | ... | @@ -241,7 +242,7 @@ test "echo content server" { |
| 241 | defer test_server.destroy(); | 242 | defer test_server.destroy(); |
| 242 | 243 | ||
| 243 | { | 244 | { |
| 244 | var client: http.Client = .{ .allocator = std.testing.allocator }; | 245 | var client: http.Client = .{ .allocator = std.testing.allocator, .io = io }; |
| 245 | defer client.deinit(); | 246 | defer client.deinit(); |
| 246 | 247 | ||
| 247 | try echoTests(&client, test_server.port()); | 248 | try echoTests(&client, test_server.port()); |
| ... | @@ -294,14 +295,15 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { | ... | @@ -294,14 +295,15 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 294 | defer test_server.destroy(); | 295 | defer test_server.destroy(); |
| 295 | 296 | ||
| 296 | const request_bytes = "GET /foo HTTP/1.1\r\n\r\n"; | 297 | const request_bytes = "GET /foo HTTP/1.1\r\n\r\n"; |
| 297 | const gpa = std.testing.allocator; | 298 | const host_name: net.HostName = try .init("127.0.0.1"); |
| 298 | var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); | 299 | var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream }); |
| 299 | defer stream.close(io); | 300 | defer stream.close(io); |
| 300 | var stream_writer = stream.writer(&.{}); | 301 | var stream_writer = stream.writer(io, &.{}); |
| 301 | try stream_writer.interface.writeAll(request_bytes); | 302 | try stream_writer.interface.writeAll(request_bytes); |
| 302 | 303 | ||
| 303 | var stream_reader = stream.reader(&.{}); | 304 | var stream_reader = stream.reader(io, &.{}); |
| 304 | const response = try stream_reader.interface().allocRemaining(gpa, .unlimited); | 305 | const gpa = std.testing.allocator; |
| 306 | const response = try stream_reader.interface.allocRemaining(gpa, .unlimited); | ||
| 305 | defer gpa.free(response); | 307 | defer gpa.free(response); |
| 306 | 308 | ||
| 307 | var expected_response = std.array_list.Managed(u8).init(gpa); | 309 | var expected_response = std.array_list.Managed(u8).init(gpa); |
| ... | @@ -366,14 +368,15 @@ test "receiving arbitrary http headers from the client" { | ... | @@ -366,14 +368,15 @@ test "receiving arbitrary http headers from the client" { |
| 366 | "CoNneCtIoN:close\r\n" ++ | 368 | "CoNneCtIoN:close\r\n" ++ |
| 367 | "aoeu: asdf \r\n" ++ | 369 | "aoeu: asdf \r\n" ++ |
| 368 | "\r\n"; | 370 | "\r\n"; |
| 369 | const gpa = std.testing.allocator; | 371 | const host_name: net.HostName = try .init("127.0.0.1"); |
| 370 | var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); | 372 | var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream }); |
| 371 | defer stream.close(io); | 373 | defer stream.close(io); |
| 372 | var stream_writer = stream.writer(&.{}); | 374 | var stream_writer = stream.writer(io, &.{}); |
| 373 | try stream_writer.interface.writeAll(request_bytes); | 375 | try stream_writer.interface.writeAll(request_bytes); |
| 374 | 376 | ||
| 375 | var stream_reader = stream.reader(&.{}); | 377 | var stream_reader = stream.reader(io, &.{}); |
| 376 | const response = try stream_reader.interface().allocRemaining(gpa, .unlimited); | 378 | const gpa = std.testing.allocator; |
| 379 | const response = try stream_reader.interface.allocRemaining(gpa, .unlimited); | ||
| 377 | defer gpa.free(response); | 380 | defer gpa.free(response); |
| 378 | 381 | ||
| 379 | var expected_response = std.array_list.Managed(u8).init(gpa); | 382 | var expected_response = std.array_list.Managed(u8).init(gpa); |
| ... | @@ -413,7 +416,7 @@ test "general client/server API coverage" { | ... | @@ -413,7 +416,7 @@ test "general client/server API coverage" { |
| 413 | else => |e| return e, | 416 | else => |e| return e, |
| 414 | }; | 417 | }; |
| 415 | 418 | ||
| 416 | try handleRequest(&request, net_server.listen_address.getPort()); | 419 | try handleRequest(&request, net_server.socket.address.getPort()); |
| 417 | } | 420 | } |
| 418 | } | 421 | } |
| 419 | } | 422 | } |
| ... | @@ -543,9 +546,9 @@ test "general client/server API coverage" { | ... | @@ -543,9 +546,9 @@ test "general client/server API coverage" { |
| 543 | 546 | ||
| 544 | fn getUnusedTcpPort() !u16 { | 547 | fn getUnusedTcpPort() !u16 { |
| 545 | const addr = try net.IpAddress.parse("127.0.0.1", 0); | 548 | const addr = try net.IpAddress.parse("127.0.0.1", 0); |
| 546 | var s = try addr.listen(.{}); | 549 | var s = try addr.listen(io, .{}); |
| 547 | defer s.deinit(); | 550 | defer s.deinit(io); |
| 548 | return s.listen_address.in.getPort(); | 551 | return s.socket.address.getPort(); |
| 549 | } | 552 | } |
| 550 | }); | 553 | }); |
| 551 | defer test_server.destroy(); | 554 | defer test_server.destroy(); |
| ... | @@ -553,7 +556,7 @@ test "general client/server API coverage" { | ... | @@ -553,7 +556,7 @@ test "general client/server API coverage" { |
| 553 | const log = std.log.scoped(.client); | 556 | const log = std.log.scoped(.client); |
| 554 | 557 | ||
| 555 | const gpa = std.testing.allocator; | 558 | const gpa = std.testing.allocator; |
| 556 | var client: http.Client = .{ .allocator = gpa }; | 559 | var client: http.Client = .{ .allocator = gpa, .io = io }; |
| 557 | defer client.deinit(); | 560 | defer client.deinit(); |
| 558 | 561 | ||
| 559 | const port = test_server.port(); | 562 | const port = test_server.port(); |
| ... | @@ -918,7 +921,10 @@ test "Server streams both reading and writing" { | ... | @@ -918,7 +921,10 @@ test "Server streams both reading and writing" { |
| 918 | }); | 921 | }); |
| 919 | defer test_server.destroy(); | 922 | defer test_server.destroy(); |
| 920 | 923 | ||
| 921 | var client: http.Client = .{ .allocator = std.testing.allocator }; | 924 | var client: http.Client = .{ |
| 925 | .allocator = std.testing.allocator, | ||
| 926 | .io = io, | ||
| 927 | }; | ||
| 922 | defer client.deinit(); | 928 | defer client.deinit(); |
| 923 | 929 | ||
| 924 | var redirect_buffer: [555]u8 = undefined; | 930 | var redirect_buffer: [555]u8 = undefined; |
| ... | @@ -1089,17 +1095,20 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -1089,17 +1095,20 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1089 | } | 1095 | } |
| 1090 | 1096 | ||
| 1091 | const TestServer = struct { | 1097 | const TestServer = struct { |
| 1098 | io: Io, | ||
| 1092 | shutting_down: bool, | 1099 | shutting_down: bool, |
| 1093 | server_thread: std.Thread, | 1100 | server_thread: std.Thread, |
| 1094 | net_server: net.Server, | 1101 | net_server: net.Server, |
| 1095 | 1102 | ||
| 1096 | fn destroy(self: *@This()) void { | 1103 | fn destroy(self: *@This()) void { |
| 1104 | const io = self.io; | ||
| 1097 | self.shutting_down = true; | 1105 | self.shutting_down = true; |
| 1098 | const conn = net.tcpConnectToAddress(self.net_server.listen_address) catch @panic("shutdown failure"); | 1106 | var stream = self.net_server.socket.address.connect(io, .{ .mode = .stream }) catch |
| 1099 | conn.close(); | 1107 | @panic("shutdown failure"); |
| 1108 | stream.close(io); | ||
| 1100 | 1109 | ||
| 1101 | self.server_thread.join(); | 1110 | self.server_thread.join(); |
| 1102 | self.net_server.deinit(); | 1111 | self.net_server.deinit(io); |
| 1103 | std.testing.allocator.destroy(self); | 1112 | std.testing.allocator.destroy(self); |
| 1104 | } | 1113 | } |
| 1105 | 1114 | ||
| ... | @@ -1118,6 +1127,7 @@ fn createTestServer(io: Io, S: type) !*TestServer { | ... | @@ -1118,6 +1127,7 @@ fn createTestServer(io: Io, S: type) !*TestServer { |
| 1118 | const address = try net.IpAddress.parse("127.0.0.1", 0); | 1127 | const address = try net.IpAddress.parse("127.0.0.1", 0); |
| 1119 | const test_server = try std.testing.allocator.create(TestServer); | 1128 | const test_server = try std.testing.allocator.create(TestServer); |
| 1120 | test_server.* = .{ | 1129 | test_server.* = .{ |
| 1130 | .io = io, | ||
| 1121 | .net_server = try address.listen(io, .{ .reuse_address = true }), | 1131 | .net_server = try address.listen(io, .{ .reuse_address = true }), |
| 1122 | .shutting_down = false, | 1132 | .shutting_down = false, |
| 1123 | .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}), | 1133 | .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}), |