authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-18 23:34:04-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:26-07:00
log5ada2e39371202f7bbd454c37be31a602f534136
tree6547878160aa3f31f879f8cbd2f74e8f252d1f10
parent6c7b103122f7394f2596830e77c8194a0d33770e

std.http tests passing with updated writer API

fix splat implementation in std.fs.File update http.Client, with caveats: * TODO: only 1 underlying write call * TODO: don't rely on max_buffers_len exceeding the caller * TODO: handle splat update net.Stream API. also make it use WSASend on windows

10 files changed, 609 insertions(+), 580 deletions(-)

lib/std/Uri.zig+28-42
......@@ -42,18 +42,19 @@ pub const Component = union(enum) {
4242
4343 pub fn format(
4444 component: Component,
45 comptime fmt_str: []const u8,
46 _: std.fmt.FormatOptions,
45 comptime fmt: []const u8,
46 options: std.fmt.Options,
4747 writer: *std.io.BufferedWriter,
4848 ) anyerror!void {
49 if (fmt_str.len == 0) {
49 _ = options;
50 if (fmt.len == 0) {
5051 try writer.print("std.Uri.Component{{ .{s} = \"{}\" }}", .{
5152 @tagName(component),
5253 std.zig.fmtEscapes(switch (component) {
5354 .raw, .percent_encoded => |string| string,
5455 }),
5556 });
56 } else if (comptime std.mem.eql(u8, fmt_str, "raw")) switch (component) {
57 } else if (comptime std.mem.eql(u8, fmt, "raw")) switch (component) {
5758 .raw => |raw| try writer.writeAll(raw),
5859 .percent_encoded => |percent_encoded| {
5960 var start: usize = 0;
......@@ -72,28 +73,28 @@ pub const Component = union(enum) {
7273 }
7374 try writer.writeAll(percent_encoded[start..]);
7475 },
75 } else if (comptime std.mem.eql(u8, fmt_str, "%")) switch (component) {
76 } else if (comptime std.mem.eql(u8, fmt, "%")) switch (component) {
7677 .raw => |raw| try percentEncode(writer, raw, isUnreserved),
7778 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
78 } else if (comptime std.mem.eql(u8, fmt_str, "user")) switch (component) {
79 } else if (comptime std.mem.eql(u8, fmt, "user")) switch (component) {
7980 .raw => |raw| try percentEncode(writer, raw, isUserChar),
8081 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
81 } else if (comptime std.mem.eql(u8, fmt_str, "password")) switch (component) {
82 } else if (comptime std.mem.eql(u8, fmt, "password")) switch (component) {
8283 .raw => |raw| try percentEncode(writer, raw, isPasswordChar),
8384 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
84 } else if (comptime std.mem.eql(u8, fmt_str, "host")) switch (component) {
85 } else if (comptime std.mem.eql(u8, fmt, "host")) switch (component) {
8586 .raw => |raw| try percentEncode(writer, raw, isHostChar),
8687 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
87 } else if (comptime std.mem.eql(u8, fmt_str, "path")) switch (component) {
88 } else if (comptime std.mem.eql(u8, fmt, "path")) switch (component) {
8889 .raw => |raw| try percentEncode(writer, raw, isPathChar),
8990 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
90 } else if (comptime std.mem.eql(u8, fmt_str, "query")) switch (component) {
91 } else if (comptime std.mem.eql(u8, fmt, "query")) switch (component) {
9192 .raw => |raw| try percentEncode(writer, raw, isQueryChar),
9293 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
93 } else if (comptime std.mem.eql(u8, fmt_str, "fragment")) switch (component) {
94 } else if (comptime std.mem.eql(u8, fmt, "fragment")) switch (component) {
9495 .raw => |raw| try percentEncode(writer, raw, isFragmentChar),
9596 .percent_encoded => |percent_encoded| try writer.writeAll(percent_encoded),
96 } else @compileError("invalid format string '" ++ fmt_str ++ "'");
97 } else @compileError("invalid format string '" ++ fmt ++ "'");
9798 }
9899
99100 pub fn percentEncode(
......@@ -227,31 +228,21 @@ pub fn parseAfterScheme(scheme: []const u8, text: []const u8) ParseError!Uri {
227228pub const WriteToStreamOptions = struct {
228229 /// When true, include the scheme part of the URI.
229230 scheme: bool = false,
230
231231 /// When true, include the user and password part of the URI. Ignored if `authority` is false.
232232 authentication: bool = false,
233
234233 /// When true, include the authority part of the URI.
235234 authority: bool = false,
236
237235 /// When true, include the path part of the URI.
238236 path: bool = false,
239
240237 /// When true, include the query part of the URI. Ignored when `path` is false.
241238 query: bool = false,
242
243239 /// When true, include the fragment part of the URI. Ignored when `path` is false.
244240 fragment: bool = false,
245
246241 /// When true, include the port part of the URI. Ignored when `port` is null.
247242 port: bool = true,
248243};
249244
250pub fn writeToStream(
251 uri: Uri,
252 options: WriteToStreamOptions,
253 writer: anytype,
254) @TypeOf(writer).Error!void {
245pub fn writeToStream(uri: Uri, options: WriteToStreamOptions, writer: *std.io.BufferedWriter) anyerror!void {
255246 if (options.scheme) {
256247 try writer.print("{s}:", .{uri.scheme});
257248 if (options.authority and uri.host != null) {
......@@ -261,45 +252,40 @@ pub fn writeToStream(
261252 if (options.authority) {
262253 if (options.authentication and uri.host != null) {
263254 if (uri.user) |user| {
264 try writer.print("{user}", .{user});
255 try writer.print("{fuser}", .{user});
265256 if (uri.password) |password| {
266 try writer.print(":{password}", .{password});
257 try writer.print(":{fpassword}", .{password});
267258 }
268259 try writer.writeByte('@');
269260 }
270261 }
271262 if (uri.host) |host| {
272 try writer.print("{host}", .{host});
263 try writer.print("{fhost}", .{host});
273264 if (options.port) {
274265 if (uri.port) |port| try writer.print(":{d}", .{port});
275266 }
276267 }
277268 }
278269 if (options.path) {
279 try writer.print("{path}", .{
270 try writer.print("{fpath}", .{
280271 if (uri.path.isEmpty()) Uri.Component{ .percent_encoded = "/" } else uri.path,
281272 });
282273 if (options.query) {
283 if (uri.query) |query| try writer.print("?{query}", .{query});
274 if (uri.query) |query| try writer.print("?{fquery}", .{query});
284275 }
285276 if (options.fragment) {
286 if (uri.fragment) |fragment| try writer.print("#{fragment}", .{fragment});
277 if (uri.fragment) |fragment| try writer.print("#{ffragment}", .{fragment});
287278 }
288279 }
289280}
290281
291pub fn format(
292 uri: Uri,
293 comptime fmt_str: []const u8,
294 _: std.fmt.FormatOptions,
295 writer: anytype,
296) @TypeOf(writer).Error!void {
297 const scheme = comptime std.mem.indexOfScalar(u8, fmt_str, ';') != null or fmt_str.len == 0;
298 const authentication = comptime std.mem.indexOfScalar(u8, fmt_str, '@') != null or fmt_str.len == 0;
299 const authority = comptime std.mem.indexOfScalar(u8, fmt_str, '+') != null or fmt_str.len == 0;
300 const path = comptime std.mem.indexOfScalar(u8, fmt_str, '/') != null or fmt_str.len == 0;
301 const query = comptime std.mem.indexOfScalar(u8, fmt_str, '?') != null or fmt_str.len == 0;
302 const fragment = comptime std.mem.indexOfScalar(u8, fmt_str, '#') != null or fmt_str.len == 0;
282pub fn format(uri: Uri, comptime fmt: []const u8, _: std.fmt.Options, writer: *std.io.BufferedWriter) anyerror!void {
283 const scheme = comptime std.mem.indexOfScalar(u8, fmt, ';') != null or fmt.len == 0;
284 const authentication = comptime std.mem.indexOfScalar(u8, fmt, '@') != null or fmt.len == 0;
285 const authority = comptime std.mem.indexOfScalar(u8, fmt, '+') != null or fmt.len == 0;
286 const path = comptime std.mem.indexOfScalar(u8, fmt, '/') != null or fmt.len == 0;
287 const query = comptime std.mem.indexOfScalar(u8, fmt, '?') != null or fmt.len == 0;
288 const fragment = comptime std.mem.indexOfScalar(u8, fmt, '#') != null or fmt.len == 0;
303289
304290 return writeToStream(uri, .{
305291 .scheme = scheme,
......@@ -449,7 +435,7 @@ fn merge_paths(base: Component, new: []u8, aux_buf: *[]u8) error{NoSpaceLeft}!Co
449435 aux.initFixed(aux_buf.*);
450436 if (!base.isEmpty()) {
451437 aux.print("{fpath}", .{base}) catch |err| return @errorCast(err);
452 aux.pos = std.mem.lastIndexOfScalar(u8, aux.getWritten(), '/') orelse
438 aux.end = std.mem.lastIndexOfScalar(u8, aux.getWritten(), '/') orelse
453439 return remove_dot_segments(new);
454440 }
455441 aux.print("/{s}", .{new}) catch |err| return @errorCast(err);
lib/std/crypto/ecdsa.zig+4-4
......@@ -135,8 +135,8 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
135135 /// The maximum length of the DER encoding is der_encoded_length_max.
136136 /// The function returns a slice, that can be shorter than der_encoded_length_max.
137137 pub fn toDer(sig: Signature, buf: *[der_encoded_length_max]u8) []u8 {
138 var fb = io.fixedBufferStream(buf);
139 const w = fb.writer();
138 var w: std.io.BufferedWriter = undefined;
139 w.initFixed(buf);
140140 const r_len = @as(u8, @intCast(sig.r.len + (sig.r[0] >> 7)));
141141 const s_len = @as(u8, @intCast(sig.s.len + (sig.s[0] >> 7)));
142142 const seq_len = @as(u8, @intCast(2 + r_len + 2 + s_len));
......@@ -151,7 +151,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
151151 w.writeByte(0x00) catch unreachable;
152152 }
153153 w.writeAll(&sig.s) catch unreachable;
154 return fb.getWritten();
154 return w.getWritten();
155155 }
156156
157157 // Read a DER-encoded integer.
......@@ -176,7 +176,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
176176 /// Returns InvalidEncoding if the DER encoding is invalid.
177177 pub fn fromDer(der: []const u8) EncodingError!Signature {
178178 var sig: Signature = mem.zeroInit(Signature, .{});
179 var fb = io.fixedBufferStream(der);
179 var fb: std.io.FixedBufferStream = .{ .buffer = der };
180180 const reader = fb.reader();
181181 var buf: [2]u8 = undefined;
182182 _ = reader.readNoEof(&buf) catch return error.InvalidEncoding;
lib/std/fs/File.zig+109-107
......@@ -1594,122 +1594,124 @@ pub fn reader(file: File) Reader {
15941594
15951595pub fn writer(file: File) std.io.Writer {
15961596 return .{
1597 .context = interface.handleToOpaque(file.handle),
1597 .context = handleToOpaque(file.handle),
15981598 .vtable = &.{
1599 .writeSplat = interface.writeSplat,
1600 .writeFile = interface.writeFile,
1599 .writeSplat = writer_writeSplat,
1600 .writeFile = writer_writeFile,
16011601 },
16021602 };
16031603}
16041604
1605const interface = struct {
1606 /// Number of slices to store on the stack, when trying to send as many byte
1607 /// vectors through the underlying write calls as possible.
1608 const max_buffers_len = 16;
1609
1610 fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1611 const file = opaqueToHandle(context);
1612 var splat_buffer: [256]u8 = undefined;
1613 if (is_windows) {
1614 if (data.len == 1 and splat == 0) return 0;
1615 return windows.WriteFile(file, data[0], null);
1616 }
1617 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
1618 var len: usize = @min(iovecs.len, data.len);
1619 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
1620 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
1621 .len = d.len,
1622 };
1623 switch (splat) {
1624 0 => return std.posix.writev(file, iovecs[0 .. len - 1]),
1625 1 => return std.posix.writev(file, iovecs[0..len]),
1626 else => {
1627 const pattern = data[data.len - 1];
1628 if (pattern.len == 1) {
1629 const memset_len = @min(splat_buffer.len, splat);
1630 const buf = splat_buffer[0..memset_len];
1631 @memset(buf, pattern[0]);
1632 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
1633 var remaining_splat = splat - buf.len;
1634 while (remaining_splat > 0 and len < iovecs.len) {
1635 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
1636 remaining_splat -= splat_buffer.len;
1637 len += 1;
1638 }
1639 return std.posix.writev(file, iovecs[0..len]);
1640 }
1641 },
1642 }
1643 return std.posix.writev(file, iovecs[0..len]);
1644 }
1645
1646 fn writeFile(
1647 context: *anyopaque,
1648 in_file: std.fs.File,
1649 in_offset: u64,
1650 in_len: std.io.Writer.VTable.FileLen,
1651 headers_and_trailers: []const []const u8,
1652 headers_len: usize,
1653 ) anyerror!usize {
1654 const out_fd = opaqueToHandle(context);
1655 const in_fd = in_file.handle;
1656 const len_int = switch (in_len) {
1657 .zero => return interface.writeSplat(context, headers_and_trailers, 1),
1658 .entire_file => 0,
1659 else => in_len.int(),
1660 };
1661 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
1662 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, headers_and_trailers.len)];
1663 for (iovecs, headers_and_trailers[0..iovecs.len]) |*v, d| v.* = .{ .base = d.ptr, .len = d.len };
1664 const headers = iovecs[0..@min(headers_len, iovecs.len)];
1665 const trailers = iovecs[headers.len..];
1666 const flags = 0;
1667 return posix.sendfile(out_fd, in_fd, in_offset, len_int, headers, trailers, flags) catch |err| switch (err) {
1668 error.Unseekable,
1669 error.FastOpenAlreadyInProgress,
1670 error.MessageTooBig,
1671 error.FileDescriptorNotASocket,
1672 error.NetworkUnreachable,
1673 error.NetworkSubsystemFailed,
1674 => return writeFileUnseekable(out_fd, in_fd, in_offset, in_len, headers_and_trailers, headers_len),
1605/// Number of slices to store on the stack, when trying to send as many byte
1606/// vectors through the underlying write calls as possible.
1607const max_buffers_len = 16;
16751608
1676 else => |e| return e,
1677 };
1609pub fn writer_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1610 const file = opaqueToHandle(context);
1611 var splat_buffer: [256]u8 = undefined;
1612 if (is_windows) {
1613 if (data.len == 1 and splat == 0) return 0;
1614 return windows.WriteFile(file, data[0], null);
1615 }
1616 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
1617 var len: usize = @min(iovecs.len, data.len);
1618 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
1619 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
1620 .len = d.len,
1621 };
1622 switch (splat) {
1623 0 => return std.posix.writev(file, iovecs[0 .. len - 1]),
1624 1 => return std.posix.writev(file, iovecs[0..len]),
1625 else => {
1626 const pattern = data[data.len - 1];
1627 if (pattern.len == 1) {
1628 const memset_len = @min(splat_buffer.len, splat);
1629 const buf = splat_buffer[0..memset_len];
1630 @memset(buf, pattern[0]);
1631 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
1632 var remaining_splat = splat - buf.len;
1633 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
1634 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
1635 remaining_splat -= splat_buffer.len;
1636 len += 1;
1637 }
1638 if (remaining_splat > 0 and len < iovecs.len) {
1639 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
1640 len += 1;
1641 }
1642 return std.posix.writev(file, iovecs[0..len]);
1643 }
1644 },
16781645 }
1646 return std.posix.writev(file, iovecs[0..len]);
1647}
16791648
1680 fn writeFileUnseekable(
1681 out_fd: Handle,
1682 in_fd: Handle,
1683 in_offset: u64,
1684 in_len: std.io.Writer.VTable.FileLen,
1685 headers_and_trailers: []const []const u8,
1686 headers_len: usize,
1687 ) anyerror!usize {
1688 _ = out_fd;
1689 _ = in_fd;
1690 _ = in_offset;
1691 _ = in_len;
1692 _ = headers_and_trailers;
1693 _ = headers_len;
1694 @panic("TODO writeFileUnseekable");
1695 }
1696
1697 fn handleToOpaque(handle: File.Handle) *anyopaque {
1698 return switch (@typeInfo(Handle)) {
1699 .pointer => @ptrCast(handle),
1700 .int => @ptrFromInt(@as(u32, @bitCast(handle))),
1701 else => @compileError("unhandled"),
1702 };
1703 }
1649pub fn writer_writeFile(
1650 context: *anyopaque,
1651 in_file: std.fs.File,
1652 in_offset: u64,
1653 in_len: std.io.Writer.VTable.FileLen,
1654 headers_and_trailers: []const []const u8,
1655 headers_len: usize,
1656) anyerror!usize {
1657 const out_fd = opaqueToHandle(context);
1658 const in_fd = in_file.handle;
1659 const len_int = switch (in_len) {
1660 .zero => return writer_writeSplat(context, headers_and_trailers, 1),
1661 .entire_file => 0,
1662 else => in_len.int(),
1663 };
1664 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
1665 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, headers_and_trailers.len)];
1666 for (iovecs, headers_and_trailers[0..iovecs.len]) |*v, d| v.* = .{ .base = d.ptr, .len = d.len };
1667 const headers = iovecs[0..@min(headers_len, iovecs.len)];
1668 const trailers = iovecs[headers.len..];
1669 const flags = 0;
1670 return posix.sendfile(out_fd, in_fd, in_offset, len_int, headers, trailers, flags) catch |err| switch (err) {
1671 error.Unseekable,
1672 error.FastOpenAlreadyInProgress,
1673 error.MessageTooBig,
1674 error.FileDescriptorNotASocket,
1675 error.NetworkUnreachable,
1676 error.NetworkSubsystemFailed,
1677 => return writeFileUnseekable(out_fd, in_fd, in_offset, in_len, headers_and_trailers, headers_len),
17041678
1705 fn opaqueToHandle(userdata: *anyopaque) Handle {
1706 return switch (@typeInfo(Handle)) {
1707 .pointer => @ptrCast(userdata),
1708 .int => @intCast(@intFromPtr(userdata)),
1709 else => @compileError("unhandled"),
1710 };
1711 }
1712};
1679 else => |e| return e,
1680 };
1681}
1682
1683fn writeFileUnseekable(
1684 out_fd: Handle,
1685 in_fd: Handle,
1686 in_offset: u64,
1687 in_len: std.io.Writer.VTable.FileLen,
1688 headers_and_trailers: []const []const u8,
1689 headers_len: usize,
1690) anyerror!usize {
1691 _ = out_fd;
1692 _ = in_fd;
1693 _ = in_offset;
1694 _ = in_len;
1695 _ = headers_and_trailers;
1696 _ = headers_len;
1697 @panic("TODO writeFileUnseekable");
1698}
1699
1700fn handleToOpaque(handle: Handle) *anyopaque {
1701 return switch (@typeInfo(Handle)) {
1702 .pointer => @ptrCast(handle),
1703 .int => @ptrFromInt(@as(u32, @bitCast(handle))),
1704 else => @compileError("unhandled"),
1705 };
1706}
1707
1708fn opaqueToHandle(userdata: *anyopaque) Handle {
1709 return switch (@typeInfo(Handle)) {
1710 .pointer => @ptrCast(userdata),
1711 .int => @intCast(@intFromPtr(userdata)),
1712 else => @compileError("unhandled"),
1713 };
1714}
17131715
17141716pub const SeekableStream = io.SeekableStream(
17151717 File,
lib/std/http/Client.zig+177-153
......@@ -187,6 +187,9 @@ pub const ConnectionPool = struct {
187187/// An interface to either a plain or TLS connection.
188188pub const Connection = struct {
189189 stream: net.Stream,
190 /// Populated when protocol is TLS; this is the writer given to the TLS
191 /// client, which writes directly to `stream`, unbuffered.
192 stream_writer: std.io.BufferedWriter,
190193 /// undefined unless protocol is tls.
191194 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
192195
......@@ -210,9 +213,10 @@ pub const Connection = struct {
210213
211214 read_start: BufferSize = 0,
212215 read_end: BufferSize = 0,
213 write_end: BufferSize = 0,
214 read_buf: [buffer_size]u8 = undefined,
215 write_buf: [buffer_size]u8 = undefined,
216 read_buf: [buffer_size]u8,
217
218 write_buffer: [buffer_size]u8,
219 writer: std.io.BufferedWriter,
216220
217221 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
218222 const BufferSize = std.math.IntFittingRange(0, buffer_size);
......@@ -314,59 +318,7 @@ pub const Connection = struct {
314318 pub const Reader = std.io.Reader(*Connection, ReadError, read);
315319
316320 pub fn reader(conn: *Connection) Reader {
317 return Reader{ .context = conn };
318 }
319
320 pub fn writeAllDirectTls(conn: *Connection, buffer: []const u8) WriteError!void {
321 return conn.tls_client.writeAll(conn.stream, buffer) catch |err| switch (err) {
322 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
323 else => return error.UnexpectedWriteFailure,
324 };
325 }
326
327 pub fn writeAllDirect(conn: *Connection, buffer: []const u8) WriteError!void {
328 if (conn.protocol == .tls) {
329 if (disable_tls) unreachable;
330
331 return conn.writeAllDirectTls(buffer);
332 }
333
334 return conn.stream.writeAll(buffer) catch |err| switch (err) {
335 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
336 else => return error.UnexpectedWriteFailure,
337 };
338 }
339
340 /// Writes the given buffer to the connection.
341 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
342 if (conn.write_buf.len - conn.write_end < buffer.len) {
343 try conn.flush();
344
345 if (buffer.len > conn.write_buf.len) {
346 try conn.writeAllDirect(buffer);
347 return buffer.len;
348 }
349 }
350
351 @memcpy(conn.write_buf[conn.write_end..][0..buffer.len], buffer);
352 conn.write_end += @intCast(buffer.len);
353
354 return buffer.len;
355 }
356
357 /// Returns a buffer to be filled with exactly len bytes to write to the connection.
358 pub fn allocWriteBuffer(conn: *Connection, len: BufferSize) WriteError![]u8 {
359 if (conn.write_buf.len - conn.write_end < len) try conn.flush();
360 defer conn.write_end += len;
361 return conn.write_buf[conn.write_end..][0..len];
362 }
363
364 /// Flushes the write buffer to the connection.
365 pub fn flush(conn: *Connection) WriteError!void {
366 if (conn.write_end == 0) return;
367
368 try conn.writeAllDirect(conn.write_buf[0..conn.write_end]);
369 conn.write_end = 0;
321 return .{ .context = conn };
370322 }
371323
372324 pub const WriteError = error{
......@@ -374,19 +326,12 @@ pub const Connection = struct {
374326 UnexpectedWriteFailure,
375327 };
376328
377 pub const Writer = std.io.Writer(*Connection, WriteError, write);
378
379 pub fn writer(conn: *Connection) Writer {
380 return Writer{ .context = conn };
381 }
382
383 /// Closes the connection.
384329 pub fn close(conn: *Connection, allocator: Allocator) void {
385330 if (conn.protocol == .tls) {
386331 if (disable_tls) unreachable;
387332
388333 // try to cleanly close the TLS connection, for any server that cares.
389 _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {};
334 _ = conn.tls_client.writeEnd("", true) catch {};
390335 if (conn.tls_client.ssl_key_log) |key_log| key_log.file.close();
391336 allocator.destroy(conn.tls_client);
392337 }
......@@ -815,15 +760,12 @@ pub const Request = struct {
815760 };
816761 }
817762
818 pub const SendError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
819
820763 /// Send the HTTP request headers to the server.
821 pub fn send(req: *Request) SendError!void {
822 if (!req.method.requestHasBody() and req.transfer_encoding != .none)
823 return error.UnsupportedTransferEncoding;
764 pub fn send(req: *Request) anyerror!void {
765 assert(req.transfer_encoding == .none or req.method.requestHasBody());
824766
825767 const connection = req.connection.?;
826 const w = connection.writer();
768 const w = &connection.writer;
827769
828770 try req.method.write(w);
829771 try w.writeByte(' ');
......@@ -852,10 +794,7 @@ pub const Request = struct {
852794 if (try emitOverridableHeader("authorization: ", req.headers.authorization, w)) {
853795 if (req.uri.user != null or req.uri.password != null) {
854796 try w.writeAll("authorization: ");
855 const authorization = try connection.allocWriteBuffer(
856 @intCast(basic_authorization.valueLengthFromUri(req.uri)),
857 );
858 assert(basic_authorization.value(req.uri, authorization).len == authorization.len);
797 try basic_authorization.write(req.uri, w);
859798 try w.writeAll("\r\n");
860799 }
861800 }
......@@ -914,7 +853,7 @@ pub const Request = struct {
914853
915854 try w.writeAll("\r\n");
916855
917 try connection.flush();
856 try connection.writer.flush();
918857 }
919858
920859 /// Returns true if the default behavior is required, otherwise handles
......@@ -953,7 +892,9 @@ pub const Request = struct {
953892 return index;
954893 }
955894
956 pub const WaitError = RequestError || SendError || TransferReadError ||
895 /// TODO collapse each error set into its own meta error code, and store
896 /// the underlying error code as a field on Request
897 pub const WaitError = RequestError || anyerror || TransferReadError ||
957898 proto.HeadersParser.CheckCompleteHeadError || Response.ParseError ||
958899 error{
959900 TooManyHttpRedirects,
......@@ -1132,59 +1073,112 @@ pub const Request = struct {
11321073 return index;
11331074 }
11341075
1135 pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
1136
1137 pub const Writer = std.io.Writer(*Request, WriteError, write);
1138
1139 pub fn writer(req: *Request) Writer {
1140 return .{ .context = req };
1076 /// Resulting `std.io.Writer` must used after `send` and before `finish`.
1077 pub fn writer(req: *Request) std.io.Writer {
1078 return .{
1079 .context = req,
1080 .vtable = switch (req.transfer_encoding) {
1081 .chunked => &.{
1082 .writeSplat = chunked_writeSplat,
1083 .writeFile = chunked_writeFile,
1084 },
1085 .content_length => &.{
1086 .writeSplat = cl_writeSplat,
1087 .writeFile = cl_writeFile,
1088 },
1089 .none => unreachable,
1090 },
1091 };
11411092 }
11421093
1143 /// Write `bytes` to the server. The `transfer_encoding` field determines how data will be sent.
1144 /// Must be called after `send` and before `finish`.
1145 pub fn write(req: *Request, bytes: []const u8) WriteError!usize {
1146 switch (req.transfer_encoding) {
1147 .chunked => {
1148 if (bytes.len > 0) {
1149 try req.connection.?.writer().print("{x}\r\n", .{bytes.len});
1150 try req.connection.?.writer().writeAll(bytes);
1151 try req.connection.?.writer().writeAll("\r\n");
1152 }
1153
1154 return bytes.len;
1155 },
1156 .content_length => |*len| {
1157 if (len.* < bytes.len) return error.MessageTooLong;
1094 fn chunked_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1095 const req: *Request = @ptrCast(@alignCast(context));
1096 var total: usize = 0;
1097 for (data) |bytes| total += bytes.len;
1098 if (total == 0) return 0;
1099 var iovecs: [max_buffers_len][]const u8 = undefined;
1100 var header_buffer: [30]u8 = undefined;
1101 var header_buffer_writer: std.io.BufferedWriter = undefined;
1102 header_buffer_writer.initFixed(&header_buffer);
1103 header_buffer_writer.print("{x}\r\n", .{total}) catch unreachable;
1104 iovecs[0] = header_buffer_writer.getWritten();
1105 @memcpy(iovecs[1..][0..data.len], data);
1106 iovecs[data.len + 1] = "\r\n";
1107 // TODO: only 1 underlying write call
1108 // TODO: don't rely on max_buffers_len exceeding the caller
1109 // TODO: handle splat
1110 _ = splat;
1111 const w = &req.connection.?.writer;
1112 try w.writevAll(iovecs[0 .. data.len + 2]);
1113 return total;
1114 }
11581115
1159 const amt = try req.connection.?.write(bytes);
1160 len.* -= amt;
1161 return amt;
1162 },
1163 .none => return error.NotWriteable,
1164 }
1116 const max_buffers_len = 16;
1117
1118 pub fn chunked_writeFile(
1119 context: *anyopaque,
1120 file: std.fs.File,
1121 offset: u64,
1122 len: std.io.Writer.VTable.FileLen,
1123 headers_and_trailers: []const []const u8,
1124 headers_len: usize,
1125 ) anyerror!usize {
1126 if (len == .entire_file) return error.Unimplemented;
1127 const req: *Request = @ptrCast(@alignCast(context));
1128 var total: usize = len.int();
1129 for (headers_and_trailers) |bytes| total += bytes.len;
1130 if (total == 0) return 0;
1131 var iovecs: [max_buffers_len][]const u8 = undefined;
1132 var header_buffer: [30]u8 = undefined;
1133 var header_buffer_writer: std.io.BufferedWriter = undefined;
1134 header_buffer_writer.initFixed(&header_buffer);
1135 header_buffer_writer.print("{x}\r\n", .{total}) catch unreachable;
1136 iovecs[0] = header_buffer_writer.getWritten();
1137 @memcpy(iovecs[1..][0..headers_and_trailers.len], headers_and_trailers);
1138 iovecs[headers_and_trailers.len + 1] = "\r\n";
1139 // TODO: only 1 underlying write call
1140 // TODO: don't rely on max_buffers_len exceeding the caller
1141 const w = &req.connection.?.writer;
1142 try w.writeFileAll(file, .{
1143 .offset = offset,
1144 .len = len,
1145 .headers_and_trailers = iovecs[0 .. headers_and_trailers.len + 2],
1146 .headers_len = headers_len + 1,
1147 });
1148 return total;
11651149 }
11661150
1167 /// Write `bytes` to the server. The `transfer_encoding` field determines how data will be sent.
1168 /// Must be called after `send` and before `finish`.
1169 pub fn writeAll(req: *Request, bytes: []const u8) WriteError!void {
1170 var index: usize = 0;
1171 while (index < bytes.len) {
1172 index += try write(req, bytes[index..]);
1173 }
1151 fn cl_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1152 const req: *Request = @ptrCast(@alignCast(context));
1153 const n = try req.connection.?.writer.writeSplat(data, splat);
1154 req.transfer_encoding.content_length -= n;
1155 return n;
11741156 }
11751157
1176 pub const FinishError = WriteError || error{MessageNotCompleted};
1158 pub fn cl_writeFile(
1159 context: *anyopaque,
1160 file: std.fs.File,
1161 offset: u64,
1162 len: std.io.Writer.VTable.FileLen,
1163 headers_and_trailers: []const []const u8,
1164 headers_len: usize,
1165 ) anyerror!usize {
1166 const req: *Request = @ptrCast(@alignCast(context));
1167 const n = try req.connection.?.writer.writeFile(file, offset, len, headers_and_trailers, headers_len);
1168 req.transfer_encoding.content_length -= n;
1169 return n;
1170 }
11771171
11781172 /// Finish the body of a request. This notifies the server that you have no more data to send.
11791173 /// Must be called after `send`.
1180 pub fn finish(req: *Request) FinishError!void {
1174 pub fn finish(req: *Request) anyerror!void {
11811175 switch (req.transfer_encoding) {
1182 .chunked => try req.connection.?.writer().writeAll("0\r\n\r\n"),
1183 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
1176 .chunked => try req.connection.?.writer.writeAll("0\r\n\r\n"),
1177 .content_length => |len| assert(len == 0),
11841178 .none => {},
11851179 }
11861180
1187 try req.connection.?.flush();
1181 try req.connection.?.writer.flush();
11881182 }
11891183};
11901184
......@@ -1276,10 +1270,8 @@ pub const basic_authorization = struct {
12761270 pub const max_password_len = 255;
12771271 pub const max_value_len = valueLength(max_user_len, max_password_len);
12781272
1279 const prefix = "Basic ";
1280
12811273 pub fn valueLength(user_len: usize, password_len: usize) usize {
1282 return prefix.len + std.base64.standard.Encoder.calcSize(user_len + 1 + password_len);
1274 return "Basic ".len + std.base64.standard.Encoder.calcSize(user_len + 1 + password_len);
12831275 }
12841276
12851277 pub fn valueLengthFromUri(uri: Uri) usize {
......@@ -1290,6 +1282,13 @@ pub const basic_authorization = struct {
12901282 }
12911283
12921284 pub fn value(uri: Uri, out: []u8) []u8 {
1285 var bw: std.io.BufferedWriter = undefined;
1286 bw.initFixed(out);
1287 write(uri, &bw) catch unreachable;
1288 return bw.getWritten();
1289 }
1290
1291 pub fn write(uri: Uri, out: *std.io.BufferedWriter) anyerror!void {
12931292 var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;
12941293 var bw: std.io.BufferedWriter = undefined;
12951294 bw.initFixed(&buf);
......@@ -1297,9 +1296,7 @@ pub const basic_authorization = struct {
12971296 uri.user orelse Uri.Component.empty,
12981297 uri.password orelse Uri.Component.empty,
12991298 }) catch unreachable;
1300 @memcpy(out[0..prefix.len], prefix);
1301 const base64 = std.base64.standard.Encoder.encode(out[prefix.len..], bw.getWritten());
1302 return out[0 .. prefix.len + base64.len];
1299 try out.print("Basic {b64}", .{bw.getWritten()});
13031300 }
13041301};
13051302
......@@ -1313,7 +1310,7 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
13131310 .host = host,
13141311 .port = port,
13151312 .protocol = protocol,
1316 })) |node| return node;
1313 })) |conn| return conn;
13171314
13181315 if (disable_tls and protocol == .tls)
13191316 return error.TlsInitializationFailed;
......@@ -1336,7 +1333,12 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
13361333
13371334 conn.* = .{
13381335 .stream = stream,
1336 .stream_writer = undefined,
13391337 .tls_client = undefined,
1338 .read_buf = undefined,
1339
1340 .write_buffer = undefined,
1341 .writer = undefined, // populated below
13401342
13411343 .protocol = protocol,
13421344 .host = try client.allocator.dupe(u8, host),
......@@ -1346,36 +1348,55 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
13461348 };
13471349 errdefer client.allocator.free(conn.host);
13481350
1349 if (protocol == .tls) {
1350 if (disable_tls) unreachable;
1351 switch (protocol) {
1352 .tls => {
1353 if (disable_tls) unreachable;
13511354
1352 conn.tls_client = try client.allocator.create(std.crypto.tls.Client);
1353 errdefer client.allocator.destroy(conn.tls_client);
1355 const tls_client = try client.allocator.create(std.crypto.tls.Client);
1356 errdefer client.allocator.destroy(tls_client);
1357
1358 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {
1359 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {
1360 error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :ssl_key_log_file null,
1361 error.OutOfMemory => return error.OutOfMemory,
1362 };
1363 defer client.allocator.free(ssl_key_log_path);
1364 break :ssl_key_log_file std.fs.cwd().createFile(ssl_key_log_path, .{
1365 .truncate = false,
1366 .mode = switch (builtin.os.tag) {
1367 .windows, .wasi => 0,
1368 else => 0o600,
1369 },
1370 }) catch null;
1371 } else null;
1372 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();
1373
1374 conn.stream_writer = .{
1375 .unbuffered_writer = stream.writer(),
1376 .buffer = &.{},
1377 };
13541378
1355 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {
1356 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {
1357 error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :ssl_key_log_file null,
1358 error.OutOfMemory => return error.OutOfMemory,
1379 tls_client.* = std.crypto.tls.Client.init(stream, &conn.stream_writer, .{
1380 .host = .{ .explicit = host },
1381 .ca = .{ .bundle = client.ca_bundle },
1382 .ssl_key_log_file = ssl_key_log_file,
1383 }) catch return error.TlsInitializationFailed;
1384 // This is appropriate for HTTPS because the HTTP headers contain
1385 // the content length which is used to detect truncation attacks.
1386 tls_client.allow_truncation_attacks = true;
1387
1388 conn.writer = .{
1389 .unbuffered_writer = tls_client.writer(),
1390 .buffer = &conn.write_buffer,
13591391 };
1360 defer client.allocator.free(ssl_key_log_path);
1361 break :ssl_key_log_file std.fs.cwd().createFile(ssl_key_log_path, .{
1362 .truncate = false,
1363 .mode = switch (builtin.os.tag) {
1364 .windows, .wasi => 0,
1365 else => 0o600,
1366 },
1367 }) catch null;
1368 } else null;
1369 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();
1370
1371 conn.tls_client.* = std.crypto.tls.Client.init(stream, .{
1372 .host = .{ .explicit = host },
1373 .ca = .{ .bundle = client.ca_bundle },
1374 .ssl_key_log_file = ssl_key_log_file,
1375 }) catch return error.TlsInitializationFailed;
1376 // This is appropriate for HTTPS because the HTTP headers contain
1377 // the content length which is used to detect truncation attacks.
1378 conn.tls_client.allow_truncation_attacks = true;
1392 conn.tls_client = tls_client;
1393 },
1394 .plain => {
1395 conn.writer = .{
1396 .unbuffered_writer = stream.writer(),
1397 .buffer = &conn.write_buffer,
1398 };
1399 },
13791400 }
13801401
13811402 client.connection_pool.addUsed(conn);
......@@ -1534,14 +1555,14 @@ pub fn connect(
15341555 return conn;
15351556}
15361557
1537pub const RequestError = ConnectTcpError || ConnectErrorPartial || Request.SendError ||
1558/// TODO collapse each error set into its own meta error code, and store
1559/// the underlying error code as a field on Request
1560pub const RequestError = ConnectTcpError || ConnectErrorPartial || anyerror ||
15381561 std.fmt.ParseIntError || Connection.WriteError ||
15391562 error{
15401563 UnsupportedUriScheme,
15411564 UriMissingHost,
1542
15431565 CertificateBundleLoadFailure,
1544 UnsupportedTransferEncoding,
15451566 };
15461567
15471568pub const RequestOptions = struct {
......@@ -1754,7 +1775,10 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {
17541775
17551776 try req.send();
17561777
1757 if (options.payload) |payload| try req.writeAll(payload);
1778 if (options.payload) |payload| {
1779 var w = req.writer().unbuffered();
1780 try w.writeAll(payload);
1781 }
17581782
17591783 try req.finish();
17601784 try req.wait();
lib/std/http/Server.zig+70-138
......@@ -113,6 +113,7 @@ fn finishReceivingHead(s: *Server, head_end: usize) ReceiveHeadError!Request {
113113 .head = Request.Head.parse(s.read_buffer[0..head_end]) catch
114114 return error.HttpHeadersInvalid,
115115 .reader_state = undefined,
116 .write_error = undefined,
116117 };
117118}
118119
......@@ -125,6 +126,8 @@ pub const Request = struct {
125126 remaining_content_length: u64,
126127 chunk_parser: http.ChunkParser,
127128 },
129 /// Populated when `error.HttpContinueWriteFailed` is received.
130 write_error: anyerror,
128131
129132 pub const Compression = union(enum) {
130133 pub const DeflateDecompressor = std.compress.zlib.Decompressor(std.io.AnyReader);
......@@ -327,6 +330,7 @@ pub const Request = struct {
327330 .head_end = request_bytes.len,
328331 .head = undefined,
329332 .reader_state = undefined,
333 .write_error = undefined,
330334 };
331335
332336 var it = request.iterateHeaders();
......@@ -391,7 +395,7 @@ pub const Request = struct {
391395 request: *Request,
392396 content: []const u8,
393397 options: RespondOptions,
394 ) Response.WriteError!void {
398 ) anyerror!void {
395399 const max_extra_headers = 25;
396400 assert(options.status != .@"continue");
397401 assert(options.extra_headers.len <= max_extra_headers);
......@@ -418,7 +422,8 @@ pub const Request = struct {
418422 h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");
419423 if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n");
420424 h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");
421 try request.server.connection.stream.writeAll(h.items);
425 var w = request.server.connection.stream.writer().unbuffered();
426 try w.writeAll(h.items);
422427 return;
423428 }
424429 h.printAssumeCapacity("{s} {d} {s}\r\n", .{
......@@ -438,47 +443,29 @@ pub const Request = struct {
438443 }
439444
440445 var chunk_header_buffer: [18]u8 = undefined;
441 var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;
446 var iovecs: [max_extra_headers * 4 + 3][]const u8 = undefined;
442447 var iovecs_len: usize = 0;
443448
444 iovecs[iovecs_len] = .{
445 .base = h.items.ptr,
446 .len = h.items.len,
447 };
449 iovecs[iovecs_len] = h.items;
448450 iovecs_len += 1;
449451
450452 for (options.extra_headers) |header| {
451 iovecs[iovecs_len] = .{
452 .base = header.name.ptr,
453 .len = header.name.len,
454 };
453 iovecs[iovecs_len] = header.name;
455454 iovecs_len += 1;
456455
457 iovecs[iovecs_len] = .{
458 .base = ": ",
459 .len = 2,
460 };
456 iovecs[iovecs_len] = ": ";
461457 iovecs_len += 1;
462458
463459 if (header.value.len != 0) {
464 iovecs[iovecs_len] = .{
465 .base = header.value.ptr,
466 .len = header.value.len,
467 };
460 iovecs[iovecs_len] = header.value;
468461 iovecs_len += 1;
469462 }
470463
471 iovecs[iovecs_len] = .{
472 .base = "\r\n",
473 .len = 2,
474 };
464 iovecs[iovecs_len] = "\r\n";
475465 iovecs_len += 1;
476466 }
477467
478 iovecs[iovecs_len] = .{
479 .base = "\r\n",
480 .len = 2,
481 };
468 iovecs[iovecs_len] = "\r\n";
482469 iovecs_len += 1;
483470
484471 if (request.head.method != .HEAD) {
......@@ -491,40 +478,26 @@ pub const Request = struct {
491478 .{content.len},
492479 ) catch unreachable;
493480
494 iovecs[iovecs_len] = .{
495 .base = chunk_header.ptr,
496 .len = chunk_header.len,
497 };
481 iovecs[iovecs_len] = chunk_header;
498482 iovecs_len += 1;
499483
500 iovecs[iovecs_len] = .{
501 .base = content.ptr,
502 .len = content.len,
503 };
484 iovecs[iovecs_len] = content;
504485 iovecs_len += 1;
505486
506 iovecs[iovecs_len] = .{
507 .base = "\r\n",
508 .len = 2,
509 };
487 iovecs[iovecs_len] = "\r\n";
510488 iovecs_len += 1;
511489 }
512490
513 iovecs[iovecs_len] = .{
514 .base = "0\r\n\r\n",
515 .len = 5,
516 };
491 iovecs[iovecs_len] = "0\r\n\r\n";
517492 iovecs_len += 1;
518493 } else if (content.len > 0) {
519 iovecs[iovecs_len] = .{
520 .base = content.ptr,
521 .len = content.len,
522 };
494 iovecs[iovecs_len] = content;
523495 iovecs_len += 1;
524496 }
525497 }
526498
527 try request.server.connection.stream.writevAll(iovecs[0..iovecs_len]);
499 var w = request.server.connection.stream.writer().unbuffered();
500 try w.writevAll(iovecs[0..iovecs_len]);
528501 }
529502
530503 pub const RespondStreamingOptions = struct {
......@@ -740,7 +713,10 @@ pub const Request = struct {
740713 return out_end;
741714 }
742715
743 pub const ReaderError = Response.WriteError || error{
716 pub const ReaderError = error{
717 /// Failed to write "100-continue" to the stream. Error value is
718 /// stored in `Request.write_error`.
719 HttpContinueWriteFailed,
744720 /// The client sent an expect HTTP header value other than
745721 /// "100-continue".
746722 HttpExpectationFailed,
......@@ -760,7 +736,11 @@ pub const Request = struct {
760736
761737 if (request.head.expect) |expect| {
762738 if (mem.eql(u8, expect, "100-continue")) {
763 try request.server.connection.stream.writeAll("HTTP/1.1 100 Continue\r\n\r\n");
739 var w = request.server.connection.stream.writer().unbuffered();
740 w.writeAll("HTTP/1.1 100 Continue\r\n\r\n") catch |err| {
741 request.write_error = err;
742 return error.HttpContinueWriteFailed;
743 };
764744 request.head.expect = null;
765745 } else {
766746 return error.HttpExpectationFailed;
......@@ -845,14 +825,12 @@ pub const Response = struct {
845825 chunked,
846826 };
847827
848 pub const WriteError = net.Stream.WriteError;
849
850828 /// When using content-length, asserts that the amount of data sent matches
851829 /// the value sent in the header, then calls `flush`.
852830 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
853831 /// end-of-stream message, then flushes the stream to the system.
854832 /// Respects the value of `elide_body` to omit all data after the headers.
855 pub fn end(r: *Response) WriteError!void {
833 pub fn end(r: *Response) anyerror!void {
856834 switch (r.transfer_encoding) {
857835 .content_length => |len| {
858836 assert(len == 0); // Trips when end() called before all bytes written.
......@@ -877,7 +855,7 @@ pub const Response = struct {
877855 /// flushes the stream to the system.
878856 /// Respects the value of `elide_body` to omit all data after the headers.
879857 /// Asserts there are at most 25 trailers.
880 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {
858 pub fn endChunked(r: *Response, options: EndChunkedOptions) anyerror!void {
881859 assert(r.transfer_encoding == .chunked);
882860 try flush_chunked(r, options.trailers);
883861 r.* = undefined;
......@@ -887,7 +865,7 @@ pub const Response = struct {
887865 /// would not exceed the content-length value sent in the HTTP header.
888866 /// May return 0, which does not indicate end of stream. The caller decides
889867 /// when the end of stream occurs by calling `end`.
890 pub fn write(r: *Response, bytes: []const u8) WriteError!usize {
868 pub fn write(r: *Response, bytes: []const u8) anyerror!usize {
891869 switch (r.transfer_encoding) {
892870 .content_length, .none => return @errorCast(cl_writeSplat(r, &.{bytes}, 1)),
893871 .chunked => return @errorCast(chunked_writeSplat(r, &.{bytes}, 1)),
......@@ -916,7 +894,7 @@ pub const Response = struct {
916894 return error.Unimplemented;
917895 }
918896
919 fn cl_write(context: *anyopaque, bytes: []const u8) WriteError!usize {
897 fn cl_write(context: *anyopaque, bytes: []const u8) anyerror!usize {
920898 const r: *Response = @constCast(@alignCast(@ptrCast(context)));
921899
922900 var trash: u64 = std.math.maxInt(u64);
......@@ -932,17 +910,12 @@ pub const Response = struct {
932910
933911 if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
934912 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
935 var iovecs: [2]std.posix.iovec_const = .{
936 .{
937 .base = r.send_buffer.ptr + r.send_buffer_start,
938 .len = send_buffer_len,
939 },
940 .{
941 .base = bytes.ptr,
942 .len = bytes.len,
943 },
913 var iovecs: [2][]const u8 = .{
914 r.send_buffer[r.send_buffer_start..][0..send_buffer_len],
915 bytes,
944916 };
945 const n = try r.stream.writev(&iovecs);
917 var w = r.stream.writer().unbuffered();
918 const n = try w.writev(&iovecs);
946919
947920 if (n >= send_buffer_len) {
948921 // It was enough to reset the buffer.
......@@ -985,10 +958,10 @@ pub const Response = struct {
985958 _ = len;
986959 _ = headers_and_trailers;
987960 _ = headers_len;
988 return error.Unimplemented;
961 return error.Unimplemented; // TODO lower to a call to writeFile on the output
989962 }
990963
991 fn chunked_write(context: *anyopaque, bytes: []const u8) WriteError!usize {
964 fn chunked_write(context: *anyopaque, bytes: []const u8) anyerror!usize {
992965 const r: *Response = @constCast(@alignCast(@ptrCast(context)));
993966 assert(r.transfer_encoding == .chunked);
994967
......@@ -1001,31 +974,17 @@ pub const Response = struct {
1001974 var header_buf: [18]u8 = undefined;
1002975 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{chunk_len}) catch unreachable;
1003976
1004 var iovecs: [5]std.posix.iovec_const = .{
1005 .{
1006 .base = r.send_buffer.ptr + r.send_buffer_start,
1007 .len = send_buffer_len - r.chunk_len,
1008 },
1009 .{
1010 .base = chunk_header.ptr,
1011 .len = chunk_header.len,
1012 },
1013 .{
1014 .base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
1015 .len = r.chunk_len,
1016 },
1017 .{
1018 .base = bytes.ptr,
1019 .len = bytes.len,
1020 },
1021 .{
1022 .base = "\r\n",
1023 .len = 2,
1024 },
977 var iovecs: [5][]const u8 = .{
978 r.send_buffer[r.send_buffer_start .. send_buffer_len - r.chunk_len],
979 chunk_header,
980 r.send_buffer[r.send_buffer_end - r.chunk_len ..][0..r.chunk_len],
981 bytes,
982 "\r\n",
1025983 };
1026984 // TODO make this writev instead of writevAll, which involves
1027985 // complicating the logic of this function.
1028 try r.stream.writevAll(&iovecs);
986 var w = r.stream.writer().unbuffered();
987 try w.writevAll(&iovecs);
1029988 r.send_buffer_start = 0;
1030989 r.send_buffer_end = 0;
1031990 r.chunk_len = 0;
......@@ -1041,7 +1000,7 @@ pub const Response = struct {
10411000
10421001 /// If using content-length, asserts that writing these bytes to the client
10431002 /// would not exceed the content-length value sent in the HTTP header.
1044 pub fn writeAll(r: *Response, bytes: []const u8) WriteError!void {
1003 pub fn writeAll(r: *Response, bytes: []const u8) anyerror!void {
10451004 var index: usize = 0;
10461005 while (index < bytes.len) {
10471006 index += try write(r, bytes[index..]);
......@@ -1051,20 +1010,21 @@ pub const Response = struct {
10511010 /// Sends all buffered data to the client.
10521011 /// This is redundant after calling `end`.
10531012 /// Respects the value of `elide_body` to omit all data after the headers.
1054 pub fn flush(r: *Response) WriteError!void {
1013 pub fn flush(r: *Response) anyerror!void {
10551014 switch (r.transfer_encoding) {
10561015 .none, .content_length => return flush_cl(r),
10571016 .chunked => return flush_chunked(r, null),
10581017 }
10591018 }
10601019
1061 fn flush_cl(r: *Response) WriteError!void {
1062 try r.stream.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]);
1020 fn flush_cl(r: *Response) anyerror!void {
1021 var w = r.stream.writer().unbuffered();
1022 try w.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]);
10631023 r.send_buffer_start = 0;
10641024 r.send_buffer_end = 0;
10651025 }
10661026
1067 fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) WriteError!void {
1027 fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) anyerror!void {
10681028 const max_trailers = 25;
10691029 if (end_trailers) |trailers| assert(trailers.len <= max_trailers);
10701030 assert(r.transfer_encoding == .chunked);
......@@ -1072,7 +1032,8 @@ pub const Response = struct {
10721032 const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len];
10731033
10741034 if (r.elide_body) {
1075 try r.stream.writeAll(http_headers);
1035 var w = r.stream.writer().unbuffered();
1036 try w.writeAll(http_headers);
10761037 r.send_buffer_start = 0;
10771038 r.send_buffer_end = 0;
10781039 r.chunk_len = 0;
......@@ -1082,78 +1043,49 @@ pub const Response = struct {
10821043 var header_buf: [18]u8 = undefined;
10831044 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable;
10841045
1085 var iovecs: [max_trailers * 4 + 5]std.posix.iovec_const = undefined;
1046 var iovecs: [max_trailers * 4 + 5][]const u8 = undefined;
10861047 var iovecs_len: usize = 0;
10871048
1088 iovecs[iovecs_len] = .{
1089 .base = http_headers.ptr,
1090 .len = http_headers.len,
1091 };
1049 iovecs[iovecs_len] = http_headers;
10921050 iovecs_len += 1;
10931051
10941052 if (r.chunk_len > 0) {
1095 iovecs[iovecs_len] = .{
1096 .base = chunk_header.ptr,
1097 .len = chunk_header.len,
1098 };
1053 iovecs[iovecs_len] = chunk_header;
10991054 iovecs_len += 1;
11001055
1101 iovecs[iovecs_len] = .{
1102 .base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
1103 .len = r.chunk_len,
1104 };
1056 iovecs[iovecs_len] = r.send_buffer[r.send_buffer_end - r.chunk_len ..][0..r.chunk_len];
11051057 iovecs_len += 1;
11061058
1107 iovecs[iovecs_len] = .{
1108 .base = "\r\n",
1109 .len = 2,
1110 };
1059 iovecs[iovecs_len] = "\r\n";
11111060 iovecs_len += 1;
11121061 }
11131062
11141063 if (end_trailers) |trailers| {
1115 iovecs[iovecs_len] = .{
1116 .base = "0\r\n",
1117 .len = 3,
1118 };
1064 iovecs[iovecs_len] = "0\r\n";
11191065 iovecs_len += 1;
11201066
11211067 for (trailers) |trailer| {
1122 iovecs[iovecs_len] = .{
1123 .base = trailer.name.ptr,
1124 .len = trailer.name.len,
1125 };
1068 iovecs[iovecs_len] = trailer.name;
11261069 iovecs_len += 1;
11271070
1128 iovecs[iovecs_len] = .{
1129 .base = ": ",
1130 .len = 2,
1131 };
1071 iovecs[iovecs_len] = ": ";
11321072 iovecs_len += 1;
11331073
11341074 if (trailer.value.len != 0) {
1135 iovecs[iovecs_len] = .{
1136 .base = trailer.value.ptr,
1137 .len = trailer.value.len,
1138 };
1075 iovecs[iovecs_len] = trailer.value;
11391076 iovecs_len += 1;
11401077 }
11411078
1142 iovecs[iovecs_len] = .{
1143 .base = "\r\n",
1144 .len = 2,
1145 };
1079 iovecs[iovecs_len] = "\r\n";
11461080 iovecs_len += 1;
11471081 }
11481082
1149 iovecs[iovecs_len] = .{
1150 .base = "\r\n",
1151 .len = 2,
1152 };
1083 iovecs[iovecs_len] = "\r\n";
11531084 iovecs_len += 1;
11541085 }
11551086
1156 try r.stream.writevAll(iovecs[0..iovecs_len]);
1087 var w = r.stream.writer().unbuffered();
1088 try w.writevAll(iovecs[0..iovecs_len]);
11571089 r.send_buffer_start = 0;
11581090 r.send_buffer_end = 0;
11591091 r.chunk_len = 0;
lib/std/http/protocol.zig+5-20
......@@ -290,7 +290,7 @@ inline fn intShift(comptime T: type, x: anytype) T {
290290const MockBufferedConnection = struct {
291291 pub const buffer_size = 0x2000;
292292
293 conn: std.io.FixedBufferStream([]const u8),
293 conn: std.io.FixedBufferStream,
294294 buf: [buffer_size]u8 = undefined,
295295 start: u16 = 0,
296296 end: u16 = 0,
......@@ -343,27 +343,12 @@ const MockBufferedConnection = struct {
343343 return conn.readAtLeast(buffer, 1);
344344 }
345345
346 pub const ReadError = std.io.FixedBufferStream([]const u8).ReadError || error{EndOfStream};
346 pub const ReadError = std.io.FixedBufferStream.ReadError || error{EndOfStream};
347347 pub const Reader = std.io.Reader(*MockBufferedConnection, ReadError, read);
348348
349349 pub fn reader(conn: *MockBufferedConnection) Reader {
350350 return Reader{ .context = conn };
351351 }
352
353 pub fn writeAll(conn: *MockBufferedConnection, buffer: []const u8) WriteError!void {
354 return conn.conn.writeAll(buffer);
355 }
356
357 pub fn write(conn: *MockBufferedConnection, buffer: []const u8) WriteError!usize {
358 return conn.conn.write(buffer);
359 }
360
361 pub const WriteError = std.io.FixedBufferStream([]const u8).WriteError;
362 pub const Writer = std.io.Writer(*MockBufferedConnection, WriteError, write);
363
364 pub fn writer(conn: *MockBufferedConnection) Writer {
365 return Writer{ .context = conn };
366 }
367352};
368353
369354test "HeadersParser.read length" {
......@@ -374,7 +359,7 @@ test "HeadersParser.read length" {
374359 const data = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nHello";
375360
376361 var conn: MockBufferedConnection = .{
377 .conn = std.io.fixedBufferStream(data),
362 .conn = .{ .buffer = data },
378363 };
379364
380365 while (true) { // read headers
......@@ -404,7 +389,7 @@ test "HeadersParser.read chunked" {
404389 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\n\r\n";
405390
406391 var conn: MockBufferedConnection = .{
407 .conn = std.io.fixedBufferStream(data),
392 .conn = .{ .buffer = data },
408393 };
409394
410395 while (true) { // read headers
......@@ -433,7 +418,7 @@ test "HeadersParser.read chunked trailer" {
433418 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\nContent-Type: text/plain\r\n\r\n";
434419
435420 var conn: MockBufferedConnection = .{
436 .conn = std.io.fixedBufferStream(data),
421 .conn = .{ .buffer = data },
437422 };
438423
439424 while (true) { // read headers
lib/std/http/test.zig+18-11
......@@ -135,7 +135,8 @@ test "HTTP server handles a chunked transfer coding request" {
135135 const gpa = std.testing.allocator;
136136 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
137137 defer stream.close();
138 try stream.writeAll(request_bytes);
138 var writer = stream.writer().unbuffered();
139 try writer.writeAll(request_bytes);
139140
140141 const expected_response =
141142 "HTTP/1.1 200 OK\r\n" ++
......@@ -276,7 +277,8 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
276277 const gpa = std.testing.allocator;
277278 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
278279 defer stream.close();
279 try stream.writeAll(request_bytes);
280 var writer = stream.writer().unbuffered();
281 try writer.writeAll(request_bytes);
280282
281283 const response = try stream.reader().readAllAlloc(gpa, 8192);
282284 defer gpa.free(response);
......@@ -339,7 +341,8 @@ test "receiving arbitrary http headers from the client" {
339341 const gpa = std.testing.allocator;
340342 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
341343 defer stream.close();
342 try stream.writeAll(request_bytes);
344 var writer = stream.writer().unbuffered();
345 try writer.writeAll(request_bytes);
343346
344347 const response = try stream.reader().readAllAlloc(gpa, 8192);
345348 defer gpa.free(response);
......@@ -960,8 +963,9 @@ test "Server streams both reading and writing" {
960963 try req.send();
961964 try req.wait();
962965
963 try req.writeAll("one ");
964 try req.writeAll("fish");
966 var w = req.writer().unbuffered();
967 try w.writeAll("one ");
968 try w.writeAll("fish");
965969
966970 try req.finish();
967971
......@@ -992,8 +996,9 @@ fn echoTests(client: *http.Client, port: u16) !void {
992996 req.transfer_encoding = .{ .content_length = 14 };
993997
994998 try req.send();
995 try req.writeAll("Hello, ");
996 try req.writeAll("World!\n");
999 var w = req.writer().unbuffered();
1000 try w.writeAll("Hello, ");
1001 try w.writeAll("World!\n");
9971002 try req.finish();
9981003
9991004 try req.wait();
......@@ -1026,8 +1031,9 @@ fn echoTests(client: *http.Client, port: u16) !void {
10261031 req.transfer_encoding = .chunked;
10271032
10281033 try req.send();
1029 try req.writeAll("Hello, ");
1030 try req.writeAll("World!\n");
1034 var w = req.writer().unbuffered();
1035 try w.writeAll("Hello, ");
1036 try w.writeAll("World!\n");
10311037 try req.finish();
10321038
10331039 try req.wait();
......@@ -1080,8 +1086,9 @@ fn echoTests(client: *http.Client, port: u16) !void {
10801086 req.transfer_encoding = .chunked;
10811087
10821088 try req.send();
1083 try req.writeAll("Hello, ");
1084 try req.writeAll("World!\n");
1089 var w = req.writer().unbuffered();
1090 try w.writeAll("Hello, ");
1091 try w.writeAll("World!\n");
10851092 try req.finish();
10861093
10871094 try req.wait();
lib/std/net.zig+197-62
......@@ -849,9 +849,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
849849 return Stream{ .handle = sockfd };
850850}
851851
852const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
853 // TODO: break this up into error sets from the various underlying functions
854
852// TODO: Instead of having a massive error set, make the error set have categories, and then
853// store the sub-error as a diagnostic anyerror value.
854const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || anyerror || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
855855 TemporaryNameServerFailure,
856856 NameServerFailure,
857857 AddressFamilyNotSupported,
......@@ -1358,16 +1358,23 @@ fn linuxLookupNameFromHosts(
13581358
13591359 var buffered_reader = std.io.bufferedReader(file.reader());
13601360 const reader = buffered_reader.reader();
1361 // TODO: rework buffered reader so that we can use its buffer directly when searching for delimiters
13611362 var line_buf: [512]u8 = undefined;
1362 while (reader.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {
1363 error.StreamTooLong => blk: {
1364 // Skip to the delimiter in the reader, to fix parsing
1365 try reader.skipUntilDelimiterOrEof('\n');
1366 // Use the truncated line. A truncated comment or hostname will be handled correctly.
1367 break :blk &line_buf;
1368 },
1369 else => |e| return e,
1370 }) |line| {
1363 var line_buf_writer: std.io.BufferedWriter = undefined;
1364 line_buf_writer.initFixed(&line_buf);
1365 while (true) {
1366 const line = if (reader.streamUntilDelimiter(&line_buf_writer, '\n', line_buf.len)) |_| l: {
1367 break :l line_buf_writer.getWritten();
1368 } else |err| switch (err) {
1369 error.EndOfStream => l: {
1370 if (line_buf_writer.getWritten().len == 0) break;
1371 // Skip to the delimiter in the reader, to fix parsing
1372 try reader.skipUntilDelimiterOrEof('\n');
1373 // Use the truncated line. A truncated comment or hostname will be handled correctly.
1374 break :l &line_buf;
1375 },
1376 else => |e| return e,
1377 };
13711378 var split_it = mem.splitScalar(u8, line, '#');
13721379 const no_comment_line = split_it.first();
13731380
......@@ -1559,16 +1566,23 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {
15591566
15601567 var buf_reader = std.io.bufferedReader(file.reader());
15611568 const stream = buf_reader.reader();
1569 // TODO: rework buffered reader so that we can use its buffer directly when searching for delimiters
15621570 var line_buf: [512]u8 = undefined;
1563 while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {
1564 error.StreamTooLong => blk: {
1565 // Skip to the delimiter in the stream, to fix parsing
1566 try stream.skipUntilDelimiterOrEof('\n');
1567 // Give an empty line to the while loop, which will be skipped.
1568 break :blk line_buf[0..0];
1569 },
1570 else => |e| return e,
1571 }) |line| {
1571 var line_buf_writer: std.io.BufferedWriter = undefined;
1572 line_buf_writer.initFixed(&line_buf);
1573 while (true) {
1574 const line = if (stream.streamUntilDelimiter(&line_buf_writer, '\n', line_buf.len)) |_| l: {
1575 break :l line_buf_writer.getWritten();
1576 } else |err| switch (err) {
1577 error.EndOfStream => l: {
1578 if (line_buf_writer.getWritten().len == 0) break;
1579 // Skip to the delimiter in the reader, to fix parsing
1580 try stream.skipUntilDelimiterOrEof('\n');
1581 // Give an empty line to the while loop, which will be skipped.
1582 break :l line_buf[0..0];
1583 },
1584 else => |e| return e,
1585 };
15721586 const no_comment_line = no_comment_line: {
15731587 var split = mem.splitScalar(u8, line, '#');
15741588 break :no_comment_line split.first();
......@@ -1833,7 +1847,9 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
18331847pub const Stream = struct {
18341848 /// Underlying platform-defined type which may or may not be
18351849 /// interchangeable with a file system file descriptor.
1836 handle: posix.socket_t,
1850 handle: Handle,
1851
1852 pub const Handle = if (native_os == .windows) windows.ws2_32.SOCKET else posix.fd_t;
18371853
18381854 pub fn close(s: Stream) void {
18391855 switch (native_os) {
......@@ -1843,17 +1859,36 @@ pub const Stream = struct {
18431859 }
18441860
18451861 pub const ReadError = posix.ReadError;
1846 pub const WriteError = posix.WriteError;
1862 pub const WriteError = posix.SendMsgError || error{
1863 ConnectionResetByPeer,
1864 SocketNotBound,
1865 MessageTooBig,
1866 NetworkSubsystemFailed,
1867 SystemResources,
1868 SocketNotConnected,
1869 Unexpected,
1870 };
18471871
18481872 pub const Reader = io.Reader(Stream, ReadError, read);
1849 pub const Writer = io.Writer(Stream, WriteError, write);
18501873
18511874 pub fn reader(self: Stream) Reader {
18521875 return .{ .context = self };
18531876 }
18541877
1855 pub fn writer(self: Stream) Writer {
1856 return .{ .context = self };
1878 pub fn writer(stream: Stream) std.io.Writer {
1879 return .{
1880 .context = handleToOpaque(stream.handle),
1881 .vtable = switch (native_os) {
1882 .windows => &.{
1883 .writeSplat = windows_writeSplat,
1884 .writeFile = windows_writeFile,
1885 },
1886 else => &.{
1887 .writeSplat = posix_writeSplat,
1888 .writeFile = std.fs.File.writer_writeFile,
1889 },
1890 },
1891 };
18571892 }
18581893
18591894 pub fn read(self: Stream, buffer: []u8) ReadError!usize {
......@@ -1898,48 +1933,148 @@ pub const Stream = struct {
18981933 return index;
18991934 }
19001935
1901 /// TODO in evented I/O mode, this implementation incorrectly uses the event loop's
1902 /// file system thread instead of non-blocking. It needs to be reworked to properly
1903 /// use non-blocking I/O.
1904 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
1905 if (native_os == .windows) {
1906 return windows.WriteFile(self.handle, buffer, null);
1936 fn windows_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1937 comptime assert(native_os == .windows);
1938 if (data.len == 1 and splat == 0) return 0;
1939 var splat_buffer: [256]u8 = undefined;
1940 var iovecs: [max_buffers_len]windows.WSABUF = undefined;
1941 var len: u32 = @min(iovecs.len, data.len);
1942 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
1943 .buf = if (d.len == 0) "" else d.ptr, // TODO: does Windows allow ptr=undefined len=0 ?
1944 .len = d.len,
1945 };
1946 switch (splat) {
1947 0 => len -= 1,
1948 1 => {},
1949 else => {
1950 const pattern = data[data.len - 1];
1951 if (pattern.len == 1) {
1952 const memset_len = @min(splat_buffer.len, splat);
1953 const buf = splat_buffer[0..memset_len];
1954 @memset(buf, pattern[0]);
1955 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
1956 var remaining_splat = splat - buf.len;
1957 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
1958 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
1959 remaining_splat -= splat_buffer.len;
1960 len += 1;
1961 }
1962 if (remaining_splat > 0 and len < iovecs.len) {
1963 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
1964 len += 1;
1965 }
1966 }
1967 },
19071968 }
1908
1909 return posix.write(self.handle, buffer);
1910 }
1911
1912 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
1913 var index: usize = 0;
1914 while (index < bytes.len) {
1915 index += try self.write(bytes[index..]);
1969 const handle = opaqueToHandle(context);
1970 var n: u32 = undefined;
1971 const rc = windows.ws2_32.WSASend(handle, &iovecs, len, &n, 0, null, null);
1972 if (rc == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
1973 .WSAECONNABORTED => return error.ConnectionResetByPeer,
1974 .WSAECONNRESET => return error.ConnectionResetByPeer,
1975 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
1976 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
1977 .WSAEINVAL => return error.SocketNotBound,
1978 .WSAEMSGSIZE => return error.MessageTooBig,
1979 .WSAENETDOWN => return error.NetworkSubsystemFailed,
1980 .WSAENETRESET => return error.ConnectionResetByPeer,
1981 .WSAENOBUFS => return error.SystemResources,
1982 .WSAENOTCONN => return error.SocketNotConnected,
1983 .WSAENOTSOCK => unreachable, // not a socket
1984 .WSAEOPNOTSUPP => unreachable, // only for message-oriented sockets
1985 .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown
1986 .WSAEWOULDBLOCK => return error.WouldBlock,
1987 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
1988 .WSA_IO_PENDING => unreachable, // not using overlapped I/O
1989 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
1990 else => |err| return windows.unexpectedWSAError(err),
1991 };
1992 return n;
1993 }
1994
1995 fn posix_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
1996 const sock_fd = opaqueToHandle(context);
1997 comptime assert(native_os != .windows);
1998 var splat_buffer: [256]u8 = undefined;
1999 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
2000 var len: usize = @min(iovecs.len, data.len);
2001 for (iovecs[0..len], data[0..len]) |*v, d| v.* = .{
2002 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
2003 .len = d.len,
2004 };
2005 var msg: posix.msghdr_const = .{
2006 .name = null,
2007 .namelen = 0,
2008 .iov = &iovecs,
2009 .iovlen = len,
2010 .control = null,
2011 .controllen = 0,
2012 .flags = 0,
2013 };
2014 switch (splat) {
2015 0 => msg.iovlen = len - 1,
2016 1 => {},
2017 else => {
2018 const pattern = data[data.len - 1];
2019 if (pattern.len == 1) {
2020 const memset_len = @min(splat_buffer.len, splat);
2021 const buf = splat_buffer[0..memset_len];
2022 @memset(buf, pattern[0]);
2023 iovecs[len - 1] = .{ .base = buf.ptr, .len = buf.len };
2024 var remaining_splat = splat - buf.len;
2025 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
2026 iovecs[len] = .{ .base = &splat_buffer, .len = splat_buffer.len };
2027 remaining_splat -= splat_buffer.len;
2028 len += 1;
2029 }
2030 if (remaining_splat > 0 and len < iovecs.len) {
2031 iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
2032 len += 1;
2033 }
2034 msg.iovlen = len;
2035 }
2036 },
19162037 }
2038 const flags = posix.MSG.NOSIGNAL;
2039 return std.posix.sendmsg(sock_fd, &msg, flags);
2040 }
2041
2042 fn windows_writeFile(
2043 context: *anyopaque,
2044 in_file: std.fs.File,
2045 in_offset: u64,
2046 in_len: std.io.Writer.VTable.FileLen,
2047 headers_and_trailers: []const []const u8,
2048 headers_len: usize,
2049 ) anyerror!usize {
2050 const len_int = switch (in_len) {
2051 .zero => return windows_writeSplat(context, headers_and_trailers, 1),
2052 .entire_file => std.math.maxInt(usize),
2053 else => in_len.int(),
2054 };
2055 if (headers_len > 0) return windows_writeSplat(context, headers_and_trailers[0..headers_len], 1);
2056 var file_contents_buffer: [4096]u8 = undefined;
2057 const read_buffer = file_contents_buffer[0..@min(file_contents_buffer.len, len_int)];
2058 const n = try windows.ReadFile(in_file.handle, read_buffer, in_offset);
2059 return windows_writeSplat(context, &.{read_buffer[0..n]}, 1);
19172060 }
19182061
1919 /// See https://github.com/ziglang/zig/issues/7699
1920 /// See equivalent function: `std.fs.File.writev`.
1921 pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize {
1922 return posix.writev(self.handle, iovecs);
1923 }
2062 const max_buffers_len = 8;
19242063
1925 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1926 /// order to handle partial writes from the underlying OS layer.
1927 /// See https://github.com/ziglang/zig/issues/7699
1928 /// See equivalent function: `std.fs.File.writevAll`.
1929 pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {
1930 if (iovecs.len == 0) return;
2064 fn handleToOpaque(handle: Handle) *anyopaque {
2065 return switch (@typeInfo(Handle)) {
2066 .pointer => @ptrCast(handle),
2067 .int => @ptrFromInt(@as(u32, @bitCast(handle))),
2068 else => @compileError("unhandled"),
2069 };
2070 }
19312071
1932 var i: usize = 0;
1933 while (true) {
1934 var amt = try self.writev(iovecs[i..]);
1935 while (amt >= iovecs[i].len) {
1936 amt -= iovecs[i].len;
1937 i += 1;
1938 if (i >= iovecs.len) return;
1939 }
1940 iovecs[i].base += amt;
1941 iovecs[i].len -= amt;
1942 }
2072 fn opaqueToHandle(userdata: *anyopaque) Handle {
2073 return switch (@typeInfo(Handle)) {
2074 .pointer => @ptrCast(userdata),
2075 .int => @intCast(@intFromPtr(userdata)),
2076 else => @compileError("unhandled"),
2077 };
19432078 }
19442079};
19452080
lib/std/os/windows.zig-34
......@@ -1690,40 +1690,6 @@ pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.so
16901690 return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen)));
16911691}
16921692
1693pub fn sendmsg(
1694 s: ws2_32.SOCKET,
1695 msg: *ws2_32.WSAMSG_const,
1696 flags: u32,
1697) i32 {
1698 var bytes_send: DWORD = undefined;
1699 if (ws2_32.WSASendMsg(s, msg, flags, &bytes_send, null, null) == ws2_32.SOCKET_ERROR) {
1700 return ws2_32.SOCKET_ERROR;
1701 } else {
1702 return @as(i32, @as(u31, @intCast(bytes_send)));
1703 }
1704}
1705
1706pub fn sendto(s: ws2_32.SOCKET, buf: [*]const u8, len: usize, flags: u32, to: ?*const ws2_32.sockaddr, to_len: ws2_32.socklen_t) i32 {
1707 var buffer = ws2_32.WSABUF{ .len = @as(u31, @truncate(len)), .buf = @constCast(buf) };
1708 var bytes_send: DWORD = undefined;
1709 if (ws2_32.WSASendTo(s, @as([*]ws2_32.WSABUF, @ptrCast(&buffer)), 1, &bytes_send, flags, to, @as(i32, @intCast(to_len)), null, null) == ws2_32.SOCKET_ERROR) {
1710 return ws2_32.SOCKET_ERROR;
1711 } else {
1712 return @as(i32, @as(u31, @intCast(bytes_send)));
1713 }
1714}
1715
1716pub fn recvfrom(s: ws2_32.SOCKET, buf: [*]u8, len: usize, flags: u32, from: ?*ws2_32.sockaddr, from_len: ?*ws2_32.socklen_t) i32 {
1717 var buffer = ws2_32.WSABUF{ .len = @as(u31, @truncate(len)), .buf = buf };
1718 var bytes_received: DWORD = undefined;
1719 var flags_inout = flags;
1720 if (ws2_32.WSARecvFrom(s, @as([*]ws2_32.WSABUF, @ptrCast(&buffer)), 1, &bytes_received, &flags_inout, from, @as(?*i32, @ptrCast(from_len)), null, null) == ws2_32.SOCKET_ERROR) {
1721 return ws2_32.SOCKET_ERROR;
1722 } else {
1723 return @as(i32, @as(u31, @intCast(bytes_received)));
1724 }
1725}
1726
17271693pub fn poll(fds: [*]ws2_32.pollfd, n: c_ulong, timeout: i32) i32 {
17281694 return ws2_32.WSAPoll(fds, n, timeout);
17291695}
lib/std/os/windows/ws2_32.zig+1-9
......@@ -1829,7 +1829,7 @@ pub extern "ws2_32" fn sendto(
18291829 buf: [*]const u8,
18301830 len: i32,
18311831 flags: i32,
1832 to: *const sockaddr,
1832 to: ?*const sockaddr,
18331833 tolen: i32,
18341834) callconv(.winapi) i32;
18351835
......@@ -2116,14 +2116,6 @@ pub extern "ws2_32" fn WSASendMsg(
21162116 lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,
21172117) callconv(.winapi) i32;
21182118
2119pub extern "ws2_32" fn WSARecvMsg(
2120 s: SOCKET,
2121 lpMsg: *WSAMSG,
2122 lpdwNumberOfBytesRecv: ?*u32,
2123 lpOverlapped: ?*OVERLAPPED,
2124 lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,
2125) callconv(.winapi) i32;
2126
21272119pub extern "ws2_32" fn WSASendDisconnect(
21282120 s: SOCKET,
21292121 lpOutboundDisconnectData: ?*WSABUF,