authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-21 21:15:54-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
logd9b9e3c2724a8ea5b66e231c4bd750c03e8f095c
tree80279fee94c7457b594abba2bf53570ed95f7d14
parent4004c8058e2233f1807c0c7f1a5bd4db4558a134

std: update some http to new reader/writer


10 files changed, 222 insertions(+), 122 deletions(-)

lib/std/crypto/Certificate/Bundle.zig+1-1
...@@ -236,7 +236,7 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFrom...@@ -236,7 +236,7 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFrom
236 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);236 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
237 const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);237 const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);
238 const buffer = cb.bytes.allocatedSlice()[end_reserved..];238 const buffer = cb.bytes.allocatedSlice()[end_reserved..];
239 const end_index = try file.readAll(buffer);239 const end_index = try file.readShort(buffer);
240 const encoded_bytes = buffer[0..end_index];240 const encoded_bytes = buffer[0..end_index];
241241
242 const begin_marker = "-----BEGIN CERTIFICATE-----";242 const begin_marker = "-----BEGIN CERTIFICATE-----";
lib/std/crypto/tls/Client.zig+1-1
...@@ -916,7 +916,7 @@ pub fn writer(c: *Client) std.io.Writer {...@@ -916,7 +916,7 @@ pub fn writer(c: *Client) std.io.Writer {
916 .context = c,916 .context = c,
917 .vtable = &.{917 .vtable = &.{
918 .writeSplat = writeSplat,918 .writeSplat = writeSplat,
919 .writeFile = std.io.Writer.unimplemented_writeFile,919 .writeFile = std.io.Writer.unimplementedWriteFile,
920 },920 },
921 };921 };
922}922}
lib/std/fs/File.zig+15-1
...@@ -839,6 +839,20 @@ pub fn read(self: File, buffer: []u8) ReadError!usize {...@@ -839,6 +839,20 @@ pub fn read(self: File, buffer: []u8) ReadError!usize {
839 return posix.read(self.handle, buffer);839 return posix.read(self.handle, buffer);
840}840}
841841
842/// One-shot alternative to `std.io.BufferedReader.readShort` via `reader`.
843///
844/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
845/// means the file reached the end.
846pub fn readShort(self: File, buffer: []u8) ReadError!usize {
847 var index: usize = 0;
848 while (index != buffer.len) {
849 const n = try self.read(buffer[index..]);
850 if (n == 0) break;
851 index += n;
852 }
853 return index;
854}
855
842/// On Windows, this function currently does alter the file pointer.856/// On Windows, this function currently does alter the file pointer.
843/// https://github.com/ziglang/zig/issues/12783857/// https://github.com/ziglang/zig/issues/12783
844pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {858pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
...@@ -884,7 +898,7 @@ pub fn write(self: File, bytes: []const u8) WriteError!usize {...@@ -884,7 +898,7 @@ pub fn write(self: File, bytes: []const u8) WriteError!usize {
884 return posix.write(self.handle, bytes);898 return posix.write(self.handle, bytes);
885}899}
886900
887/// One-shot alternative to `writer`.901/// One-shot alternative to `std.io.BufferedWriter.writeAll` via `writer`.
888pub fn writeAll(self: File, bytes: []const u8) WriteError!void {902pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
889 var index: usize = 0;903 var index: usize = 0;
890 while (index < bytes.len) {904 while (index < bytes.len) {
lib/std/http/Client.zig+13-14
...@@ -38,9 +38,12 @@ next_https_rescan_certs: bool = true,...@@ -38,9 +38,12 @@ next_https_rescan_certs: bool = true,
38/// The pool of connections that can be reused (and currently in use).38/// The pool of connections that can be reused (and currently in use).
39connection_pool: ConnectionPool = .{},39connection_pool: ConnectionPool = .{},
40/// Each `Connection` allocates this amount for the reader buffer.40/// Each `Connection` allocates this amount for the reader buffer.
41read_buffer_size: usize,41///
42/// If the entire HTTP header cannot fit in this amount of bytes,
43/// `error.HttpHeadersOversize` will be returned from `Request.wait`.
44read_buffer_size: usize = 4096,
42/// Each `Connection` allocates this amount for the writer buffer.45/// Each `Connection` allocates this amount for the writer buffer.
43write_buffer_size: usize,46write_buffer_size: usize = 1024,
4447
45/// If populated, all http traffic travels through this third party.48/// If populated, all http traffic travels through this third party.
46/// This field cannot be modified while the client has active connections.49/// This field cannot be modified while the client has active connections.
...@@ -177,23 +180,21 @@ pub const ConnectionPool = struct {...@@ -177,23 +180,21 @@ pub const ConnectionPool = struct {
177 /// All future operations on the connection pool will deadlock.180 /// All future operations on the connection pool will deadlock.
178 ///181 ///
179 /// Threadsafe.182 /// Threadsafe.
180 pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void {183 pub fn deinit(pool: *ConnectionPool) void {
181 pool.mutex.lock();184 pool.mutex.lock();
182185
183 var next = pool.free.first;186 var next = pool.free.first;
184 while (next) |node| {187 while (next) |node| {
185 const connection: *Connection = @fieldParentPtr("pool_node", node);188 const connection: *Connection = @fieldParentPtr("pool_node", node);
186 next = node.next;189 next = node.next;
187 connection.close(allocator);190 connection.destroy();
188 allocator.destroy(connection);
189 }191 }
190192
191 next = pool.used.first;193 next = pool.used.first;
192 while (next) |node| {194 while (next) |node| {
193 const connection: *Connection = @fieldParentPtr("pool_node", node);195 const connection: *Connection = @fieldParentPtr("pool_node", node);
194 next = node.next;196 next = node.next;
195 connection.close(allocator);197 connection.destroy();
196 allocator.destroy(node);
197 }198 }
198199
199 pool.* = undefined;200 pool.* = undefined;
...@@ -324,8 +325,9 @@ pub const Connection = struct {...@@ -324,8 +325,9 @@ pub const Connection = struct {
324 return tls;325 return tls;
325 }326 }
326327
327 fn destroy(tls: *Tls, gpa: Allocator) void {328 fn destroy(tls: *Tls) void {
328 const c = &tls.connection;329 const c = &tls.connection;
330 const gpa = c.client.allocator;
329 const base: [*]u8 = @ptrCast(tls);331 const base: [*]u8 = @ptrCast(tls);
330 gpa.free(base[0..allocLen(c.client, c.host_len)]);332 gpa.free(base[0..allocLen(c.client, c.host_len)]);
331 }333 }
...@@ -1262,10 +1264,8 @@ pub const Proxy = struct {...@@ -1262,10 +1264,8 @@ pub const Proxy = struct {
1262pub fn deinit(client: *Client) void {1264pub fn deinit(client: *Client) void {
1263 assert(client.connection_pool.used.first == null); // There are still active requests.1265 assert(client.connection_pool.used.first == null); // There are still active requests.
12641266
1265 client.connection_pool.deinit(client.allocator);1267 client.connection_pool.deinit();
12661268 if (!disable_tls) client.ca_bundle.deinit(client.allocator);
1267 if (!disable_tls)
1268 client.ca_bundle.deinit(client.allocator);
12691269
1270 client.* = undefined;1270 client.* = undefined;
1271}1271}
...@@ -1574,8 +1574,7 @@ pub fn connect(...@@ -1574,8 +1574,7 @@ pub fn connect(
15741574
1575/// TODO collapse each error set into its own meta error code, and store1575/// TODO collapse each error set into its own meta error code, and store
1576/// the underlying error code as a field on Request1576/// the underlying error code as a field on Request
1577pub const RequestError = ConnectTcpError || ConnectErrorPartial || std.io.Writer.Error ||1577pub const RequestError = ConnectTcpError || ConnectErrorPartial || std.io.Writer.Error || std.fmt.ParseIntError ||
1578 std.fmt.ParseIntError || Connection.WriteError ||
1579 error{1578 error{
1580 UnsupportedUriScheme,1579 UnsupportedUriScheme,
1581 UriMissingHost,1580 UriMissingHost,
lib/std/http/Server.zig+34-14
...@@ -20,7 +20,8 @@ out: *std.io.BufferedWriter,...@@ -20,7 +20,8 @@ out: *std.io.BufferedWriter,
20/// same connection, and makes invalid API usage cause assertion failures20/// same connection, and makes invalid API usage cause assertion failures
21/// rather than HTTP protocol violations.21/// rather than HTTP protocol violations.
22state: State,22state: State,
23head_parse_err: Request.Head.ParseError,23/// Populated when `receiveHead` returns `ReceiveHeadError.HttpHeadersInvalid`.
24head_parse_err: ?Request.Head.ParseError = null,
2425
25pub const State = enum {26pub const State = enum {
26 /// The connection is available to be used for the first time, or reused.27 /// The connection is available to be used for the first time, or reused.
...@@ -46,7 +47,6 @@ pub fn init(in: *std.io.BufferedReader, out: *std.io.BufferedWriter) Server {...@@ -46,7 +47,6 @@ pub fn init(in: *std.io.BufferedReader, out: *std.io.BufferedWriter) Server {
46 .in = in,47 .in = in,
47 .out = out,48 .out = out,
48 .state = .ready,49 .state = .ready,
49 .head_parse_err = undefined,
50 };50 };
51}51}
5252
...@@ -92,8 +92,6 @@ pub fn receiveHead(s: *Server) ReceiveHeadError!Request {...@@ -92,8 +92,6 @@ pub fn receiveHead(s: *Server) ReceiveHeadError!Request {
92 if (hp.state == .finished) return .{92 if (hp.state == .finished) return .{
93 .server = s,93 .server = s,
94 .head_end = head_end,94 .head_end = head_end,
95 .trailers_len = 0,
96 .read_err = null,
97 .head = Request.Head.parse(buf[0..head_end]) catch |err| {95 .head = Request.Head.parse(buf[0..head_end]) catch |err| {
98 s.head_parse_err = err;96 s.head_parse_err = err;
99 return error.HttpHeadersInvalid;97 return error.HttpHeadersInvalid;
...@@ -109,13 +107,13 @@ pub const Request = struct {...@@ -109,13 +107,13 @@ pub const Request = struct {
109 head_end: usize,107 head_end: usize,
110 /// Number of bytes of HTTP trailers. These are at the end of a108 /// Number of bytes of HTTP trailers. These are at the end of a
111 /// transfer-encoding: chunked message.109 /// transfer-encoding: chunked message.
112 trailers_len: usize,110 trailers_len: usize = 0,
113 head: Head,111 head: Head,
114 reader_state: union {112 reader_state: union {
115 remaining_content_length: u64,113 remaining_content_length: u64,
116 remaining_chunk_len: RemainingChunkLen,114 remaining_chunk_len: RemainingChunkLen,
117 },115 },
118 read_err: ?ReadError,116 read_err: ?ReadError = null,
119117
120 pub const ReadError = error{118 pub const ReadError = error{
121 HttpChunkInvalid,119 HttpChunkInvalid,
...@@ -330,12 +328,12 @@ pub const Request = struct {...@@ -330,12 +328,12 @@ pub const Request = struct {
330 .in = &br,328 .in = &br,
331 .out = undefined,329 .out = undefined,
332 .state = .ready,330 .state = .ready,
333 .in_err = undefined,
334 };331 };
335332
336 var request: Request = .{333 var request: Request = .{
337 .server = &server,334 .server = &server,
338 .head_end = request_bytes.len,335 .head_end = request_bytes.len,
336 .trailers_len = 0,
339 .head = undefined,337 .head = undefined,
340 .reader_state = undefined,338 .reader_state = undefined,
341 };339 };
...@@ -513,15 +511,16 @@ pub const Request = struct {...@@ -513,15 +511,16 @@ pub const Request = struct {
513 respond_options: RespondOptions = .{},511 respond_options: RespondOptions = .{},
514 };512 };
515513
516 /// The header is buffered but not sent until `Response.flush` is called.514 /// The header is not guaranteed to be sent until `Response.flush` is called.
517 ///515 ///
518 /// If the request contains a body and the connection is to be reused,516 /// If the request contains a body and the connection is to be reused,
519 /// discards the request body, leaving the Server in the `ready` state. If517 /// discards the request body, leaving the Server in the `ready` state. If
520 /// this discarding fails, the connection is marked as not to be reused and518 /// this discarding fails, the connection is marked as not to be reused and
521 /// no error is surfaced.519 /// no error is surfaced.
522 ///520 ///
523 /// HEAD requests are handled transparently by setting a flag on the521 /// HEAD requests are handled transparently by setting the
524 /// returned Response to omit the body. However it may be worth noticing522 /// `Response.elide_body` flag on the returned `Response`, causing
523 /// the response stream to omit the body. However, it may be worth noticing
525 /// that flag and skipping any expensive work that would otherwise need to524 /// that flag and skipping any expensive work that would otherwise need to
526 /// be done to satisfy the request.525 /// be done to satisfy the request.
527 ///526 ///
...@@ -922,6 +921,9 @@ pub const Response = struct {...@@ -922,6 +921,9 @@ pub const Response = struct {
922 ///921 ///
923 /// This is the underlying stream; use `buffered` to create a922 /// This is the underlying stream; use `buffered` to create a
924 /// `BufferedWriter` for this `Response`.923 /// `BufferedWriter` for this `Response`.
924 ///
925 /// Until the lifetime of `Response` ends, it is illegal to modify the
926 /// state of this other than via methods of `Response`.
925 server_output: *std.io.BufferedWriter,927 server_output: *std.io.BufferedWriter,
926 /// `null` means transfer-encoding: chunked.928 /// `null` means transfer-encoding: chunked.
927 /// As a debugging utility, counts down to zero as bytes are written.929 /// As a debugging utility, counts down to zero as bytes are written.
...@@ -966,12 +968,30 @@ pub const Response = struct {...@@ -966,12 +968,30 @@ pub const Response = struct {
966 };968 };
967 };969 };
968970
971 /// Sends all buffered data across `Response.server_output`.
972 ///
973 /// Some buffered data will remain if transfer-encoding is chunked and the
974 /// response is mid-chunk.
975 pub fn flush(r: *Response) WriteError!void {
976 switch (r.transfer_encoding) {
977 .none, .content_length => return r.server_output.flush(),
978 .chunked => |*chunked| switch (chunked.*) {
979 .offset => |*offset| {
980 try r.server_output.flushLimit(.limited(r.server_output.end - offset.*));
981 offset.* = 0;
982 },
983 .chunk_len => return r.server_output.flush(),
984 },
985 }
986 }
987
969 /// When using content-length, asserts that the amount of data sent matches988 /// When using content-length, asserts that the amount of data sent matches
970 /// the value sent in the header, then calls `flush`.989 /// the value sent in the header, then flushes. Asserts the amount of bytes
990 /// sent matches the content-length value provided in the HTTP header.
971 ///991 ///
972 /// Otherwise, transfer-encoding: chunked is being used, and it writes the992 /// When using transfer-encoding: chunked, writes the end-of-stream message
973 /// end-of-stream message with empty trailers, then flushes the stream to993 /// with empty trailers, then flushes the stream to the system. Asserts any
974 /// the system.994 /// started chunk has been completely finished.
975 ///995 ///
976 /// Respects the value of `elide_body` to omit all data after the headers.996 /// Respects the value of `elide_body` to omit all data after the headers.
977 ///997 ///
lib/std/http/test.zig+121-88
...@@ -11,13 +11,18 @@ const expectError = std.testing.expectError;...@@ -11,13 +11,18 @@ const expectError = std.testing.expectError;
11test "trailers" {11test "trailers" {
12 const test_server = try createTestServer(struct {12 const test_server = try createTestServer(struct {
13 fn run(net_server: *std.net.Server) anyerror!void {13 fn run(net_server: *std.net.Server) anyerror!void {
14 var header_buffer: [1024]u8 = undefined;14 var recv_buffer: [1024]u8 = undefined;
15 var send_buffer: [1024]u8 = undefined;
15 var remaining: usize = 1;16 var remaining: usize = 1;
16 while (remaining != 0) : (remaining -= 1) {17 while (remaining != 0) : (remaining -= 1) {
17 const conn = try net_server.accept();18 const connection = try net_server.accept();
18 defer conn.stream.close();19 defer connection.stream.close();
1920
20 var server = http.Server.init(conn, &header_buffer);21 var stream_reader = connection.stream.reader();
22 var stream_writer = connection.stream.writer();
23 var connection_br = stream_reader.interface().buffered(&recv_buffer);
24 var connection_bw = stream_writer.interface().buffered(&send_buffer);
25 var server = http.Server.init(&connection_br, &connection_bw);
2126
22 try expectEqual(.ready, server.state);27 try expectEqual(.ready, server.state);
23 var request = try server.receiveHead();28 var request = try server.receiveHead();
...@@ -29,13 +34,11 @@ test "trailers" {...@@ -29,13 +34,11 @@ test "trailers" {
29 fn serve(request: *http.Server.Request) !void {34 fn serve(request: *http.Server.Request) !void {
30 try expectEqualStrings(request.head.target, "/trailer");35 try expectEqualStrings(request.head.target, "/trailer");
3136
32 var send_buffer: [1024]u8 = undefined;37 var response = try request.respondStreaming(.{});
33 var response = request.respondStreaming(.{38 var bw = response.writer().unbuffered();
34 .send_buffer = &send_buffer,39 try bw.writeAll("Hello, ");
35 });
36 try response.writeAll("Hello, ");
37 try response.flush();40 try response.flush();
38 try response.writeAll("World!\n");41 try bw.writeAll("World!\n");
39 try response.flush();42 try response.flush();
40 try response.endChunked(.{43 try response.endChunked(.{
41 .trailers = &.{44 .trailers = &.{
...@@ -95,11 +98,16 @@ test "trailers" {...@@ -95,11 +98,16 @@ test "trailers" {
95test "HTTP server handles a chunked transfer coding request" {98test "HTTP server handles a chunked transfer coding request" {
96 const test_server = try createTestServer(struct {99 const test_server = try createTestServer(struct {
97 fn run(net_server: *std.net.Server) !void {100 fn run(net_server: *std.net.Server) !void {
98 var header_buffer: [8192]u8 = undefined;101 var recv_buffer: [8192]u8 = undefined;
99 const conn = try net_server.accept();102 var send_buffer: [500]u8 = undefined;
100 defer conn.stream.close();103 const connection = try net_server.accept();
101104 defer connection.stream.close();
102 var server = http.Server.init(conn, &header_buffer);105
106 var stream_reader = connection.stream.reader();
107 var stream_writer = connection.stream.writer();
108 var connection_br = stream_reader.interface().buffered(&recv_buffer);
109 var connection_bw = stream_writer.interface().buffered(&send_buffer);
110 var server = http.Server.init(&connection_br, &connection_bw);
103 var request = try server.receiveHead();111 var request = try server.receiveHead();
104112
105 try expect(request.head.transfer_encoding == .chunked);113 try expect(request.head.transfer_encoding == .chunked);
...@@ -153,13 +161,18 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -153,13 +161,18 @@ test "HTTP server handles a chunked transfer coding request" {
153test "echo content server" {161test "echo content server" {
154 const test_server = try createTestServer(struct {162 const test_server = try createTestServer(struct {
155 fn run(net_server: *std.net.Server) anyerror!void {163 fn run(net_server: *std.net.Server) anyerror!void {
156 var read_buffer: [1024]u8 = undefined;164 var recv_buffer: [1024]u8 = undefined;
165 var send_buffer: [100]u8 = undefined;
157166
158 accept: while (true) {167 accept: while (true) {
159 const conn = try net_server.accept();168 const connection = try net_server.accept();
160 defer conn.stream.close();169 defer connection.stream.close();
161170
162 var http_server = http.Server.init(conn, &read_buffer);171 var stream_reader = connection.stream.reader();
172 var stream_writer = connection.stream.writer();
173 var connection_br = stream_reader.interface().buffered(&recv_buffer);
174 var connection_bw = stream_writer.interface().buffered(&send_buffer);
175 var http_server = http.Server.init(&connection_br, &connection_bw);
163176
164 while (http_server.state == .ready) {177 while (http_server.state == .ready) {
165 var request = http_server.receiveHead() catch |err| switch (err) {178 var request = http_server.receiveHead() catch |err| switch (err) {
...@@ -200,9 +213,7 @@ test "echo content server" {...@@ -200,9 +213,7 @@ test "echo content server" {
200 try expectEqualStrings("Hello, World!\n", body);213 try expectEqualStrings("Hello, World!\n", body);
201 try expectEqualStrings("text/plain", request.head.content_type.?);214 try expectEqualStrings("text/plain", request.head.content_type.?);
202215
203 var send_buffer: [100]u8 = undefined;216 var response = try request.respondStreaming(.{
204 var response = request.respondStreaming(.{
205 .send_buffer = &send_buffer,
206 .content_length = switch (request.head.transfer_encoding) {217 .content_length = switch (request.head.transfer_encoding) {
207 .chunked => null,218 .chunked => null,
208 .none => len: {219 .none => len: {
...@@ -211,11 +222,10 @@ test "echo content server" {...@@ -211,11 +222,10 @@ test "echo content server" {
211 },222 },
212 },223 },
213 });224 });
214
215 try response.flush(); // Test an early flush to send the HTTP headers before the body.225 try response.flush(); // Test an early flush to send the HTTP headers before the body.
216 const w = response.writer();226 var bw = response.writer().unbuffered();
217 try w.writeAll("Hello, ");227 try bw.writeAll("Hello, ");
218 try w.writeAll("World!\n");228 try bw.writeAll("World!\n");
219 try response.end();229 try response.end();
220 //std.debug.print(" server finished responding\n", .{});230 //std.debug.print(" server finished responding\n", .{});
221 }231 }
...@@ -240,32 +250,33 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -240,32 +250,33 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
240 // closed, indicating the end of the body.250 // closed, indicating the end of the body.
241 const test_server = try createTestServer(struct {251 const test_server = try createTestServer(struct {
242 fn run(net_server: *std.net.Server) anyerror!void {252 fn run(net_server: *std.net.Server) anyerror!void {
243 var header_buffer: [1000]u8 = undefined;253 var recv_buffer: [1000]u8 = undefined;
254 var send_buffer: [500]u8 = undefined;
244 var remaining: usize = 1;255 var remaining: usize = 1;
245 while (remaining != 0) : (remaining -= 1) {256 while (remaining != 0) : (remaining -= 1) {
246 const conn = try net_server.accept();257 const connection = try net_server.accept();
247 defer conn.stream.close();258 defer connection.stream.close();
248259
249 var server = http.Server.init(conn, &header_buffer);260 var stream_reader = connection.stream.reader();
261 var stream_writer = connection.stream.writer();
262 var connection_br = stream_reader.interface().buffered(&recv_buffer);
263 var connection_bw = stream_writer.interface().buffered(&send_buffer);
264 var server = http.Server.init(&connection_br, &connection_bw);
250265
251 try expectEqual(.ready, server.state);266 try expectEqual(.ready, server.state);
252 var request = try server.receiveHead();267 var request = try server.receiveHead();
253 try expectEqualStrings(request.head.target, "/foo");268 try expectEqualStrings(request.head.target, "/foo");
254 var send_buffer: [500]u8 = undefined;269 var response = try request.respondStreaming(.{
255 var response = request.respondStreaming(.{
256 .send_buffer = &send_buffer,
257 .respond_options = .{270 .respond_options = .{
258 .transfer_encoding = .none,271 .transfer_encoding = .none,
259 },272 },
260 });273 });
261 var total: usize = 0;274 var buf: [30]u8 = undefined;
275 var bw = response.writer().buffered(&buf);
262 for (0..500) |i| {276 for (0..500) |i| {
263 var buf: [30]u8 = undefined;277 try bw.print("{d}, ah ha ha!\n", .{i});
264 const line = try std.fmt.bufPrint(&buf, "{d}, ah ha ha!\n", .{i});
265 try response.writeAll(line);
266 total += line.len;
267 }278 }
268 try expectEqual(7390, total);279 try expectEqual(7390, bw.count);
269 try response.end();280 try response.end();
270 try expectEqual(.closing, server.state);281 try expectEqual(.closing, server.state);
271 }282 }
...@@ -305,13 +316,19 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -305,13 +316,19 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
305test "receiving arbitrary http headers from the client" {316test "receiving arbitrary http headers from the client" {
306 const test_server = try createTestServer(struct {317 const test_server = try createTestServer(struct {
307 fn run(net_server: *std.net.Server) anyerror!void {318 fn run(net_server: *std.net.Server) anyerror!void {
308 var read_buffer: [666]u8 = undefined;319 var recv_buffer: [666]u8 = undefined;
320 var send_buffer: [777]u8 = undefined;
309 var remaining: usize = 1;321 var remaining: usize = 1;
310 while (remaining != 0) : (remaining -= 1) {322 while (remaining != 0) : (remaining -= 1) {
311 const conn = try net_server.accept();323 const connection = try net_server.accept();
312 defer conn.stream.close();324 defer connection.stream.close();
325
326 var stream_reader = connection.stream.reader();
327 var stream_writer = connection.stream.writer();
328 var connection_br = stream_reader.interface().buffered(&recv_buffer);
329 var connection_bw = stream_writer.interface().buffered(&send_buffer);
330 var server = http.Server.init(&connection_br, &connection_bw);
313331
314 var server = http.Server.init(conn, &read_buffer);
315 try expectEqual(.ready, server.state);332 try expectEqual(.ready, server.state);
316 var request = try server.receiveHead();333 var request = try server.receiveHead();
317 try expectEqualStrings("/bar", request.head.target);334 try expectEqualStrings("/bar", request.head.target);
...@@ -341,7 +358,8 @@ test "receiving arbitrary http headers from the client" {...@@ -341,7 +358,8 @@ test "receiving arbitrary http headers from the client" {
341 const gpa = std.testing.allocator;358 const gpa = std.testing.allocator;
342 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());359 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
343 defer stream.close();360 defer stream.close();
344 var writer = stream.writer().unbuffered();361 var stream_writer = stream.writer();
362 var writer = stream_writer.interface().unbuffered();
345 try writer.writeAll(request_bytes);363 try writer.writeAll(request_bytes);
346364
347 const response = try stream.reader().readAllAlloc(gpa, 8192);365 const response = try stream.reader().readAllAlloc(gpa, 8192);
...@@ -367,12 +385,18 @@ test "general client/server API coverage" {...@@ -367,12 +385,18 @@ test "general client/server API coverage" {
367 };385 };
368 const test_server = try createTestServer(struct {386 const test_server = try createTestServer(struct {
369 fn run(net_server: *std.net.Server) anyerror!void {387 fn run(net_server: *std.net.Server) anyerror!void {
370 var client_header_buffer: [1024]u8 = undefined;388 var recv_buffer: [1024]u8 = undefined;
389 var send_buffer: [100]u8 = undefined;
390
371 outer: while (global.handle_new_requests) {391 outer: while (global.handle_new_requests) {
372 var connection = try net_server.accept();392 var connection = try net_server.accept();
373 defer connection.stream.close();393 defer connection.stream.close();
374394
375 var http_server = http.Server.init(connection, &client_header_buffer);395 var stream_reader = connection.stream.reader();
396 var stream_writer = connection.stream.writer();
397 var connection_br = stream_reader.interface().buffered(&recv_buffer);
398 var connection_bw = stream_writer.interface().buffered(&send_buffer);
399 var http_server = http.Server.init(&connection_br, &connection_bw);
376400
377 while (http_server.state == .ready) {401 while (http_server.state == .ready) {
378 var request = http_server.receiveHead() catch |err| switch (err) {402 var request = http_server.receiveHead() catch |err| switch (err) {
...@@ -398,11 +422,8 @@ test "general client/server API coverage" {...@@ -398,11 +422,8 @@ test "general client/server API coverage" {
398 const body = try (try request.reader()).readAllAlloc(gpa, 8192);422 const body = try (try request.reader()).readAllAlloc(gpa, 8192);
399 defer gpa.free(body);423 defer gpa.free(body);
400424
401 var send_buffer: [100]u8 = undefined;
402
403 if (mem.startsWith(u8, request.head.target, "/get")) {425 if (mem.startsWith(u8, request.head.target, "/get")) {
404 var response = request.respondStreaming(.{426 var response = try request.respondStreaming(.{
405 .send_buffer = &send_buffer,
406 .content_length = if (mem.indexOf(u8, request.head.target, "?chunked") == null)427 .content_length = if (mem.indexOf(u8, request.head.target, "?chunked") == null)
407 14428 14
408 else429 else
...@@ -413,37 +434,35 @@ test "general client/server API coverage" {...@@ -413,37 +434,35 @@ test "general client/server API coverage" {
413 },434 },
414 },435 },
415 });436 });
416 const w = response.writer();437 var bw = response.writer().unbuffered();
417 try w.writeAll("Hello, ");438 try bw.writeAll("Hello, ");
418 try w.writeAll("World!\n");439 try bw.writeAll("World!\n");
419 try response.end();440 try response.end();
420 // Writing again would cause an assertion failure.441 // Writing again would cause an assertion failure.
421 } else if (mem.startsWith(u8, request.head.target, "/large")) {442 } else if (mem.startsWith(u8, request.head.target, "/large")) {
422 var response = request.respondStreaming(.{443 var response = try request.respondStreaming(.{
423 .send_buffer = &send_buffer,
424 .content_length = 14 * 1024 + 14 * 10,444 .content_length = 14 * 1024 + 14 * 10,
425 });445 });
426446
427 try response.flush(); // Test an early flush to send the HTTP headers before the body.447 try response.flush(); // Test an early flush to send the HTTP headers before the body.
428448
429 const w = response.writer();449 var bw = response.writer().unbuffered();
430450
431 var i: u32 = 0;451 var i: u32 = 0;
432 while (i < 5) : (i += 1) {452 while (i < 5) : (i += 1) {
433 try w.writeAll("Hello, World!\n");453 try bw.writeAll("Hello, World!\n");
434 }454 }
435455
436 try w.writeAll("Hello, World!\n" ** 1024);456 try bw.writeAll("Hello, World!\n" ** 1024);
437457
438 i = 0;458 i = 0;
439 while (i < 5) : (i += 1) {459 while (i < 5) : (i += 1) {
440 try w.writeAll("Hello, World!\n");460 try bw.writeAll("Hello, World!\n");
441 }461 }
442462
443 try response.end();463 try response.end();
444 } else if (mem.eql(u8, request.head.target, "/redirect/1")) {464 } else if (mem.eql(u8, request.head.target, "/redirect/1")) {
445 var response = request.respondStreaming(.{465 var response = try request.respondStreaming(.{
446 .send_buffer = &send_buffer,
447 .respond_options = .{466 .respond_options = .{
448 .status = .found,467 .status = .found,
449 .extra_headers = &.{468 .extra_headers = &.{
...@@ -452,9 +471,9 @@ test "general client/server API coverage" {...@@ -452,9 +471,9 @@ test "general client/server API coverage" {
452 },471 },
453 });472 });
454473
455 const w = response.writer();474 var bw = response.writer().unbuffered();
456 try w.writeAll("Hello, ");475 try bw.writeAll("Hello, ");
457 try w.writeAll("Redirected!\n");476 try bw.writeAll("Redirected!\n");
458 try response.end();477 try response.end();
459 } else if (mem.eql(u8, request.head.target, "/redirect/2")) {478 } else if (mem.eql(u8, request.head.target, "/redirect/2")) {
460 try request.respond("Hello, Redirected!\n", .{479 try request.respond("Hello, Redirected!\n", .{
...@@ -914,31 +933,36 @@ test "general client/server API coverage" {...@@ -914,31 +933,36 @@ test "general client/server API coverage" {
914test "Server streams both reading and writing" {933test "Server streams both reading and writing" {
915 const test_server = try createTestServer(struct {934 const test_server = try createTestServer(struct {
916 fn run(net_server: *std.net.Server) anyerror!void {935 fn run(net_server: *std.net.Server) anyerror!void {
917 var header_buffer: [1024]u8 = undefined;936 var recv_buffer: [1024]u8 = undefined;
918 const conn = try net_server.accept();937 var send_buffer: [777]u8 = undefined;
919 defer conn.stream.close();
920938
921 var server = http.Server.init(conn, &header_buffer);939 const connection = try net_server.accept();
922 var request = try server.receiveHead();940 defer connection.stream.close();
923 const reader = try request.reader();
924941
925 var send_buffer: [777]u8 = undefined;942 var stream_reader = connection.stream.reader();
926 var response = request.respondStreaming(.{943 var stream_writer = connection.stream.writer();
927 .send_buffer = &send_buffer,944 var connection_br = stream_reader.interface().buffered(&recv_buffer);
945 var connection_bw = stream_writer.interface().buffered(&send_buffer);
946 var server = http.Server.init(&connection_br, &connection_bw);
947 var request = try server.receiveHead();
948 var read_buffer: [100]u8 = undefined;
949 var br = try request.reader().buffered(&read_buffer);
950 var response = try request.respondStreaming(.{
928 .respond_options = .{951 .respond_options = .{
929 .transfer_encoding = .none, // Causes keep_alive=false952 .transfer_encoding = .none, // Causes keep_alive=false
930 },953 },
931 });954 });
932 const writer = response.writer();955 var bw = response.writer().unbuffered();
933956
934 while (true) {957 while (true) {
935 try response.flush();958 try response.flush();
936 var buf: [100]u8 = undefined;959 const buf = br.peekGreedy(1) catch |err| switch (err) {
937 const n = try reader.read(&buf);960 error.EndOfStream => break,
938 if (n == 0) break;961 error.ReadFailed => return error.ReadFailed,
939 const sub_buf = buf[0..n];962 };
940 for (sub_buf) |*b| b.* = std.ascii.toUpper(b.*);963 br.toss(buf.len);
941 try writer.writeAll(sub_buf);964 for (buf) |*b| b.* = std.ascii.toUpper(b.*);
965 try bw.writeAll(buf);
942 }966 }
943 try response.end();967 try response.end();
944 }968 }
...@@ -1161,12 +1185,17 @@ fn createTestServer(S: type) !*TestServer {...@@ -1161,12 +1185,17 @@ fn createTestServer(S: type) !*TestServer {
1161test "redirect to different connection" {1185test "redirect to different connection" {
1162 const test_server_new = try createTestServer(struct {1186 const test_server_new = try createTestServer(struct {
1163 fn run(net_server: *std.net.Server) anyerror!void {1187 fn run(net_server: *std.net.Server) anyerror!void {
1164 var header_buffer: [888]u8 = undefined;1188 var recv_buffer: [888]u8 = undefined;
1189 var send_buffer: [777]u8 = undefined;
11651190
1166 const conn = try net_server.accept();1191 const connection = try net_server.accept();
1167 defer conn.stream.close();1192 defer connection.stream.close();
11681193
1169 var server = http.Server.init(conn, &header_buffer);1194 var stream_reader = connection.stream.reader();
1195 var stream_writer = connection.stream.writer();
1196 var connection_br = stream_reader.interface().buffered(&recv_buffer);
1197 var connection_bw = stream_writer.interface().buffered(&send_buffer);
1198 var server = http.Server.init(&connection_br, &connection_bw);
1170 var request = try server.receiveHead();1199 var request = try server.receiveHead();
1171 try expectEqualStrings(request.head.target, "/ok");1200 try expectEqualStrings(request.head.target, "/ok");
1172 try request.respond("good job, you pass", .{});1201 try request.respond("good job, you pass", .{});
...@@ -1181,17 +1210,21 @@ test "redirect to different connection" {...@@ -1181,17 +1210,21 @@ test "redirect to different connection" {
11811210
1182 const test_server_orig = try createTestServer(struct {1211 const test_server_orig = try createTestServer(struct {
1183 fn run(net_server: *std.net.Server) anyerror!void {1212 fn run(net_server: *std.net.Server) anyerror!void {
1184 var header_buffer: [999]u8 = undefined;1213 var recv_buffer: [999]u8 = undefined;
1185 var send_buffer: [100]u8 = undefined;1214 var send_buffer: [100]u8 = undefined;
11861215
1187 const conn = try net_server.accept();1216 const connection = try net_server.accept();
1188 defer conn.stream.close();1217 defer connection.stream.close();
11891218
1190 const new_loc = try std.fmt.bufPrint(&send_buffer, "http://127.0.0.1:{d}/ok", .{1219 const new_loc = try std.fmt.bufPrint(&send_buffer, "http://127.0.0.1:{d}/ok", .{
1191 global.other_port.?,1220 global.other_port.?,
1192 });1221 });
11931222
1194 var server = http.Server.init(conn, &header_buffer);1223 var stream_reader = connection.stream.reader();
1224 var stream_writer = connection.stream.writer();
1225 var connection_br = stream_reader.interface().buffered(&recv_buffer);
1226 var connection_bw = stream_writer.interface().buffered(&send_buffer);
1227 var server = http.Server.init(&connection_br, &connection_bw);
1195 var request = try server.receiveHead();1228 var request = try server.receiveHead();
1196 try expectEqualStrings(request.head.target, "/help");1229 try expectEqualStrings(request.head.target, "/help");
1197 try request.respond("", .{1230 try request.respond("", .{
lib/std/io/BufferedReader.zig+2
...@@ -227,6 +227,8 @@ pub fn peekGreedy(br: *BufferedReader, n: usize) Reader.Error![]u8 {...@@ -227,6 +227,8 @@ pub fn peekGreedy(br: *BufferedReader, n: usize) Reader.Error![]u8 {
227///227///
228/// Asserts that the number of bytes buffered is at least as many as `n`.228/// Asserts that the number of bytes buffered is at least as many as `n`.
229///229///
230/// The "tossed" memory remains alive until a "peek" operation occurs.
231///
230/// See also:232/// See also:
231/// * `peek`.233/// * `peek`.
232/// * `discard`.234/// * `discard`.
lib/std/io/BufferedWriter.zig+13
...@@ -78,6 +78,15 @@ pub fn flush(bw: *BufferedWriter) Writer.Error!void {...@@ -78,6 +78,15 @@ pub fn flush(bw: *BufferedWriter) Writer.Error!void {
78 bw.end = 0;78 bw.end = 0;
79}79}
8080
81pub fn flushLimit(bw: *BufferedWriter, limit: Writer.Limit) Writer.Error!void {
82 const buffer = limit.slice(bw.buffer[0..bw.end]);
83 var index: usize = 0;
84 while (index < buffer.len) index += try bw.unbuffered_writer.writeVec(&.{buffer[index..]});
85 const remainder = bw.buffer[index..];
86 std.mem.copyForwards(u8, bw.buffer[0..remainder.len], remainder);
87 bw.end = remainder.len;
88}
89
81pub fn unusedCapacitySlice(bw: *const BufferedWriter) []u8 {90pub fn unusedCapacitySlice(bw: *const BufferedWriter) []u8 {
82 return bw.buffer[bw.end..];91 return bw.buffer[bw.end..];
83}92}
...@@ -1852,3 +1861,7 @@ test "fixed output" {...@@ -1852,3 +1861,7 @@ test "fixed output" {
1852 try bw.seekTo((try bw.getEndPos()) + 1);1861 try bw.seekTo((try bw.getEndPos()) + 1);
1853 try testing.expectError(error.WriteStreamEnd, bw.writeAll("H"));1862 try testing.expectError(error.WriteStreamEnd, bw.writeAll("H"));
1854}1863}
1864
1865test flushLimit {
1866 return error.Unimplemented;
1867}
lib/std/io/Writer.zig+17
...@@ -156,6 +156,23 @@ pub const failing: Writer = .{...@@ -156,6 +156,23 @@ pub const failing: Writer = .{
156 },156 },
157};157};
158158
159pub fn unimplementedWriteFile(
160 context: ?*anyopaque,
161 file: std.fs.File,
162 offset: std.io.Writer.Offset,
163 limit: std.io.Writer.Limit,
164 headers_and_trailers: []const []const u8,
165 headers_len: usize,
166) Error!usize {
167 _ = context;
168 _ = file;
169 _ = offset;
170 _ = limit;
171 _ = headers_and_trailers;
172 _ = headers_len;
173 return error.Unimplemented;
174}
175
159test {176test {
160 _ = Null;177 _ = Null;
161}178}
lib/std/net.zig+5-3
...@@ -1357,7 +1357,8 @@ fn linuxLookupNameFromHosts(...@@ -1357,7 +1357,8 @@ fn linuxLookupNameFromHosts(
1357 defer file.close();1357 defer file.close();
13581358
1359 var line_buf: [512]u8 = undefined;1359 var line_buf: [512]u8 = undefined;
1360 var br = file.reader().buffered(&line_buf);1360 var file_reader = file.reader();
1361 var br = file_reader.interface().buffered(&line_buf);
1361 while (br.takeSentinel('\n')) |line| {1362 while (br.takeSentinel('\n')) |line| {
1362 var split_it = mem.splitScalar(u8, line, '#');1363 var split_it = mem.splitScalar(u8, line, '#');
1363 const no_comment_line = split_it.first();1364 const no_comment_line = split_it.first();
...@@ -1530,7 +1531,7 @@ const ResolvConf = struct {...@@ -1530,7 +1531,7 @@ const ResolvConf = struct {
1530/// Returns `error.StreamTooLong` if a line is longer than 512 bytes.1531/// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
1531/// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/27611532/// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
1532fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {1533fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {
1533 rc.* = ResolvConf{1534 rc.* = .{
1534 .ns = std.ArrayList(LookupAddr).init(allocator),1535 .ns = std.ArrayList(LookupAddr).init(allocator),
1535 .search = std.ArrayList(u8).init(allocator),1536 .search = std.ArrayList(u8).init(allocator),
1536 .ndots = 1,1537 .ndots = 1,
...@@ -1549,7 +1550,8 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {...@@ -1549,7 +1550,8 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {
1549 defer file.close();1550 defer file.close();
15501551
1551 var line_buf: [512]u8 = undefined;1552 var line_buf: [512]u8 = undefined;
1552 var br = file.reader().buffered(&line_buf);1553 var file_reader = file.reader();
1554 var br = file_reader.interface().buffered(&line_buf);
1553 while (br.takeSentinel('\n')) |line_with_comment| {1555 while (br.takeSentinel('\n')) |line_with_comment| {
1554 const line = line: {1556 const line = line: {
1555 var split = mem.splitScalar(u8, line_with_comment, '#');1557 var split = mem.splitScalar(u8, line_with_comment, '#');