authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-18 20:22:09-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:37:11-07:00
log6129ecd4fe88e14531db98866c92c4a5660849ee
treeca8c76c12cbce56e8038bfecaae3590ae037487e
parentf1565e3d09f4c8a0d5c55a635a7e7f9925eae20f

std.net, std.http: simplify


10 files changed, 668 insertions(+), 795 deletions(-)

lib/std/http/Server.zig+314-554
...@@ -1,155 +1,54 @@...@@ -1,155 +1,54 @@
1//! HTTP Server implementation.1version: http.Version,
2//!2status: http.Status,
3//! This server assumes clients are well behaved and standard compliant; it3reason: ?[]const u8,
4//! deadlocks if a client holds a connection open without sending a request.4transfer_encoding: ResponseTransfer,
5keep_alive: bool,
6connection: Connection,
57
6const builtin = @import("builtin");8/// Externally-owned; must outlive the Server.
7const std = @import("../std.zig");9extra_headers: []const http.Header,
8const testing = std.testing;
9const http = std.http;
10const mem = std.mem;
11const net = std.net;
12const Uri = std.Uri;
13const Allocator = mem.Allocator;
14const assert = std.debug.assert;
15
16const Server = @This();
17const proto = @import("protocol.zig");
18
19/// The underlying server socket.
20socket: net.StreamServer,
21
22/// An interface to a plain connection.
23pub const Connection = struct {
24 stream: net.Stream,
25 protocol: Protocol,
2610
27 closing: bool = true,11/// The HTTP request that this response is responding to.
2812///
29 read_buf: [buffer_size]u8 = undefined,13/// This field is only valid after calling `wait`.
30 read_start: u16 = 0,14request: Request,
31 read_end: u16 = 0,
32
33 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
34 pub const Protocol = enum { plain };
35
36 pub fn rawReadAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
37 return switch (conn.protocol) {
38 .plain => conn.stream.readAtLeast(buffer, len),
39 // .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
40 } catch |err| {
41 switch (err) {
42 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
43 else => return error.UnexpectedReadFailure,
44 }
45 };
46 }
47
48 pub fn fill(conn: *Connection) ReadError!void {
49 if (conn.read_end != conn.read_start) return;
50
51 const nread = try conn.rawReadAtLeast(conn.read_buf[0..], 1);
52 if (nread == 0) return error.EndOfStream;
53 conn.read_start = 0;
54 conn.read_end = @intCast(nread);
55 }
56
57 pub fn peek(conn: *Connection) []const u8 {
58 return conn.read_buf[conn.read_start..conn.read_end];
59 }
60
61 pub fn drop(conn: *Connection, num: u16) void {
62 conn.read_start += num;
63 }
64
65 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
66 assert(len <= buffer.len);
67
68 var out_index: u16 = 0;
69 while (out_index < len) {
70 const available_read = conn.read_end - conn.read_start;
71 const available_buffer = buffer.len - out_index;
72
73 if (available_read > available_buffer) { // partially read buffered data
74 @memcpy(buffer[out_index..], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]);
75 out_index += @as(u16, @intCast(available_buffer));
76 conn.read_start += @as(u16, @intCast(available_buffer));
77
78 break;
79 } else if (available_read > 0) { // fully read buffered data
80 @memcpy(buffer[out_index..][0..available_read], conn.read_buf[conn.read_start..conn.read_end]);
81 out_index += available_read;
82 conn.read_start += available_read;
83
84 if (out_index >= len) break;
85 }
86
87 const leftover_buffer = available_buffer - available_read;
88 const leftover_len = len - out_index;
89
90 if (leftover_buffer > conn.read_buf.len) {
91 // skip the buffer if the output is large enough
92 return conn.rawReadAtLeast(buffer[out_index..], leftover_len);
93 }
94
95 try conn.fill();
96 }
97
98 return out_index;
99 }
100
101 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
102 return conn.readAtLeast(buffer, 1);
103 }
104
105 pub const ReadError = error{
106 ConnectionTimedOut,
107 ConnectionResetByPeer,
108 UnexpectedReadFailure,
109 EndOfStream,
110 };
111
112 pub const Reader = std.io.Reader(*Connection, ReadError, read);
113
114 pub fn reader(conn: *Connection) Reader {
115 return Reader{ .context = conn };
116 }
117
118 pub fn writeAll(conn: *Connection, buffer: []const u8) WriteError!void {
119 return switch (conn.protocol) {
120 .plain => conn.stream.writeAll(buffer),
121 // .tls => return conn.tls_client.writeAll(conn.stream, buffer),
122 } catch |err| switch (err) {
123 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
124 else => return error.UnexpectedWriteFailure,
125 };
126 }
12715
128 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {16state: State = .first,
129 return switch (conn.protocol) {
130 .plain => conn.stream.write(buffer),
131 // .tls => return conn.tls_client.write(conn.stream, buffer),
132 } catch |err| switch (err) {
133 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
134 else => return error.UnexpectedWriteFailure,
135 };
136 }
13717
138 pub const WriteError = error{18/// Initialize an HTTP server that can respond to multiple requests on the same
139 ConnectionResetByPeer,19/// connection.
140 UnexpectedWriteFailure,20/// The returned `Server` is ready for `reset` or `wait` to be called.
21pub fn init(connection: std.net.Server.Connection, options: Server.Request.InitOptions) Server {
22 return .{
23 .transfer_encoding = .none,
24 .keep_alive = true,
25 .connection = .{
26 .stream = connection.stream,
27 .protocol = .plain,
28 .closing = true,
29 .read_buf = undefined,
30 .read_start = 0,
31 .read_end = 0,
32 },
33 .request = Server.Request.init(options),
34 .version = .@"HTTP/1.1",
35 .status = .ok,
36 .reason = null,
37 .extra_headers = &.{},
141 };38 };
39}
14240
143 pub const Writer = std.io.Writer(*Connection, WriteError, write);41pub const State = enum {
42 first,
43 start,
44 waited,
45 responded,
46 finished,
47};
14448
145 pub fn writer(conn: *Connection) Writer {49pub const ResetState = enum { reset, closing };
146 return Writer{ .context = conn };
147 }
14850
149 pub fn close(conn: *Connection) void {51pub const Connection = @import("Server/Connection.zig");
150 conn.stream.close();
151 }
152};
15352
154/// The mode of transport for responses.53/// The mode of transport for responses.
155pub const ResponseTransfer = union(enum) {54pub const ResponseTransfer = union(enum) {
...@@ -160,10 +59,10 @@ pub const ResponseTransfer = union(enum) {...@@ -160,10 +59,10 @@ pub const ResponseTransfer = union(enum) {
16059
161/// The decompressor for request messages.60/// The decompressor for request messages.
162pub const Compression = union(enum) {61pub const Compression = union(enum) {
163 pub const DeflateDecompressor = std.compress.zlib.Decompressor(Response.TransferReader);62 pub const DeflateDecompressor = std.compress.zlib.Decompressor(Server.TransferReader);
164 pub const GzipDecompressor = std.compress.gzip.Decompressor(Response.TransferReader);63 pub const GzipDecompressor = std.compress.gzip.Decompressor(Server.TransferReader);
165 // https://github.com/ziglang/zig/issues/1893764 // https://github.com/ziglang/zig/issues/18937
166 //pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Response.TransferReader, .{});65 //pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Server.TransferReader, .{});
16766
168 deflate: DeflateDecompressor,67 deflate: DeflateDecompressor,
169 gzip: GzipDecompressor,68 gzip: GzipDecompressor,
...@@ -177,14 +76,37 @@ pub const Request = struct {...@@ -177,14 +76,37 @@ pub const Request = struct {
177 method: http.Method,76 method: http.Method,
178 target: []const u8,77 target: []const u8,
179 version: http.Version,78 version: http.Version,
180 expect: ?[]const u8 = null,79 expect: ?[]const u8,
181 content_type: ?[]const u8 = null,80 content_type: ?[]const u8,
182 content_length: ?u64 = null,81 content_length: ?u64,
183 transfer_encoding: http.TransferEncoding = .none,82 transfer_encoding: http.TransferEncoding,
184 transfer_compression: http.ContentEncoding = .identity,83 transfer_compression: http.ContentEncoding,
185 keep_alive: bool = false,84 keep_alive: bool,
186 parser: proto.HeadersParser,85 parser: proto.HeadersParser,
187 compression: Compression = .none,86 compression: Compression,
87
88 pub const InitOptions = struct {
89 /// Externally-owned memory used to store the client's entire HTTP header.
90 /// `error.HttpHeadersOversize` is returned from read() when a
91 /// client sends too many bytes of HTTP headers.
92 client_header_buffer: []u8,
93 };
94
95 pub fn init(options: InitOptions) Request {
96 return .{
97 .method = undefined,
98 .target = undefined,
99 .version = undefined,
100 .expect = null,
101 .content_type = null,
102 .content_length = null,
103 .transfer_encoding = .none,
104 .transfer_compression = .identity,
105 .keep_alive = false,
106 .parser = proto.HeadersParser.init(options.client_header_buffer),
107 .compression = .none,
108 };
109 }
188110
189 pub const ParseError = Allocator.Error || error{111 pub const ParseError = Allocator.Error || error{
190 UnknownHttpMethod,112 UnknownHttpMethod,
...@@ -300,478 +222,316 @@ pub const Request = struct {...@@ -300,478 +222,316 @@ pub const Request = struct {
300 }222 }
301};223};
302224
303/// A HTTP response waiting to be sent.225/// Reset this response to its initial state. This must be called before
304///226/// handling a second request on the same connection.
305/// Order of operations:227pub fn reset(res: *Server) ResetState {
306/// ```228 if (res.state == .first) {
307/// [/ <--------------------------------------- \]229 res.state = .start;
308/// accept -> wait -> send [ -> write -> finish][ -> reset /]230 return .reset;
309/// \ -> read /
310/// ```
311pub const Response = struct {
312 version: http.Version = .@"HTTP/1.1",
313 status: http.Status = .ok,
314 reason: ?[]const u8 = null,
315 transfer_encoding: ResponseTransfer,
316 keep_alive: bool,
317
318 /// The peer's address
319 address: net.Address,
320
321 /// The underlying connection for this response.
322 connection: Connection,
323
324 /// Externally-owned; must outlive the Response.
325 extra_headers: []const http.Header = &.{},
326
327 /// The HTTP request that this response is responding to.
328 ///
329 /// This field is only valid after calling `wait`.
330 request: Request,
331
332 state: State = .first,
333
334 pub const State = enum {
335 first,
336 start,
337 waited,
338 responded,
339 finished,
340 };
341
342 /// Free all resources associated with this response.
343 pub fn deinit(res: *Response) void {
344 res.connection.close();
345 }231 }
346232
347 pub const ResetState = enum { reset, closing };233 if (!res.request.parser.done) {
348234 // If the response wasn't fully read, then we need to close the connection.
349 /// Reset this response to its initial state. This must be called before235 res.connection.closing = true;
350 /// handling a second request on the same connection.236 return .closing;
351 pub fn reset(res: *Response) ResetState {237 }
352 if (res.state == .first) {
353 res.state = .start;
354 return .reset;
355 }
356238
357 if (!res.request.parser.done) {239 // A connection is only keep-alive if the Connection header is present
358 // If the response wasn't fully read, then we need to close the connection.240 // and its value is not "close". The server and client must both agree.
359 res.connection.closing = true;241 //
360 return .closing;242 // send() defaults to using keep-alive if the client requests it.
361 }243 res.connection.closing = !res.keep_alive or !res.request.keep_alive;
362244
363 // A connection is only keep-alive if the Connection header is present245 res.state = .start;
364 // and its value is not "close". The server and client must both agree.246 res.version = .@"HTTP/1.1";
365 //247 res.status = .ok;
366 // send() defaults to using keep-alive if the client requests it.248 res.reason = null;
367 res.connection.closing = !res.keep_alive or !res.request.keep_alive;
368249
369 res.state = .start;250 res.transfer_encoding = .none;
370 res.version = .@"HTTP/1.1";
371 res.status = .ok;
372 res.reason = null;
373251
374 res.transfer_encoding = .none;252 res.request = Request.init(.{
253 .client_header_buffer = res.request.parser.header_bytes_buffer,
254 });
375255
376 res.request.parser.reset();256 return if (res.connection.closing) .closing else .reset;
257}
377258
378 res.request = .{259pub const SendError = Connection.WriteError || error{
379 .version = undefined,260 UnsupportedTransferEncoding,
380 .method = undefined,261 InvalidContentLength,
381 .target = undefined,262};
382 .parser = res.request.parser,
383 };
384263
385 return if (res.connection.closing) .closing else .reset;264/// Send the HTTP response headers to the client.
265pub fn send(res: *Server) SendError!void {
266 switch (res.state) {
267 .waited => res.state = .responded,
268 .first, .start, .responded, .finished => unreachable,
386 }269 }
387270
388 pub const SendError = Connection.WriteError || error{271 var buffered = std.io.bufferedWriter(res.connection.writer());
389 UnsupportedTransferEncoding,272 const w = buffered.writer();
390 InvalidContentLength,273
391 };274 try w.writeAll(@tagName(res.version));
392275 try w.writeByte(' ');
393 /// Send the HTTP response headers to the client.276 try w.print("{d}", .{@intFromEnum(res.status)});
394 pub fn send(res: *Response) SendError!void {277 try w.writeByte(' ');
395 switch (res.state) {278 if (res.reason) |reason| {
396 .waited => res.state = .responded,279 try w.writeAll(reason);
397 .first, .start, .responded, .finished => unreachable,280 } else if (res.status.phrase()) |phrase| {
398 }281 try w.writeAll(phrase);
399282 }
400 var buffered = std.io.bufferedWriter(res.connection.writer());283 try w.writeAll("\r\n");
401 const w = buffered.writer();
402
403 try w.writeAll(@tagName(res.version));
404 try w.writeByte(' ');
405 try w.print("{d}", .{@intFromEnum(res.status)});
406 try w.writeByte(' ');
407 if (res.reason) |reason| {
408 try w.writeAll(reason);
409 } else if (res.status.phrase()) |phrase| {
410 try w.writeAll(phrase);
411 }
412 try w.writeAll("\r\n");
413284
414 if (res.status == .@"continue") {285 if (res.status == .@"continue") {
415 res.state = .waited; // we still need to send another request after this286 res.state = .waited; // we still need to send another request after this
287 } else {
288 if (res.keep_alive and res.request.keep_alive) {
289 try w.writeAll("connection: keep-alive\r\n");
416 } else {290 } else {
417 if (res.keep_alive and res.request.keep_alive) {291 try w.writeAll("connection: close\r\n");
418 try w.writeAll("connection: keep-alive\r\n");
419 } else {
420 try w.writeAll("connection: close\r\n");
421 }
422
423 switch (res.transfer_encoding) {
424 .chunked => try w.writeAll("transfer-encoding: chunked\r\n"),
425 .content_length => |content_length| try w.print("content-length: {d}\r\n", .{content_length}),
426 .none => {},
427 }
428
429 for (res.extra_headers) |header| {
430 try w.print("{s}: {s}\r\n", .{ header.name, header.value });
431 }
432 }292 }
433293
434 if (res.request.method == .HEAD) {294 switch (res.transfer_encoding) {
435 res.transfer_encoding = .none;295 .chunked => try w.writeAll("transfer-encoding: chunked\r\n"),
296 .content_length => |content_length| try w.print("content-length: {d}\r\n", .{content_length}),
297 .none => {},
436 }298 }
437299
438 try w.writeAll("\r\n");300 for (res.extra_headers) |header| {
439301 try w.print("{s}: {s}\r\n", .{ header.name, header.value });
440 try buffered.flush();
441 }
442
443 const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
444
445 const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead);
446
447 fn transferReader(res: *Response) TransferReader {
448 return .{ .context = res };
449 }
450
451 fn transferRead(res: *Response, buf: []u8) TransferReadError!usize {
452 if (res.request.parser.done) return 0;
453
454 var index: usize = 0;
455 while (index == 0) {
456 const amt = try res.request.parser.read(&res.connection, buf[index..], false);
457 if (amt == 0 and res.request.parser.done) break;
458 index += amt;
459 }302 }
460
461 return index;
462 }303 }
463304
464 pub const WaitError = Connection.ReadError ||305 if (res.request.method == .HEAD) {
465 proto.HeadersParser.CheckCompleteHeadError || Request.ParseError ||306 res.transfer_encoding = .none;
466 error{CompressionUnsupported};307 }
467
468 /// Wait for the client to send a complete request head.
469 ///
470 /// For correct behavior, the following rules must be followed:
471 ///
472 /// * If this returns any error in `Connection.ReadError`, you MUST
473 /// immediately close the connection by calling `deinit`.
474 /// * If this returns `error.HttpHeadersInvalid`, you MAY immediately close
475 /// the connection by calling `deinit`.
476 /// * If this returns `error.HttpHeadersOversize`, you MUST
477 /// respond with a 431 status code and then call `deinit`.
478 /// * If this returns any error in `Request.ParseError`, you MUST respond
479 /// with a 400 status code and then call `deinit`.
480 /// * If this returns any other error, you MUST respond with a 400 status
481 /// code and then call `deinit`.
482 /// * If the request has an Expect header containing 100-continue, you MUST either:
483 /// * Respond with a 100 status code, then call `wait` again.
484 /// * Respond with a 417 status code.
485 pub fn wait(res: *Response) WaitError!void {
486 switch (res.state) {
487 .first, .start => res.state = .waited,
488 .waited, .responded, .finished => unreachable,
489 }
490308
491 while (true) {309 try w.writeAll("\r\n");
492 try res.connection.fill();
493310
494 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());311 try buffered.flush();
495 res.connection.drop(@intCast(nchecked));312}
496313
497 if (res.request.parser.state.isContent()) break;314const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
498 }
499315
500 try res.request.parse(res.request.parser.get());316const TransferReader = std.io.Reader(*Server, TransferReadError, transferRead);
501317
502 switch (res.request.transfer_encoding) {318fn transferReader(res: *Server) TransferReader {
503 .none => {319 return .{ .context = res };
504 if (res.request.content_length) |len| {320}
505 res.request.parser.next_chunk_length = len;
506321
507 if (len == 0) res.request.parser.done = true;322fn transferRead(res: *Server, buf: []u8) TransferReadError!usize {
508 } else {323 if (res.request.parser.done) return 0;
509 res.request.parser.done = true;
510 }
511 },
512 .chunked => {
513 res.request.parser.next_chunk_length = 0;
514 res.request.parser.state = .chunk_head_size;
515 },
516 }
517324
518 if (!res.request.parser.done) {325 var index: usize = 0;
519 switch (res.request.transfer_compression) {326 while (index == 0) {
520 .identity => res.request.compression = .none,327 const amt = try res.request.parser.read(&res.connection, buf[index..], false);
521 .compress, .@"x-compress" => return error.CompressionUnsupported,328 if (amt == 0 and res.request.parser.done) break;
522 .deflate => res.request.compression = .{329 index += amt;
523 .deflate = std.compress.zlib.decompressor(res.transferReader()),
524 },
525 .gzip, .@"x-gzip" => res.request.compression = .{
526 .gzip = std.compress.gzip.decompressor(res.transferReader()),
527 },
528 .zstd => {
529 // https://github.com/ziglang/zig/issues/18937
530 return error.CompressionUnsupported;
531 },
532 }
533 }
534 }330 }
535331
536 pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError || error{ DecompressionFailure, InvalidTrailers };332 return index;
333}
537334
538 pub const Reader = std.io.Reader(*Response, ReadError, read);335pub const WaitError = Connection.ReadError ||
336 proto.HeadersParser.CheckCompleteHeadError || Request.ParseError ||
337 error{CompressionUnsupported};
539338
540 pub fn reader(res: *Response) Reader {339/// Wait for the client to send a complete request head.
541 return .{ .context = res };340///
341/// For correct behavior, the following rules must be followed:
342///
343/// * If this returns any error in `Connection.ReadError`, you MUST
344/// immediately close the connection by calling `deinit`.
345/// * If this returns `error.HttpHeadersInvalid`, you MAY immediately close
346/// the connection by calling `deinit`.
347/// * If this returns `error.HttpHeadersOversize`, you MUST
348/// respond with a 431 status code and then call `deinit`.
349/// * If this returns any error in `Request.ParseError`, you MUST respond
350/// with a 400 status code and then call `deinit`.
351/// * If this returns any other error, you MUST respond with a 400 status
352/// code and then call `deinit`.
353/// * If the request has an Expect header containing 100-continue, you MUST either:
354/// * Respond with a 100 status code, then call `wait` again.
355/// * Respond with a 417 status code.
356pub fn wait(res: *Server) WaitError!void {
357 switch (res.state) {
358 .first, .start => res.state = .waited,
359 .waited, .responded, .finished => unreachable,
542 }360 }
543361
544 /// Reads data from the response body. Must be called after `wait`.362 while (true) {
545 pub fn read(res: *Response, buffer: []u8) ReadError!usize {363 try res.connection.fill();
546 switch (res.state) {
547 .waited, .responded, .finished => {},
548 .first, .start => unreachable,
549 }
550364
551 const out_index = switch (res.request.compression) {365 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());
552 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,366 res.connection.drop(@intCast(nchecked));
553 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
554 // https://github.com/ziglang/zig/issues/18937
555 //.zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
556 else => try res.transferRead(buffer),
557 };
558367
559 if (out_index == 0) {368 if (res.request.parser.state.isContent()) break;
560 const has_trail = !res.request.parser.state.isContent();369 }
561370
562 while (!res.request.parser.state.isContent()) { // read trailing headers371 try res.request.parse(res.request.parser.get());
563 try res.connection.fill();
564372
565 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());373 switch (res.request.transfer_encoding) {
566 res.connection.drop(@intCast(nchecked));374 .none => {
567 }375 if (res.request.content_length) |len| {
376 res.request.parser.next_chunk_length = len;
568377
569 if (has_trail) {378 if (len == 0) res.request.parser.done = true;
570 // The response headers before the trailers are already379 } else {
571 // guaranteed to be valid, so they will always be parsed again380 res.request.parser.done = true;
572 // and cannot return an error.
573 // This will *only* fail for a malformed trailer.
574 res.request.parse(res.request.parser.get()) catch return error.InvalidTrailers;
575 }381 }
576 }382 },
577383 .chunked => {
578 return out_index;384 res.request.parser.next_chunk_length = 0;
385 res.request.parser.state = .chunk_head_size;
386 },
579 }387 }
580388
581 /// Reads data from the response body. Must be called after `wait`.389 if (!res.request.parser.done) {
582 pub fn readAll(res: *Response, buffer: []u8) !usize {390 switch (res.request.transfer_compression) {
583 var index: usize = 0;391 .identity => res.request.compression = .none,
584 while (index < buffer.len) {392 .compress, .@"x-compress" => return error.CompressionUnsupported,
585 const amt = try read(res, buffer[index..]);393 .deflate => res.request.compression = .{
586 if (amt == 0) break;394 .deflate = std.compress.zlib.decompressor(res.transferReader()),
587 index += amt;395 },
396 .gzip, .@"x-gzip" => res.request.compression = .{
397 .gzip = std.compress.gzip.decompressor(res.transferReader()),
398 },
399 .zstd => {
400 // https://github.com/ziglang/zig/issues/18937
401 return error.CompressionUnsupported;
402 },
588 }403 }
589 return index;
590 }404 }
405}
406
407pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError || error{ DecompressionFailure, InvalidTrailers };
591408
592 pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };409pub const Reader = std.io.Reader(*Server, ReadError, read);
593410
594 pub const Writer = std.io.Writer(*Response, WriteError, write);411pub fn reader(res: *Server) Reader {
412 return .{ .context = res };
413}
595414
596 pub fn writer(res: *Response) Writer {415/// Reads data from the response body. Must be called after `wait`.
597 return .{ .context = res };416pub fn read(res: *Server, buffer: []u8) ReadError!usize {
417 switch (res.state) {
418 .waited, .responded, .finished => {},
419 .first, .start => unreachable,
598 }420 }
599421
600 /// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.422 const out_index = switch (res.request.compression) {
601 /// Must be called after `send` and before `finish`.423 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
602 pub fn write(res: *Response, bytes: []const u8) WriteError!usize {424 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
603 switch (res.state) {425 // https://github.com/ziglang/zig/issues/18937
604 .responded => {},426 //.zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
605 .first, .waited, .start, .finished => unreachable,427 else => try res.transferRead(buffer),
606 }428 };
607429
608 switch (res.transfer_encoding) {430 if (out_index == 0) {
609 .chunked => {431 const has_trail = !res.request.parser.state.isContent();
610 if (bytes.len > 0) {
611 try res.connection.writer().print("{x}\r\n", .{bytes.len});
612 try res.connection.writeAll(bytes);
613 try res.connection.writeAll("\r\n");
614 }
615432
616 return bytes.len;433 while (!res.request.parser.state.isContent()) { // read trailing headers
617 },434 try res.connection.fill();
618 .content_length => |*len| {
619 if (len.* < bytes.len) return error.MessageTooLong;
620435
621 const amt = try res.connection.write(bytes);436 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());
622 len.* -= amt;437 res.connection.drop(@intCast(nchecked));
623 return amt;
624 },
625 .none => return error.NotWriteable,
626 }438 }
627 }
628439
629 /// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.440 if (has_trail) {
630 /// Must be called after `send` and before `finish`.441 // The response headers before the trailers are already
631 pub fn writeAll(req: *Response, bytes: []const u8) WriteError!void {442 // guaranteed to be valid, so they will always be parsed again
632 var index: usize = 0;443 // and cannot return an error.
633 while (index < bytes.len) {444 // This will *only* fail for a malformed trailer.
634 index += try write(req, bytes[index..]);445 res.request.parse(res.request.parser.get()) catch return error.InvalidTrailers;
635 }446 }
636 }447 }
637448
638 pub const FinishError = Connection.WriteError || error{MessageNotCompleted};449 return out_index;
639450}
640 /// Finish the body of a request. This notifies the server that you have no more data to send.
641 /// Must be called after `send`.
642 pub fn finish(res: *Response) FinishError!void {
643 switch (res.state) {
644 .responded => res.state = .finished,
645 .first, .waited, .start, .finished => unreachable,
646 }
647451
648 switch (res.transfer_encoding) {452/// Reads data from the response body. Must be called after `wait`.
649 .chunked => try res.connection.writeAll("0\r\n\r\n"),453pub fn readAll(res: *Server, buffer: []u8) !usize {
650 .content_length => |len| if (len != 0) return error.MessageNotCompleted,454 var index: usize = 0;
651 .none => {},455 while (index < buffer.len) {
652 }456 const amt = try read(res, buffer[index..]);
457 if (amt == 0) break;
458 index += amt;
653 }459 }
654};460 return index;
655
656/// Create a new HTTP server.
657pub fn init(options: net.StreamServer.Options) Server {
658 return .{
659 .socket = net.StreamServer.init(options),
660 };
661}461}
662462
663/// Free all resources associated with this server.463pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
664pub fn deinit(server: *Server) void {
665 server.socket.deinit();
666}
667464
668pub const ListenError = std.os.SocketError || std.os.BindError || std.os.ListenError || std.os.SetSockOptError || std.os.GetSockNameError;465pub const Writer = std.io.Writer(*Server, WriteError, write);
669466
670/// Start the HTTP server listening on the given address.467pub fn writer(res: *Server) Writer {
671pub fn listen(server: *Server, address: net.Address) ListenError!void {468 return .{ .context = res };
672 try server.socket.listen(address);
673}469}
674470
675pub const AcceptError = net.StreamServer.AcceptError;471/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
676472/// Must be called after `send` and before `finish`.
677pub const AcceptOptions = struct {473pub fn write(res: *Server, bytes: []const u8) WriteError!usize {
678 /// Externally-owned memory used to store the client's entire HTTP header.474 switch (res.state) {
679 /// `error.HttpHeadersOversize` is returned from read() when a475 .responded => {},
680 /// client sends too many bytes of HTTP headers.476 .first, .waited, .start, .finished => unreachable,
681 client_header_buffer: []u8,477 }
682};
683478
684pub fn accept(server: *Server, options: AcceptOptions) AcceptError!Response {479 switch (res.transfer_encoding) {
685 const in = try server.socket.accept();480 .chunked => {
481 if (bytes.len > 0) {
482 try res.connection.writer().print("{x}\r\n", .{bytes.len});
483 try res.connection.writeAll(bytes);
484 try res.connection.writeAll("\r\n");
485 }
686486
687 return .{487 return bytes.len;
688 .transfer_encoding = .none,
689 .keep_alive = true,
690 .address = in.address,
691 .connection = .{
692 .stream = in.stream,
693 .protocol = .plain,
694 },488 },
695 .request = .{489 .content_length => |*len| {
696 .version = undefined,490 if (len.* < bytes.len) return error.MessageTooLong;
697 .method = undefined,491
698 .target = undefined,492 const amt = try res.connection.write(bytes);
699 .parser = proto.HeadersParser.init(options.client_header_buffer),493 len.* -= amt;
494 return amt;
700 },495 },
701 };496 .none => return error.NotWriteable,
497 }
702}498}
703499
704test "HTTP server handles a chunked transfer coding request" {500/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
705 // This test requires spawning threads.501/// Must be called after `send` and before `finish`.
706 if (builtin.single_threaded) {502pub fn writeAll(req: *Server, bytes: []const u8) WriteError!void {
707 return error.SkipZigTest;503 var index: usize = 0;
504 while (index < bytes.len) {
505 index += try write(req, bytes[index..]);
708 }506 }
507}
709508
710 const native_endian = comptime builtin.cpu.arch.endian();509pub const FinishError = Connection.WriteError || error{MessageNotCompleted};
711 if (builtin.zig_backend == .stage2_llvm and native_endian == .big) {510
712 // https://github.com/ziglang/zig/issues/13782511/// Finish the body of a request. This notifies the server that you have no more data to send.
713 return error.SkipZigTest;512/// Must be called after `send`.
513pub fn finish(res: *Server) FinishError!void {
514 switch (res.state) {
515 .responded => res.state = .finished,
516 .first, .waited, .start, .finished => unreachable,
714 }517 }
715518
716 if (builtin.os.tag == .wasi) return error.SkipZigTest;519 switch (res.transfer_encoding) {
717520 .chunked => try res.connection.writeAll("0\r\n\r\n"),
718 const allocator = std.testing.allocator;521 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
719 const expect = std.testing.expect;522 .none => {},
720523 }
721 const max_header_size = 8192;
722 var server = std.http.Server.init(.{ .reuse_address = true });
723 defer server.deinit();
724
725 const address = try std.net.Address.parseIp("127.0.0.1", 0);
726 try server.listen(address);
727 const server_port = server.socket.listen_address.in.getPort();
728
729 const server_thread = try std.Thread.spawn(.{}, (struct {
730 fn apply(s: *std.http.Server) !void {
731 var header_buffer: [max_header_size]u8 = undefined;
732 var res = try s.accept(.{
733 .allocator = allocator,
734 .client_header_buffer = &header_buffer,
735 });
736 defer res.deinit();
737 defer _ = res.reset();
738 try res.wait();
739
740 try expect(res.request.transfer_encoding == .chunked);
741
742 const server_body: []const u8 = "message from server!\n";
743 res.transfer_encoding = .{ .content_length = server_body.len };
744 res.extra_headers = &.{
745 .{ .name = "content-type", .value = "text/plain" },
746 };
747 res.keep_alive = false;
748 try res.send();
749
750 var buf: [128]u8 = undefined;
751 const n = try res.readAll(&buf);
752 try expect(std.mem.eql(u8, buf[0..n], "ABCD"));
753 _ = try res.writer().writeAll(server_body);
754 try res.finish();
755 }
756 }).apply, .{&server});
757
758 const request_bytes =
759 "POST / HTTP/1.1\r\n" ++
760 "Content-Type: text/plain\r\n" ++
761 "Transfer-Encoding: chunked\r\n" ++
762 "\r\n" ++
763 "1\r\n" ++
764 "A\r\n" ++
765 "1\r\n" ++
766 "B\r\n" ++
767 "2\r\n" ++
768 "CD\r\n" ++
769 "0\r\n" ++
770 "\r\n";
771
772 const stream = try std.net.tcpConnectToHost(allocator, "127.0.0.1", server_port);
773 defer stream.close();
774 _ = try stream.writeAll(request_bytes[0..]);
775
776 server_thread.join();
777}524}
525
526const builtin = @import("builtin");
527const std = @import("../std.zig");
528const testing = std.testing;
529const http = std.http;
530const mem = std.mem;
531const net = std.net;
532const Uri = std.Uri;
533const Allocator = mem.Allocator;
534const assert = std.debug.assert;
535
536const Server = @This();
537const proto = @import("protocol.zig");
lib/std/http/Server/Connection.zig created+132
...@@ -0,0 +1,132 @@
1stream: std.net.Stream,
2protocol: Protocol,
3
4closing: bool,
5
6read_buf: [buffer_size]u8,
7read_start: u16,
8read_end: u16,
9
10pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
11pub const Protocol = enum { plain };
12
13pub fn rawReadAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
14 return switch (conn.protocol) {
15 .plain => conn.stream.readAtLeast(buffer, len),
16 // .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
17 } catch |err| {
18 switch (err) {
19 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
20 else => return error.UnexpectedReadFailure,
21 }
22 };
23}
24
25pub fn fill(conn: *Connection) ReadError!void {
26 if (conn.read_end != conn.read_start) return;
27
28 const nread = try conn.rawReadAtLeast(conn.read_buf[0..], 1);
29 if (nread == 0) return error.EndOfStream;
30 conn.read_start = 0;
31 conn.read_end = @intCast(nread);
32}
33
34pub fn peek(conn: *Connection) []const u8 {
35 return conn.read_buf[conn.read_start..conn.read_end];
36}
37
38pub fn drop(conn: *Connection, num: u16) void {
39 conn.read_start += num;
40}
41
42pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
43 assert(len <= buffer.len);
44
45 var out_index: u16 = 0;
46 while (out_index < len) {
47 const available_read = conn.read_end - conn.read_start;
48 const available_buffer = buffer.len - out_index;
49
50 if (available_read > available_buffer) { // partially read buffered data
51 @memcpy(buffer[out_index..], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]);
52 out_index += @as(u16, @intCast(available_buffer));
53 conn.read_start += @as(u16, @intCast(available_buffer));
54
55 break;
56 } else if (available_read > 0) { // fully read buffered data
57 @memcpy(buffer[out_index..][0..available_read], conn.read_buf[conn.read_start..conn.read_end]);
58 out_index += available_read;
59 conn.read_start += available_read;
60
61 if (out_index >= len) break;
62 }
63
64 const leftover_buffer = available_buffer - available_read;
65 const leftover_len = len - out_index;
66
67 if (leftover_buffer > conn.read_buf.len) {
68 // skip the buffer if the output is large enough
69 return conn.rawReadAtLeast(buffer[out_index..], leftover_len);
70 }
71
72 try conn.fill();
73 }
74
75 return out_index;
76}
77
78pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
79 return conn.readAtLeast(buffer, 1);
80}
81
82pub const ReadError = error{
83 ConnectionTimedOut,
84 ConnectionResetByPeer,
85 UnexpectedReadFailure,
86 EndOfStream,
87};
88
89pub const Reader = std.io.Reader(*Connection, ReadError, read);
90
91pub fn reader(conn: *Connection) Reader {
92 return .{ .context = conn };
93}
94
95pub fn writeAll(conn: *Connection, buffer: []const u8) WriteError!void {
96 return switch (conn.protocol) {
97 .plain => conn.stream.writeAll(buffer),
98 // .tls => return conn.tls_client.writeAll(conn.stream, buffer),
99 } catch |err| switch (err) {
100 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
101 else => return error.UnexpectedWriteFailure,
102 };
103}
104
105pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
106 return switch (conn.protocol) {
107 .plain => conn.stream.write(buffer),
108 // .tls => return conn.tls_client.write(conn.stream, buffer),
109 } catch |err| switch (err) {
110 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
111 else => return error.UnexpectedWriteFailure,
112 };
113}
114
115pub const WriteError = error{
116 ConnectionResetByPeer,
117 UnexpectedWriteFailure,
118};
119
120pub const Writer = std.io.Writer(*Connection, WriteError, write);
121
122pub fn writer(conn: *Connection) Writer {
123 return .{ .context = conn };
124}
125
126pub fn close(conn: *Connection) void {
127 conn.stream.close();
128}
129
130const Connection = @This();
131const std = @import("../../std.zig");
132const assert = std.debug.assert;
lib/std/http/test.zig+79-13
...@@ -8,13 +8,12 @@ test "trailers" {...@@ -8,13 +8,12 @@ test "trailers" {
88
9 const gpa = testing.allocator;9 const gpa = testing.allocator;
1010
11 var http_server = std.http.Server.init(.{11 const address = try std.net.Address.parseIp("127.0.0.1", 0);
12 var http_server = try address.listen(.{
12 .reuse_address = true,13 .reuse_address = true,
13 });14 });
14 const address = try std.net.Address.parseIp("127.0.0.1", 0);
15 try http_server.listen(address);
1615
17 const port = http_server.socket.listen_address.in.getPort();16 const port = http_server.listen_address.in.getPort();
1817
19 const server_thread = try std.Thread.spawn(.{}, serverThread, .{&http_server});18 const server_thread = try std.Thread.spawn(.{}, serverThread, .{&http_server});
20 defer server_thread.join();19 defer server_thread.join();
...@@ -67,17 +66,14 @@ test "trailers" {...@@ -67,17 +66,14 @@ test "trailers" {
67 try testing.expect(client.connection_pool.free_len == 1);66 try testing.expect(client.connection_pool.free_len == 1);
68}67}
6968
70fn serverThread(http_server: *std.http.Server) anyerror!void {69fn serverThread(http_server: *std.net.Server) anyerror!void {
71 const gpa = testing.allocator;
72
73 var header_buffer: [1024]u8 = undefined;70 var header_buffer: [1024]u8 = undefined;
74 var remaining: usize = 1;71 var remaining: usize = 1;
75 accept: while (remaining != 0) : (remaining -= 1) {72 accept: while (remaining != 0) : (remaining -= 1) {
76 var res = try http_server.accept(.{73 const conn = try http_server.accept();
77 .allocator = gpa,74 defer conn.stream.close();
78 .client_header_buffer = &header_buffer,75
79 });76 var res = std.http.Server.init(conn, .{ .client_header_buffer = &header_buffer });
80 defer res.deinit();
8177
82 res.wait() catch |err| switch (err) {78 res.wait() catch |err| switch (err) {
83 error.HttpHeadersInvalid => continue :accept,79 error.HttpHeadersInvalid => continue :accept,
...@@ -90,7 +86,7 @@ fn serverThread(http_server: *std.http.Server) anyerror!void {...@@ -90,7 +86,7 @@ fn serverThread(http_server: *std.http.Server) anyerror!void {
90 }86 }
91}87}
9288
93fn serve(res: *std.http.Server.Response) !void {89fn serve(res: *std.http.Server) !void {
94 try testing.expectEqualStrings(res.request.target, "/trailer");90 try testing.expectEqualStrings(res.request.target, "/trailer");
95 res.transfer_encoding = .chunked;91 res.transfer_encoding = .chunked;
9692
...@@ -99,3 +95,73 @@ fn serve(res: *std.http.Server.Response) !void {...@@ -99,3 +95,73 @@ fn serve(res: *std.http.Server.Response) !void {
99 try res.writeAll("World!\n");95 try res.writeAll("World!\n");
100 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");96 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");
101}97}
98
99test "HTTP server handles a chunked transfer coding request" {
100 // This test requires spawning threads.
101 if (builtin.single_threaded) {
102 return error.SkipZigTest;
103 }
104
105 const native_endian = comptime builtin.cpu.arch.endian();
106 if (builtin.zig_backend == .stage2_llvm and native_endian == .big) {
107 // https://github.com/ziglang/zig/issues/13782
108 return error.SkipZigTest;
109 }
110
111 if (builtin.os.tag == .wasi) return error.SkipZigTest;
112
113 const allocator = std.testing.allocator;
114 const expect = std.testing.expect;
115
116 const max_header_size = 8192;
117
118 const address = try std.net.Address.parseIp("127.0.0.1", 0);
119 var server = try address.listen(.{ .reuse_address = true });
120 defer server.deinit();
121 const server_port = server.listen_address.in.getPort();
122
123 const server_thread = try std.Thread.spawn(.{}, (struct {
124 fn apply(s: *std.net.Server) !void {
125 var header_buffer: [max_header_size]u8 = undefined;
126 const conn = try s.accept();
127 defer conn.stream.close();
128 var res = std.http.Server.init(conn, .{ .client_header_buffer = &header_buffer });
129 try res.wait();
130
131 try expect(res.request.transfer_encoding == .chunked);
132 const server_body: []const u8 = "message from server!\n";
133 res.transfer_encoding = .{ .content_length = server_body.len };
134 res.extra_headers = &.{
135 .{ .name = "content-type", .value = "text/plain" },
136 };
137 res.keep_alive = false;
138 try res.send();
139
140 var buf: [128]u8 = undefined;
141 const n = try res.readAll(&buf);
142 try expect(std.mem.eql(u8, buf[0..n], "ABCD"));
143 _ = try res.writer().writeAll(server_body);
144 try res.finish();
145 }
146 }).apply, .{&server});
147
148 const request_bytes =
149 "POST / HTTP/1.1\r\n" ++
150 "Content-Type: text/plain\r\n" ++
151 "Transfer-Encoding: chunked\r\n" ++
152 "\r\n" ++
153 "1\r\n" ++
154 "A\r\n" ++
155 "1\r\n" ++
156 "B\r\n" ++
157 "2\r\n" ++
158 "CD\r\n" ++
159 "0\r\n" ++
160 "\r\n";
161
162 const stream = try std.net.tcpConnectToHost(allocator, "127.0.0.1", server_port);
163 defer stream.close();
164 _ = try stream.writeAll(request_bytes[0..]);
165
166 server_thread.join();
167}
lib/std/net.zig+91-154
...@@ -4,15 +4,17 @@ const assert = std.debug.assert;...@@ -4,15 +4,17 @@ const assert = std.debug.assert;
4const net = @This();4const net = @This();
5const mem = std.mem;5const mem = std.mem;
6const os = std.os;6const os = std.os;
7const posix = std.posix;
7const fs = std.fs;8const fs = std.fs;
8const io = std.io;9const io = std.io;
9const native_endian = builtin.target.cpu.arch.endian();10const native_endian = builtin.target.cpu.arch.endian();
1011
11// Windows 10 added support for unix sockets in build 17063, redstone 4 is the12// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
12// first release to support them.13// first release to support them.
13pub const has_unix_sockets = @hasDecl(os.sockaddr, "un") and14pub const has_unix_sockets = switch (builtin.os.tag) {
14 (builtin.target.os.tag != .windows or15 .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,
15 builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false);16 else => true,
17};
1618
17pub const IPParseError = error{19pub const IPParseError = error{
18 Overflow,20 Overflow,
...@@ -206,6 +208,57 @@ pub const Address = extern union {...@@ -206,6 +208,57 @@ pub const Address = extern union {
206 else => unreachable,208 else => unreachable,
207 }209 }
208 }210 }
211
212 pub const ListenError = posix.SocketError || posix.BindError || posix.ListenError ||
213 posix.SetSockOptError || posix.GetSockNameError;
214
215 pub const ListenOptions = struct {
216 /// How many connections the kernel will accept on the application's behalf.
217 /// If more than this many connections pool in the kernel, clients will start
218 /// seeing "Connection refused".
219 kernel_backlog: u31 = 128,
220 reuse_address: bool = false,
221 reuse_port: bool = false,
222 force_nonblocking: bool = false,
223 };
224
225 /// The returned `Server` has an open `stream`.
226 pub fn listen(address: Address, options: ListenOptions) ListenError!Server {
227 const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0;
228 const sock_flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC | nonblock;
229 const proto: u32 = if (address.any.family == posix.AF.UNIX) 0 else posix.IPPROTO.TCP;
230
231 const sockfd = try posix.socket(address.any.family, sock_flags, proto);
232 var s: Server = .{
233 .listen_address = undefined,
234 .stream = .{ .handle = sockfd },
235 };
236 errdefer s.stream.close();
237
238 if (options.reuse_address) {
239 try posix.setsockopt(
240 sockfd,
241 posix.SOL.SOCKET,
242 posix.SO.REUSEADDR,
243 &mem.toBytes(@as(c_int, 1)),
244 );
245 }
246
247 if (options.reuse_port) {
248 try posix.setsockopt(
249 sockfd,
250 posix.SOL.SOCKET,
251 posix.SO.REUSEPORT,
252 &mem.toBytes(@as(c_int, 1)),
253 );
254 }
255
256 var socklen = address.getOsSockLen();
257 try posix.bind(sockfd, &address.any, socklen);
258 try posix.listen(sockfd, options.kernel_backlog);
259 try posix.getsockname(sockfd, &s.listen_address.any, &socklen);
260 return s;
261 }
209};262};
210263
211pub const Ip4Address = extern struct {264pub const Ip4Address = extern struct {
...@@ -657,7 +710,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {...@@ -657,7 +710,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {
657 os.SOCK.STREAM | os.SOCK.CLOEXEC | opt_non_block,710 os.SOCK.STREAM | os.SOCK.CLOEXEC | opt_non_block,
658 0,711 0,
659 );712 );
660 errdefer os.closeSocket(sockfd);713 errdefer Stream.close(.{ .handle = sockfd });
661714
662 var addr = try std.net.Address.initUnix(path);715 var addr = try std.net.Address.initUnix(path);
663 try os.connect(sockfd, &addr.any, addr.getOsSockLen());716 try os.connect(sockfd, &addr.any, addr.getOsSockLen());
...@@ -669,7 +722,7 @@ fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 {...@@ -669,7 +722,7 @@ fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 {
669 if (builtin.target.os.tag == .linux) {722 if (builtin.target.os.tag == .linux) {
670 var ifr: os.ifreq = undefined;723 var ifr: os.ifreq = undefined;
671 const sockfd = try os.socket(os.AF.UNIX, os.SOCK.DGRAM | os.SOCK.CLOEXEC, 0);724 const sockfd = try os.socket(os.AF.UNIX, os.SOCK.DGRAM | os.SOCK.CLOEXEC, 0);
672 defer os.closeSocket(sockfd);725 defer Stream.close(.{ .handle = sockfd });
673726
674 @memcpy(ifr.ifrn.name[0..name.len], name);727 @memcpy(ifr.ifrn.name[0..name.len], name);
675 ifr.ifrn.name[name.len] = 0;728 ifr.ifrn.name[name.len] = 0;
...@@ -738,7 +791,7 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {...@@ -738,7 +791,7 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
738 const sock_flags = os.SOCK.STREAM | nonblock |791 const sock_flags = os.SOCK.STREAM | nonblock |
739 (if (builtin.target.os.tag == .windows) 0 else os.SOCK.CLOEXEC);792 (if (builtin.target.os.tag == .windows) 0 else os.SOCK.CLOEXEC);
740 const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO.TCP);793 const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO.TCP);
741 errdefer os.closeSocket(sockfd);794 errdefer Stream.close(.{ .handle = sockfd });
742795
743 try os.connect(sockfd, &address.any, address.getOsSockLen());796 try os.connect(sockfd, &address.any, address.getOsSockLen());
744797
...@@ -1068,7 +1121,7 @@ fn linuxLookupName(...@@ -1068,7 +1121,7 @@ fn linuxLookupName(
1068 var prefixlen: i32 = 0;1121 var prefixlen: i32 = 0;
1069 const sock_flags = os.SOCK.DGRAM | os.SOCK.CLOEXEC;1122 const sock_flags = os.SOCK.DGRAM | os.SOCK.CLOEXEC;
1070 if (os.socket(addr.addr.any.family, sock_flags, os.IPPROTO.UDP)) |fd| syscalls: {1123 if (os.socket(addr.addr.any.family, sock_flags, os.IPPROTO.UDP)) |fd| syscalls: {
1071 defer os.closeSocket(fd);1124 defer Stream.close(.{ .handle = fd });
1072 os.connect(fd, da, dalen) catch break :syscalls;1125 os.connect(fd, da, dalen) catch break :syscalls;
1073 key |= DAS_USABLE;1126 key |= DAS_USABLE;
1074 os.getsockname(fd, sa, &salen) catch break :syscalls;1127 os.getsockname(fd, sa, &salen) catch break :syscalls;
...@@ -1553,7 +1606,7 @@ fn resMSendRc(...@@ -1553,7 +1606,7 @@ fn resMSendRc(
1553 },1606 },
1554 else => |e| return e,1607 else => |e| return e,
1555 };1608 };
1556 defer os.closeSocket(fd);1609 defer Stream.close(.{ .handle = fd });
15571610
1558 // Past this point, there are no errors. Each individual query will1611 // Past this point, there are no errors. Each individual query will
1559 // yield either no reply (indicated by zero length) or an answer1612 // yield either no reply (indicated by zero length) or an answer
...@@ -1729,13 +1782,15 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)...@@ -1729,13 +1782,15 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
1729}1782}
17301783
1731pub const Stream = struct {1784pub const Stream = struct {
1732 // Underlying socket descriptor.1785 /// Underlying platform-defined type which may or may not be
1733 // Note that on some platforms this may not be interchangeable with a1786 /// interchangeable with a file system file descriptor.
1734 // regular files descriptor.1787 handle: posix.socket_t,
1735 handle: os.socket_t,1788
17361789 pub fn close(s: Stream) void {
1737 pub fn close(self: Stream) void {1790 switch (builtin.os.tag) {
1738 os.closeSocket(self.handle);1791 .windows => std.os.windows.closesocket(s.handle) catch unreachable,
1792 else => posix.close(s.handle),
1793 }
1739 }1794 }
17401795
1741 pub const ReadError = os.ReadError;1796 pub const ReadError = os.ReadError;
...@@ -1839,156 +1894,38 @@ pub const Stream = struct {...@@ -1839,156 +1894,38 @@ pub const Stream = struct {
1839 }1894 }
1840};1895};
18411896
1842pub const StreamServer = struct {1897pub const Server = struct {
1843 /// Copied from `Options` on `init`.
1844 kernel_backlog: u31,
1845 reuse_address: bool,
1846 reuse_port: bool,
1847 force_nonblocking: bool,
1848
1849 /// `undefined` until `listen` returns successfully.
1850 listen_address: Address,1898 listen_address: Address,
1899 stream: std.net.Stream,
18511900
1852 sockfd: ?os.socket_t,1901 pub const Connection = struct {
18531902 stream: std.net.Stream,
1854 pub const Options = struct {1903 address: Address,
1855 /// How many connections the kernel will accept on the application's behalf.
1856 /// If more than this many connections pool in the kernel, clients will start
1857 /// seeing "Connection refused".
1858 kernel_backlog: u31 = 128,
1859
1860 /// Enable SO.REUSEADDR on the socket.
1861 reuse_address: bool = false,
1862
1863 /// Enable SO.REUSEPORT on the socket.
1864 reuse_port: bool = false,
1865
1866 /// Force non-blocking mode.
1867 force_nonblocking: bool = false,
1868 };1904 };
18691905
1870 /// After this call succeeds, resources have been acquired and must1906 pub fn deinit(s: *Server) void {
1871 /// be released with `deinit`.1907 s.stream.close();
1872 pub fn init(options: Options) StreamServer {1908 s.* = undefined;
1873 return StreamServer{
1874 .sockfd = null,
1875 .kernel_backlog = options.kernel_backlog,
1876 .reuse_address = options.reuse_address,
1877 .reuse_port = options.reuse_port,
1878 .force_nonblocking = options.force_nonblocking,
1879 .listen_address = undefined,
1880 };
1881 }
1882
1883 /// Release all resources. The `StreamServer` memory becomes `undefined`.
1884 pub fn deinit(self: *StreamServer) void {
1885 self.close();
1886 self.* = undefined;
1887 }
1888
1889 pub fn listen(self: *StreamServer, address: Address) !void {
1890 const nonblock = 0;
1891 const sock_flags = os.SOCK.STREAM | os.SOCK.CLOEXEC | nonblock;
1892 var use_sock_flags: u32 = sock_flags;
1893 if (self.force_nonblocking) use_sock_flags |= os.SOCK.NONBLOCK;
1894 const proto = if (address.any.family == os.AF.UNIX) @as(u32, 0) else os.IPPROTO.TCP;
1895
1896 const sockfd = try os.socket(address.any.family, use_sock_flags, proto);
1897 self.sockfd = sockfd;
1898 errdefer {
1899 os.closeSocket(sockfd);
1900 self.sockfd = null;
1901 }
1902
1903 if (self.reuse_address) {
1904 try os.setsockopt(
1905 sockfd,
1906 os.SOL.SOCKET,
1907 os.SO.REUSEADDR,
1908 &mem.toBytes(@as(c_int, 1)),
1909 );
1910 }
1911 if (@hasDecl(os.SO, "REUSEPORT") and self.reuse_port) {
1912 try os.setsockopt(
1913 sockfd,
1914 os.SOL.SOCKET,
1915 os.SO.REUSEPORT,
1916 &mem.toBytes(@as(c_int, 1)),
1917 );
1918 }
1919
1920 var socklen = address.getOsSockLen();
1921 try os.bind(sockfd, &address.any, socklen);
1922 try os.listen(sockfd, self.kernel_backlog);
1923 try os.getsockname(sockfd, &self.listen_address.any, &socklen);
1924 }
1925
1926 /// Stop listening. It is still necessary to call `deinit` after stopping listening.
1927 /// Calling `deinit` will automatically call `close`. It is safe to call `close` when
1928 /// not listening.
1929 pub fn close(self: *StreamServer) void {
1930 if (self.sockfd) |fd| {
1931 os.closeSocket(fd);
1932 self.sockfd = null;
1933 self.listen_address = undefined;
1934 }
1935 }1909 }
19361910
1937 pub const AcceptError = error{1911 pub const AcceptError = posix.AcceptError;
1938 ConnectionAborted,
1939
1940 /// The per-process limit on the number of open file descriptors has been reached.
1941 ProcessFdQuotaExceeded,
1942
1943 /// The system-wide limit on the total number of open files has been reached.
1944 SystemFdQuotaExceeded,
1945
1946 /// Not enough free memory. This often means that the memory allocation
1947 /// is limited by the socket buffer limits, not by the system memory.
1948 SystemResources,
1949
1950 /// Socket is not listening for new connections.
1951 SocketNotListening,
1952
1953 ProtocolFailure,
1954
1955 /// Socket is in non-blocking mode and there is no connection to accept.
1956 WouldBlock,
1957
1958 /// Firewall rules forbid connection.
1959 BlockedByFirewall,
1960
1961 FileDescriptorNotASocket,
1962
1963 ConnectionResetByPeer,
1964
1965 NetworkSubsystemFailed,
19661912
1967 OperationNotSupported,1913 /// Blocks until a client connects to the server. The returned `Connection` has
1968 } || os.UnexpectedError;1914 /// an open stream.
19691915 pub fn accept(s: *Server) AcceptError!Connection {
1970 pub const Connection = struct {
1971 stream: Stream,
1972 address: Address,
1973 };
1974
1975 /// If this function succeeds, the returned `Connection` is a caller-managed resource.
1976 pub fn accept(self: *StreamServer) AcceptError!Connection {
1977 var accepted_addr: Address = undefined;1916 var accepted_addr: Address = undefined;
1978 var adr_len: os.socklen_t = @sizeOf(Address);1917 var addr_len: posix.socklen_t = @sizeOf(Address);
1979 const accept_result = os.accept(self.sockfd.?, &accepted_addr.any, &adr_len, os.SOCK.CLOEXEC);1918 const fd = try posix.accept(s.stream.handle, &accepted_addr.any, &addr_len, posix.SOCK.CLOEXEC);
19801919 return .{
1981 if (accept_result) |fd| {1920 .stream = .{ .handle = fd },
1982 return Connection{1921 .address = accepted_addr,
1983 .stream = Stream{ .handle = fd },1922 };
1984 .address = accepted_addr,
1985 };
1986 } else |err| {
1987 return err;
1988 }
1989 }1923 }
1990};1924};
19911925
1992test {1926test {
1993 _ = @import("net/test.zig");1927 _ = @import("net/test.zig");
1928 _ = Server;
1929 _ = Stream;
1930 _ = Address;
1994}1931}
lib/std/net/test.zig+8-18
...@@ -181,11 +181,9 @@ test "listen on a port, send bytes, receive bytes" {...@@ -181,11 +181,9 @@ test "listen on a port, send bytes, receive bytes" {
181 // configured.181 // configured.
182 const localhost = try net.Address.parseIp("127.0.0.1", 0);182 const localhost = try net.Address.parseIp("127.0.0.1", 0);
183183
184 var server = net.StreamServer.init(.{});184 var server = try localhost.listen(.{});
185 defer server.deinit();185 defer server.deinit();
186186
187 try server.listen(localhost);
188
189 const S = struct {187 const S = struct {
190 fn clientFn(server_address: net.Address) !void {188 fn clientFn(server_address: net.Address) !void {
191 const socket = try net.tcpConnectToAddress(server_address);189 const socket = try net.tcpConnectToAddress(server_address);
...@@ -215,17 +213,11 @@ test "listen on an in use port" {...@@ -215,17 +213,11 @@ test "listen on an in use port" {
215213
216 const localhost = try net.Address.parseIp("127.0.0.1", 0);214 const localhost = try net.Address.parseIp("127.0.0.1", 0);
217215
218 var server1 = net.StreamServer.init(net.StreamServer.Options{216 var server1 = try localhost.listen(.{ .reuse_port = true });
219 .reuse_port = true,
220 });
221 defer server1.deinit();217 defer server1.deinit();
222 try server1.listen(localhost);
223218
224 var server2 = net.StreamServer.init(net.StreamServer.Options{219 var server2 = try server1.listen_address.listen(.{ .reuse_port = true });
225 .reuse_port = true,
226 });
227 defer server2.deinit();220 defer server2.deinit();
228 try server2.listen(server1.listen_address);
229}221}
230222
231fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {223fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
...@@ -252,7 +244,7 @@ fn testClient(addr: net.Address) anyerror!void {...@@ -252,7 +244,7 @@ fn testClient(addr: net.Address) anyerror!void {
252 try testing.expect(mem.eql(u8, msg, "hello from server\n"));244 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
253}245}
254246
255fn testServer(server: *net.StreamServer) anyerror!void {247fn testServer(server: *net.Server) anyerror!void {
256 if (builtin.os.tag == .wasi) return error.SkipZigTest;248 if (builtin.os.tag == .wasi) return error.SkipZigTest;
257249
258 var client = try server.accept();250 var client = try server.accept();
...@@ -274,15 +266,14 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -274,15 +266,14 @@ test "listen on a unix socket, send bytes, receive bytes" {
274 }266 }
275 }267 }
276268
277 var server = net.StreamServer.init(.{});
278 defer server.deinit();
279
280 const socket_path = try generateFileName("socket.unix");269 const socket_path = try generateFileName("socket.unix");
281 defer testing.allocator.free(socket_path);270 defer testing.allocator.free(socket_path);
282271
283 const socket_addr = try net.Address.initUnix(socket_path);272 const socket_addr = try net.Address.initUnix(socket_path);
284 defer std.fs.cwd().deleteFile(socket_path) catch {};273 defer std.fs.cwd().deleteFile(socket_path) catch {};
285 try server.listen(socket_addr);274
275 var server = try socket_addr.listen(.{});
276 defer server.deinit();
286277
287 const S = struct {278 const S = struct {
288 fn clientFn(path: []const u8) !void {279 fn clientFn(path: []const u8) !void {
...@@ -323,9 +314,8 @@ test "non-blocking tcp server" {...@@ -323,9 +314,8 @@ test "non-blocking tcp server" {
323 }314 }
324315
325 const localhost = try net.Address.parseIp("127.0.0.1", 0);316 const localhost = try net.Address.parseIp("127.0.0.1", 0);
326 var server = net.StreamServer.init(.{ .force_nonblocking = true });317 var server = localhost.listen(.{ .force_nonblocking = true });
327 defer server.deinit();318 defer server.deinit();
328 try server.listen(localhost);
329319
330 const accept_err = server.accept();320 const accept_err = server.accept();
331 try testing.expectError(error.WouldBlock, accept_err);321 try testing.expectError(error.WouldBlock, accept_err);
lib/std/os.zig-8
...@@ -3598,14 +3598,6 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {...@@ -3598,14 +3598,6 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {
3598 }3598 }
3599}3599}
36003600
3601pub fn closeSocket(sock: socket_t) void {
3602 if (builtin.os.tag == .windows) {
3603 windows.closesocket(sock) catch unreachable;
3604 } else {
3605 close(sock);
3606 }
3607}
3608
3609pub const BindError = error{3601pub const BindError = error{
3610 /// The address is protected, and the user is not the superuser.3602 /// The address is protected, and the user is not the superuser.
3611 /// For UNIX domain sockets: Search permission is denied on a component3603 /// For UNIX domain sockets: Search permission is denied on a component
lib/std/os/linux/io_uring.zig+16-15
...@@ -4,6 +4,7 @@ const assert = std.debug.assert;...@@ -4,6 +4,7 @@ const assert = std.debug.assert;
4const mem = std.mem;4const mem = std.mem;
5const net = std.net;5const net = std.net;
6const os = std.os;6const os = std.os;
7const posix = std.posix;
7const linux = os.linux;8const linux = os.linux;
8const testing = std.testing;9const testing = std.testing;
910
...@@ -3730,8 +3731,8 @@ const SocketTestHarness = struct {...@@ -3730,8 +3731,8 @@ const SocketTestHarness = struct {
3730 client: os.socket_t,3731 client: os.socket_t,
37313732
3732 fn close(self: SocketTestHarness) void {3733 fn close(self: SocketTestHarness) void {
3733 os.closeSocket(self.client);3734 posix.close(self.client);
3734 os.closeSocket(self.listener);3735 posix.close(self.listener);
3735 }3736 }
3736};3737};
37373738
...@@ -3739,7 +3740,7 @@ fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {...@@ -3739,7 +3740,7 @@ fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
3739 // Create a TCP server socket3740 // Create a TCP server socket
3740 var address = try net.Address.parseIp4("127.0.0.1", 0);3741 var address = try net.Address.parseIp4("127.0.0.1", 0);
3741 const listener_socket = try createListenerSocket(&address);3742 const listener_socket = try createListenerSocket(&address);
3742 errdefer os.closeSocket(listener_socket);3743 errdefer posix.close(listener_socket);
37433744
3744 // Submit 1 accept3745 // Submit 1 accept
3745 var accept_addr: os.sockaddr = undefined;3746 var accept_addr: os.sockaddr = undefined;
...@@ -3748,7 +3749,7 @@ fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {...@@ -3748,7 +3749,7 @@ fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
37483749
3749 // Create a TCP client socket3750 // Create a TCP client socket
3750 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);3751 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
3751 errdefer os.closeSocket(client);3752 errdefer posix.close(client);
3752 _ = try ring.connect(0xcccccccc, client, &address.any, address.getOsSockLen());3753 _ = try ring.connect(0xcccccccc, client, &address.any, address.getOsSockLen());
37533754
3754 try testing.expectEqual(@as(u32, 2), try ring.submit());3755 try testing.expectEqual(@as(u32, 2), try ring.submit());
...@@ -3788,7 +3789,7 @@ fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {...@@ -3788,7 +3789,7 @@ fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
3788fn createListenerSocket(address: *net.Address) !os.socket_t {3789fn createListenerSocket(address: *net.Address) !os.socket_t {
3789 const kernel_backlog = 1;3790 const kernel_backlog = 1;
3790 const listener_socket = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);3791 const listener_socket = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
3791 errdefer os.closeSocket(listener_socket);3792 errdefer posix.close(listener_socket);
37923793
3793 try os.setsockopt(listener_socket, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));3794 try os.setsockopt(listener_socket, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
3794 try os.bind(listener_socket, &address.any, address.getOsSockLen());3795 try os.bind(listener_socket, &address.any, address.getOsSockLen());
...@@ -3813,7 +3814,7 @@ test "accept multishot" {...@@ -3813,7 +3814,7 @@ test "accept multishot" {
38133814
3814 var address = try net.Address.parseIp4("127.0.0.1", 0);3815 var address = try net.Address.parseIp4("127.0.0.1", 0);
3815 const listener_socket = try createListenerSocket(&address);3816 const listener_socket = try createListenerSocket(&address);
3816 defer os.closeSocket(listener_socket);3817 defer posix.close(listener_socket);
38173818
3818 // submit multishot accept operation3819 // submit multishot accept operation
3819 var addr: os.sockaddr = undefined;3820 var addr: os.sockaddr = undefined;
...@@ -3826,7 +3827,7 @@ test "accept multishot" {...@@ -3826,7 +3827,7 @@ test "accept multishot" {
3826 while (nr > 0) : (nr -= 1) {3827 while (nr > 0) : (nr -= 1) {
3827 // connect client3828 // connect client
3828 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);3829 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
3829 errdefer os.closeSocket(client);3830 errdefer posix.close(client);
3830 try os.connect(client, &address.any, address.getOsSockLen());3831 try os.connect(client, &address.any, address.getOsSockLen());
38313832
3832 // test accept completion3833 // test accept completion
...@@ -3836,7 +3837,7 @@ test "accept multishot" {...@@ -3836,7 +3837,7 @@ test "accept multishot" {
3836 try testing.expect(cqe.user_data == userdata);3837 try testing.expect(cqe.user_data == userdata);
3837 try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE > 0); // more flag is set3838 try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE > 0); // more flag is set
38383839
3839 os.closeSocket(client);3840 posix.close(client);
3840 }3841 }
3841}3842}
38423843
...@@ -3909,7 +3910,7 @@ test "accept_direct" {...@@ -3909,7 +3910,7 @@ test "accept_direct" {
3909 try ring.register_files(registered_fds[0..]);3910 try ring.register_files(registered_fds[0..]);
39103911
3911 const listener_socket = try createListenerSocket(&address);3912 const listener_socket = try createListenerSocket(&address);
3912 defer os.closeSocket(listener_socket);3913 defer posix.close(listener_socket);
39133914
3914 const accept_userdata: u64 = 0xaaaaaaaa;3915 const accept_userdata: u64 = 0xaaaaaaaa;
3915 const read_userdata: u64 = 0xbbbbbbbb;3916 const read_userdata: u64 = 0xbbbbbbbb;
...@@ -3927,7 +3928,7 @@ test "accept_direct" {...@@ -3927,7 +3928,7 @@ test "accept_direct" {
3927 // connect3928 // connect
3928 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);3929 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
3929 try os.connect(client, &address.any, address.getOsSockLen());3930 try os.connect(client, &address.any, address.getOsSockLen());
3930 defer os.closeSocket(client);3931 defer posix.close(client);
39313932
3932 // accept completion3933 // accept completion
3933 const cqe_accept = try ring.copy_cqe();3934 const cqe_accept = try ring.copy_cqe();
...@@ -3961,7 +3962,7 @@ test "accept_direct" {...@@ -3961,7 +3962,7 @@ test "accept_direct" {
3961 // connect3962 // connect
3962 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);3963 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
3963 try os.connect(client, &address.any, address.getOsSockLen());3964 try os.connect(client, &address.any, address.getOsSockLen());
3964 defer os.closeSocket(client);3965 defer posix.close(client);
3965 // completion with error3966 // completion with error
3966 const cqe_accept = try ring.copy_cqe();3967 const cqe_accept = try ring.copy_cqe();
3967 try testing.expect(cqe_accept.user_data == accept_userdata);3968 try testing.expect(cqe_accept.user_data == accept_userdata);
...@@ -3989,7 +3990,7 @@ test "accept_multishot_direct" {...@@ -3989,7 +3990,7 @@ test "accept_multishot_direct" {
3989 try ring.register_files(registered_fds[0..]);3990 try ring.register_files(registered_fds[0..]);
39903991
3991 const listener_socket = try createListenerSocket(&address);3992 const listener_socket = try createListenerSocket(&address);
3992 defer os.closeSocket(listener_socket);3993 defer posix.close(listener_socket);
39933994
3994 const accept_userdata: u64 = 0xaaaaaaaa;3995 const accept_userdata: u64 = 0xaaaaaaaa;
39953996
...@@ -4003,7 +4004,7 @@ test "accept_multishot_direct" {...@@ -4003,7 +4004,7 @@ test "accept_multishot_direct" {
4003 // connect4004 // connect
4004 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);4005 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
4005 try os.connect(client, &address.any, address.getOsSockLen());4006 try os.connect(client, &address.any, address.getOsSockLen());
4006 defer os.closeSocket(client);4007 defer posix.close(client);
40074008
4008 // accept completion4009 // accept completion
4009 const cqe_accept = try ring.copy_cqe();4010 const cqe_accept = try ring.copy_cqe();
...@@ -4018,7 +4019,7 @@ test "accept_multishot_direct" {...@@ -4018,7 +4019,7 @@ test "accept_multishot_direct" {
4018 // connect4019 // connect
4019 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);4020 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
4020 try os.connect(client, &address.any, address.getOsSockLen());4021 try os.connect(client, &address.any, address.getOsSockLen());
4021 defer os.closeSocket(client);4022 defer posix.close(client);
4022 // completion with error4023 // completion with error
4023 const cqe_accept = try ring.copy_cqe();4024 const cqe_accept = try ring.copy_cqe();
4024 try testing.expect(cqe_accept.user_data == accept_userdata);4025 try testing.expect(cqe_accept.user_data == accept_userdata);
...@@ -4092,7 +4093,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {...@@ -4092,7 +4093,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {
4092 // use sockets from registered_fds in connect operation4093 // use sockets from registered_fds in connect operation
4093 var address = try net.Address.parseIp4("127.0.0.1", 0);4094 var address = try net.Address.parseIp4("127.0.0.1", 0);
4094 const listener_socket = try createListenerSocket(&address);4095 const listener_socket = try createListenerSocket(&address);
4095 defer os.closeSocket(listener_socket);4096 defer posix.close(listener_socket);
4096 const accept_userdata: u64 = 0xaaaaaaaa;4097 const accept_userdata: u64 = 0xaaaaaaaa;
4097 const connect_userdata: u64 = 0xbbbbbbbb;4098 const connect_userdata: u64 = 0xbbbbbbbb;
4098 const close_userdata: u64 = 0xcccccccc;4099 const close_userdata: u64 = 0xcccccccc;
lib/std/os/test.zig+1-1
...@@ -817,7 +817,7 @@ test "shutdown socket" {...@@ -817,7 +817,7 @@ test "shutdown socket" {
817 error.SocketNotConnected => {},817 error.SocketNotConnected => {},
818 else => |e| return e,818 else => |e| return e,
819 };819 };
820 os.closeSocket(sock);820 std.net.Stream.close(.{ .handle = sock });
821}821}
822822
823test "sigaction" {823test "sigaction" {
src/main.zig+3-3
...@@ -3322,13 +3322,13 @@ fn buildOutputType(...@@ -3322,13 +3322,13 @@ fn buildOutputType(
3322 .ip4 => |ip4_addr| {3322 .ip4 => |ip4_addr| {
3323 if (build_options.only_core_functionality) unreachable;3323 if (build_options.only_core_functionality) unreachable;
33243324
3325 var server = std.net.StreamServer.init(.{3325 const addr: std.net.Address = .{ .in = ip4_addr };
3326
3327 var server = try addr.listen(.{
3326 .reuse_address = true,3328 .reuse_address = true,
3327 });3329 });
3328 defer server.deinit();3330 defer server.deinit();
33293331
3330 try server.listen(.{ .in = ip4_addr });
3331
3332 const conn = try server.accept();3332 const conn = try server.accept();
3333 defer conn.stream.close();3333 defer conn.stream.close();
33343334
test/standalone/http.zig+24-29
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const std = @import("std");1const std = @import("std");
22
3const http = std.http;3const http = std.http;
4const Server = http.Server;
5const Client = http.Client;
64
7const mem = std.mem;5const mem = std.mem;
8const testing = std.testing;6const testing = std.testing;
...@@ -19,9 +17,7 @@ var gpa_client = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 })...@@ -19,9 +17,7 @@ var gpa_client = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 })
19const salloc = gpa_server.allocator();17const salloc = gpa_server.allocator();
20const calloc = gpa_client.allocator();18const calloc = gpa_client.allocator();
2119
22var server: Server = undefined;20fn handleRequest(res: *http.Server, listen_port: u16) !void {
23
24fn handleRequest(res: *Server.Response) !void {
25 const log = std.log.scoped(.server);21 const log = std.log.scoped(.server);
2622
27 log.info("{} {s} {s}", .{ res.request.method, @tagName(res.request.version), res.request.target });23 log.info("{} {s} {s}", .{ res.request.method, @tagName(res.request.version), res.request.target });
...@@ -125,7 +121,9 @@ fn handleRequest(res: *Server.Response) !void {...@@ -125,7 +121,9 @@ fn handleRequest(res: *Server.Response) !void {
125 } else if (mem.eql(u8, res.request.target, "/redirect/3")) {121 } else if (mem.eql(u8, res.request.target, "/redirect/3")) {
126 res.transfer_encoding = .chunked;122 res.transfer_encoding = .chunked;
127123
128 const location = try std.fmt.allocPrint(salloc, "http://127.0.0.1:{d}/redirect/2", .{server.socket.listen_address.getPort()});124 const location = try std.fmt.allocPrint(salloc, "http://127.0.0.1:{d}/redirect/2", .{
125 listen_port,
126 });
129 defer salloc.free(location);127 defer salloc.free(location);
130128
131 res.status = .found;129 res.status = .found;
...@@ -168,14 +166,15 @@ fn handleRequest(res: *Server.Response) !void {...@@ -168,14 +166,15 @@ fn handleRequest(res: *Server.Response) !void {
168166
169var handle_new_requests = true;167var handle_new_requests = true;
170168
171fn runServer(srv: *Server) !void {169fn runServer(server: *std.net.Server) !void {
172 var client_header_buffer: [1024]u8 = undefined;170 var client_header_buffer: [1024]u8 = undefined;
173 outer: while (handle_new_requests) {171 outer: while (handle_new_requests) {
174 var res = try srv.accept(.{172 var connection = try server.accept();
175 .allocator = salloc,173 defer connection.stream.close();
174
175 var res = http.Server.init(connection, .{
176 .client_header_buffer = &client_header_buffer,176 .client_header_buffer = &client_header_buffer,
177 });177 });
178 defer res.deinit();
179178
180 while (res.reset() != .closing) {179 while (res.reset() != .closing) {
181 res.wait() catch |err| switch (err) {180 res.wait() catch |err| switch (err) {
...@@ -184,16 +183,15 @@ fn runServer(srv: *Server) !void {...@@ -184,16 +183,15 @@ fn runServer(srv: *Server) !void {
184 else => return err,183 else => return err,
185 };184 };
186185
187 try handleRequest(&res);186 try handleRequest(&res, server.listen_address.getPort());
188 }187 }
189 }188 }
190}189}
191190
192fn serverThread(srv: *Server) void {191fn serverThread(server: *std.net.Server) void {
193 defer srv.deinit();
194 defer _ = gpa_server.deinit();192 defer _ = gpa_server.deinit();
195193
196 runServer(srv) catch |err| {194 runServer(server) catch |err| {
197 std.debug.print("server error: {}\n", .{err});195 std.debug.print("server error: {}\n", .{err});
198196
199 if (@errorReturnTrace()) |trace| {197 if (@errorReturnTrace()) |trace| {
...@@ -205,18 +203,10 @@ fn serverThread(srv: *Server) void {...@@ -205,18 +203,10 @@ fn serverThread(srv: *Server) void {
205 };203 };
206}204}
207205
208fn killServer(addr: std.net.Address) void {
209 handle_new_requests = false;
210
211 const conn = std.net.tcpConnectToAddress(addr) catch return;
212 conn.close();
213}
214
215fn getUnusedTcpPort() !u16 {206fn getUnusedTcpPort() !u16 {
216 const addr = try std.net.Address.parseIp("127.0.0.1", 0);207 const addr = try std.net.Address.parseIp("127.0.0.1", 0);
217 var s = std.net.StreamServer.init(.{});208 var s = try addr.listen(.{});
218 defer s.deinit();209 defer s.deinit();
219 try s.listen(addr);
220 return s.listen_address.in.getPort();210 return s.listen_address.in.getPort();
221}211}
222212
...@@ -225,16 +215,15 @@ pub fn main() !void {...@@ -225,16 +215,15 @@ pub fn main() !void {
225215
226 defer _ = gpa_client.deinit();216 defer _ = gpa_client.deinit();
227217
228 server = Server.init(.{ .reuse_address = true });
229
230 const addr = std.net.Address.parseIp("127.0.0.1", 0) catch unreachable;218 const addr = std.net.Address.parseIp("127.0.0.1", 0) catch unreachable;
231 try server.listen(addr);219 var server = try addr.listen(.{ .reuse_address = true });
220 defer server.deinit();
232221
233 const port = server.socket.listen_address.getPort();222 const port = server.listen_address.getPort();
234223
235 const server_thread = try std.Thread.spawn(.{}, serverThread, .{&server});224 const server_thread = try std.Thread.spawn(.{}, serverThread, .{&server});
236225
237 var client = Client{ .allocator = calloc };226 var client: http.Client = .{ .allocator = calloc };
238 errdefer client.deinit();227 errdefer client.deinit();
239 // defer client.deinit(); handled below228 // defer client.deinit(); handled below
240229
...@@ -691,6 +680,12 @@ pub fn main() !void {...@@ -691,6 +680,12 @@ pub fn main() !void {
691680
692 client.deinit();681 client.deinit();
693682
694 killServer(server.socket.listen_address);683 {
684 handle_new_requests = false;
685
686 const conn = std.net.tcpConnectToAddress(server.listen_address) catch return;
687 conn.close();
688 }
689
695 server_thread.join();690 server_thread.join();
696}691}