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//! HTTP Server implementation.
2//!
3//! This server assumes clients are well behaved and standard compliant; it
4//! deadlocks if a client holds a connection open without sending a request.
1version: http.Version,
2status: http.Status,
3reason: ?[]const u8,
4transfer_encoding: ResponseTransfer,
5keep_alive: bool,
6connection: Connection,
57
6const builtin = @import("builtin");
7const std = @import("../std.zig");
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,
8/// Externally-owned; must outlive the Server.
9extra_headers: []const http.Header,
2610
27 closing: bool = true,
28
29 read_buf: [buffer_size]u8 = undefined,
30 read_start: u16 = 0,
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 }
11/// The HTTP request that this response is responding to.
12///
13/// This field is only valid after calling `wait`.
14request: Request,
12715
128 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
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 }
16state: State = .first,
13717
138 pub const WriteError = error{
139 ConnectionResetByPeer,
140 UnexpectedWriteFailure,
18/// Initialize an HTTP server that can respond to multiple requests on the same
19/// connection.
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 = &.{},
14138 };
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 {
146 return Writer{ .context = conn };
147 }
49pub const ResetState = enum { reset, closing };
14850
149 pub fn close(conn: *Connection) void {
150 conn.stream.close();
151 }
152};
51pub const Connection = @import("Server/Connection.zig");
15352
15453/// The mode of transport for responses.
15554pub const ResponseTransfer = union(enum) {
......@@ -160,10 +59,10 @@ pub const ResponseTransfer = union(enum) {
16059
16160/// The decompressor for request messages.
16261pub const Compression = union(enum) {
163 pub const DeflateDecompressor = std.compress.zlib.Decompressor(Response.TransferReader);
164 pub const GzipDecompressor = std.compress.gzip.Decompressor(Response.TransferReader);
62 pub const DeflateDecompressor = std.compress.zlib.Decompressor(Server.TransferReader);
63 pub const GzipDecompressor = std.compress.gzip.Decompressor(Server.TransferReader);
16564 // 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
16867 deflate: DeflateDecompressor,
16968 gzip: GzipDecompressor,
......@@ -177,14 +76,37 @@ pub const Request = struct {
17776 method: http.Method,
17877 target: []const u8,
17978 version: http.Version,
180 expect: ?[]const u8 = null,
181 content_type: ?[]const u8 = null,
182 content_length: ?u64 = null,
183 transfer_encoding: http.TransferEncoding = .none,
184 transfer_compression: http.ContentEncoding = .identity,
185 keep_alive: bool = false,
79 expect: ?[]const u8,
80 content_type: ?[]const u8,
81 content_length: ?u64,
82 transfer_encoding: http.TransferEncoding,
83 transfer_compression: http.ContentEncoding,
84 keep_alive: bool,
18685 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
189111 pub const ParseError = Allocator.Error || error{
190112 UnknownHttpMethod,
......@@ -300,478 +222,316 @@ pub const Request = struct {
300222 }
301223};
302224
303/// A HTTP response waiting to be sent.
304///
305/// Order of operations:
306/// ```
307/// [/ <--------------------------------------- \]
308/// accept -> wait -> send [ -> write -> finish][ -> 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();
225/// Reset this response to its initial state. This must be called before
226/// handling a second request on the same connection.
227pub fn reset(res: *Server) ResetState {
228 if (res.state == .first) {
229 res.state = .start;
230 return .reset;
345231 }
346232
347 pub const ResetState = enum { reset, closing };
348
349 /// Reset this response to its initial state. This must be called before
350 /// handling a second request on the same connection.
351 pub fn reset(res: *Response) ResetState {
352 if (res.state == .first) {
353 res.state = .start;
354 return .reset;
355 }
233 if (!res.request.parser.done) {
234 // If the response wasn't fully read, then we need to close the connection.
235 res.connection.closing = true;
236 return .closing;
237 }
356238
357 if (!res.request.parser.done) {
358 // If the response wasn't fully read, then we need to close the connection.
359 res.connection.closing = true;
360 return .closing;
361 }
239 // A connection is only keep-alive if the Connection header is present
240 // and its value is not "close". The server and client must both agree.
241 //
242 // send() defaults to using keep-alive if the client requests it.
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 present
364 // and its value is not "close". The server and client must both agree.
365 //
366 // send() defaults to using keep-alive if the client requests it.
367 res.connection.closing = !res.keep_alive or !res.request.keep_alive;
245 res.state = .start;
246 res.version = .@"HTTP/1.1";
247 res.status = .ok;
248 res.reason = null;
368249
369 res.state = .start;
370 res.version = .@"HTTP/1.1";
371 res.status = .ok;
372 res.reason = null;
250 res.transfer_encoding = .none;
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 = .{
379 .version = undefined,
380 .method = undefined,
381 .target = undefined,
382 .parser = res.request.parser,
383 };
259pub const SendError = Connection.WriteError || error{
260 UnsupportedTransferEncoding,
261 InvalidContentLength,
262};
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,
386269 }
387270
388 pub const SendError = Connection.WriteError || error{
389 UnsupportedTransferEncoding,
390 InvalidContentLength,
391 };
392
393 /// Send the HTTP response headers to the client.
394 pub fn send(res: *Response) SendError!void {
395 switch (res.state) {
396 .waited => res.state = .responded,
397 .first, .start, .responded, .finished => unreachable,
398 }
399
400 var buffered = std.io.bufferedWriter(res.connection.writer());
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");
271 var buffered = std.io.bufferedWriter(res.connection.writer());
272 const w = buffered.writer();
273
274 try w.writeAll(@tagName(res.version));
275 try w.writeByte(' ');
276 try w.print("{d}", .{@intFromEnum(res.status)});
277 try w.writeByte(' ');
278 if (res.reason) |reason| {
279 try w.writeAll(reason);
280 } else if (res.status.phrase()) |phrase| {
281 try w.writeAll(phrase);
282 }
283 try w.writeAll("\r\n");
413284
414 if (res.status == .@"continue") {
415 res.state = .waited; // we still need to send another request after this
285 if (res.status == .@"continue") {
286 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");
416290 } else {
417 if (res.keep_alive and res.request.keep_alive) {
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 }
291 try w.writeAll("connection: close\r\n");
432292 }
433293
434 if (res.request.method == .HEAD) {
435 res.transfer_encoding = .none;
294 switch (res.transfer_encoding) {
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 => {},
436298 }
437299
438 try w.writeAll("\r\n");
439
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;
300 for (res.extra_headers) |header| {
301 try w.print("{s}: {s}\r\n", .{ header.name, header.value });
459302 }
460
461 return index;
462303 }
463304
464 pub const WaitError = Connection.ReadError ||
465 proto.HeadersParser.CheckCompleteHeadError || Request.ParseError ||
466 error{CompressionUnsupported};
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 }
305 if (res.request.method == .HEAD) {
306 res.transfer_encoding = .none;
307 }
490308
491 while (true) {
492 try res.connection.fill();
309 try w.writeAll("\r\n");
493310
494 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());
495 res.connection.drop(@intCast(nchecked));
311 try buffered.flush();
312}
496313
497 if (res.request.parser.state.isContent()) break;
498 }
314const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
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) {
503 .none => {
504 if (res.request.content_length) |len| {
505 res.request.parser.next_chunk_length = len;
318fn transferReader(res: *Server) TransferReader {
319 return .{ .context = res };
320}
506321
507 if (len == 0) res.request.parser.done = true;
508 } else {
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 }
322fn transferRead(res: *Server, buf: []u8) TransferReadError!usize {
323 if (res.request.parser.done) return 0;
517324
518 if (!res.request.parser.done) {
519 switch (res.request.transfer_compression) {
520 .identity => res.request.compression = .none,
521 .compress, .@"x-compress" => return error.CompressionUnsupported,
522 .deflate => res.request.compression = .{
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 }
325 var index: usize = 0;
326 while (index == 0) {
327 const amt = try res.request.parser.read(&res.connection, buf[index..], false);
328 if (amt == 0 and res.request.parser.done) break;
329 index += amt;
534330 }
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 {
541 return .{ .context = res };
339/// Wait for the client to send a complete request head.
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,
542360 }
543361
544 /// Reads data from the response body. Must be called after `wait`.
545 pub fn read(res: *Response, buffer: []u8) ReadError!usize {
546 switch (res.state) {
547 .waited, .responded, .finished => {},
548 .first, .start => unreachable,
549 }
362 while (true) {
363 try res.connection.fill();
550364
551 const out_index = switch (res.request.compression) {
552 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
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 };
365 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());
366 res.connection.drop(@intCast(nchecked));
558367
559 if (out_index == 0) {
560 const has_trail = !res.request.parser.state.isContent();
368 if (res.request.parser.state.isContent()) break;
369 }
561370
562 while (!res.request.parser.state.isContent()) { // read trailing headers
563 try res.connection.fill();
371 try res.request.parse(res.request.parser.get());
564372
565 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());
566 res.connection.drop(@intCast(nchecked));
567 }
373 switch (res.request.transfer_encoding) {
374 .none => {
375 if (res.request.content_length) |len| {
376 res.request.parser.next_chunk_length = len;
568377
569 if (has_trail) {
570 // The response headers before the trailers are already
571 // guaranteed to be valid, so they will always be parsed again
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;
378 if (len == 0) res.request.parser.done = true;
379 } else {
380 res.request.parser.done = true;
575381 }
576 }
577
578 return out_index;
382 },
383 .chunked => {
384 res.request.parser.next_chunk_length = 0;
385 res.request.parser.state = .chunk_head_size;
386 },
579387 }
580388
581 /// Reads data from the response body. Must be called after `wait`.
582 pub fn readAll(res: *Response, buffer: []u8) !usize {
583 var index: usize = 0;
584 while (index < buffer.len) {
585 const amt = try read(res, buffer[index..]);
586 if (amt == 0) break;
587 index += amt;
389 if (!res.request.parser.done) {
390 switch (res.request.transfer_compression) {
391 .identity => res.request.compression = .none,
392 .compress, .@"x-compress" => return error.CompressionUnsupported,
393 .deflate => res.request.compression = .{
394 .deflate = std.compress.zlib.decompressor(res.transferReader()),
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 },
588403 }
589 return index;
590404 }
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 {
597 return .{ .context = res };
415/// Reads data from the response body. Must be called after `wait`.
416pub fn read(res: *Server, buffer: []u8) ReadError!usize {
417 switch (res.state) {
418 .waited, .responded, .finished => {},
419 .first, .start => unreachable,
598420 }
599421
600 /// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
601 /// Must be called after `send` and before `finish`.
602 pub fn write(res: *Response, bytes: []const u8) WriteError!usize {
603 switch (res.state) {
604 .responded => {},
605 .first, .waited, .start, .finished => unreachable,
606 }
422 const out_index = switch (res.request.compression) {
423 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
424 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
425 // https://github.com/ziglang/zig/issues/18937
426 //.zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
427 else => try res.transferRead(buffer),
428 };
607429
608 switch (res.transfer_encoding) {
609 .chunked => {
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 }
430 if (out_index == 0) {
431 const has_trail = !res.request.parser.state.isContent();
615432
616 return bytes.len;
617 },
618 .content_length => |*len| {
619 if (len.* < bytes.len) return error.MessageTooLong;
433 while (!res.request.parser.state.isContent()) { // read trailing headers
434 try res.connection.fill();
620435
621 const amt = try res.connection.write(bytes);
622 len.* -= amt;
623 return amt;
624 },
625 .none => return error.NotWriteable,
436 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());
437 res.connection.drop(@intCast(nchecked));
626438 }
627 }
628439
629 /// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
630 /// Must be called after `send` and before `finish`.
631 pub fn writeAll(req: *Response, bytes: []const u8) WriteError!void {
632 var index: usize = 0;
633 while (index < bytes.len) {
634 index += try write(req, bytes[index..]);
440 if (has_trail) {
441 // The response headers before the trailers are already
442 // guaranteed to be valid, so they will always be parsed again
443 // and cannot return an error.
444 // This will *only* fail for a malformed trailer.
445 res.request.parse(res.request.parser.get()) catch return error.InvalidTrailers;
635446 }
636447 }
637448
638 pub const FinishError = Connection.WriteError || error{MessageNotCompleted};
639
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 }
449 return out_index;
450}
647451
648 switch (res.transfer_encoding) {
649 .chunked => try res.connection.writeAll("0\r\n\r\n"),
650 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
651 .none => {},
652 }
452/// Reads data from the response body. Must be called after `wait`.
453pub fn readAll(res: *Server, buffer: []u8) !usize {
454 var index: usize = 0;
455 while (index < buffer.len) {
456 const amt = try read(res, buffer[index..]);
457 if (amt == 0) break;
458 index += amt;
653459 }
654};
655
656/// Create a new HTTP server.
657pub fn init(options: net.StreamServer.Options) Server {
658 return .{
659 .socket = net.StreamServer.init(options),
660 };
460 return index;
661461}
662462
663/// Free all resources associated with this server.
664pub fn deinit(server: *Server) void {
665 server.socket.deinit();
666}
463pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
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.
671pub fn listen(server: *Server, address: net.Address) ListenError!void {
672 try server.socket.listen(address);
467pub fn writer(res: *Server) Writer {
468 return .{ .context = res };
673469}
674470
675pub const AcceptError = net.StreamServer.AcceptError;
676
677pub const AcceptOptions = struct {
678 /// Externally-owned memory used to store the client's entire HTTP header.
679 /// `error.HttpHeadersOversize` is returned from read() when a
680 /// client sends too many bytes of HTTP headers.
681 client_header_buffer: []u8,
682};
471/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
472/// Must be called after `send` and before `finish`.
473pub fn write(res: *Server, bytes: []const u8) WriteError!usize {
474 switch (res.state) {
475 .responded => {},
476 .first, .waited, .start, .finished => unreachable,
477 }
683478
684pub fn accept(server: *Server, options: AcceptOptions) AcceptError!Response {
685 const in = try server.socket.accept();
479 switch (res.transfer_encoding) {
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 .{
688 .transfer_encoding = .none,
689 .keep_alive = true,
690 .address = in.address,
691 .connection = .{
692 .stream = in.stream,
693 .protocol = .plain,
487 return bytes.len;
694488 },
695 .request = .{
696 .version = undefined,
697 .method = undefined,
698 .target = undefined,
699 .parser = proto.HeadersParser.init(options.client_header_buffer),
489 .content_length => |*len| {
490 if (len.* < bytes.len) return error.MessageTooLong;
491
492 const amt = try res.connection.write(bytes);
493 len.* -= amt;
494 return amt;
700495 },
701 };
496 .none => return error.NotWriteable,
497 }
702498}
703499
704test "HTTP server handles a chunked transfer coding request" {
705 // This test requires spawning threads.
706 if (builtin.single_threaded) {
707 return error.SkipZigTest;
500/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
501/// Must be called after `send` and before `finish`.
502pub fn writeAll(req: *Server, bytes: []const u8) WriteError!void {
503 var index: usize = 0;
504 while (index < bytes.len) {
505 index += try write(req, bytes[index..]);
708506 }
507}
709508
710 const native_endian = comptime builtin.cpu.arch.endian();
711 if (builtin.zig_backend == .stage2_llvm and native_endian == .big) {
712 // https://github.com/ziglang/zig/issues/13782
713 return error.SkipZigTest;
509pub const FinishError = Connection.WriteError || error{MessageNotCompleted};
510
511/// Finish the body of a request. This notifies the server that you have no more data to send.
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,
714517 }
715518
716 if (builtin.os.tag == .wasi) return error.SkipZigTest;
717
718 const allocator = std.testing.allocator;
719 const expect = std.testing.expect;
720
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();
519 switch (res.transfer_encoding) {
520 .chunked => try res.connection.writeAll("0\r\n\r\n"),
521 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
522 .none => {},
523 }
777524}
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" {
88
99 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(.{
1213 .reuse_address = true,
1314 });
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
1918 const server_thread = try std.Thread.spawn(.{}, serverThread, .{&http_server});
2019 defer server_thread.join();
......@@ -67,17 +66,14 @@ test "trailers" {
6766 try testing.expect(client.connection_pool.free_len == 1);
6867}
6968
70fn serverThread(http_server: *std.http.Server) anyerror!void {
71 const gpa = testing.allocator;
72
69fn serverThread(http_server: *std.net.Server) anyerror!void {
7370 var header_buffer: [1024]u8 = undefined;
7471 var remaining: usize = 1;
7572 accept: while (remaining != 0) : (remaining -= 1) {
76 var res = try http_server.accept(.{
77 .allocator = gpa,
78 .client_header_buffer = &header_buffer,
79 });
80 defer res.deinit();
73 const conn = try http_server.accept();
74 defer conn.stream.close();
75
76 var res = std.http.Server.init(conn, .{ .client_header_buffer = &header_buffer });
8177
8278 res.wait() catch |err| switch (err) {
8379 error.HttpHeadersInvalid => continue :accept,
......@@ -90,7 +86,7 @@ fn serverThread(http_server: *std.http.Server) anyerror!void {
9086 }
9187}
9288
93fn serve(res: *std.http.Server.Response) !void {
89fn serve(res: *std.http.Server) !void {
9490 try testing.expectEqualStrings(res.request.target, "/trailer");
9591 res.transfer_encoding = .chunked;
9692
......@@ -99,3 +95,73 @@ fn serve(res: *std.http.Server.Response) !void {
9995 try res.writeAll("World!\n");
10096 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");
10197}
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;
44const net = @This();
55const mem = std.mem;
66const os = std.os;
7const posix = std.posix;
78const fs = std.fs;
89const io = std.io;
910const native_endian = builtin.target.cpu.arch.endian();
1011
1112// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
1213// first release to support them.
13pub const has_unix_sockets = @hasDecl(os.sockaddr, "un") and
14 (builtin.target.os.tag != .windows or
15 builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false);
14pub const has_unix_sockets = switch (builtin.os.tag) {
15 .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,
16 else => true,
17};
1618
1719pub const IPParseError = error{
1820 Overflow,
......@@ -206,6 +208,57 @@ pub const Address = extern union {
206208 else => unreachable,
207209 }
208210 }
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 }
209262};
210263
211264pub const Ip4Address = extern struct {
......@@ -657,7 +710,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {
657710 os.SOCK.STREAM | os.SOCK.CLOEXEC | opt_non_block,
658711 0,
659712 );
660 errdefer os.closeSocket(sockfd);
713 errdefer Stream.close(.{ .handle = sockfd });
661714
662715 var addr = try std.net.Address.initUnix(path);
663716 try os.connect(sockfd, &addr.any, addr.getOsSockLen());
......@@ -669,7 +722,7 @@ fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 {
669722 if (builtin.target.os.tag == .linux) {
670723 var ifr: os.ifreq = undefined;
671724 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
674727 @memcpy(ifr.ifrn.name[0..name.len], name);
675728 ifr.ifrn.name[name.len] = 0;
......@@ -738,7 +791,7 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
738791 const sock_flags = os.SOCK.STREAM | nonblock |
739792 (if (builtin.target.os.tag == .windows) 0 else os.SOCK.CLOEXEC);
740793 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
743796 try os.connect(sockfd, &address.any, address.getOsSockLen());
744797
......@@ -1068,7 +1121,7 @@ fn linuxLookupName(
10681121 var prefixlen: i32 = 0;
10691122 const sock_flags = os.SOCK.DGRAM | os.SOCK.CLOEXEC;
10701123 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 });
10721125 os.connect(fd, da, dalen) catch break :syscalls;
10731126 key |= DAS_USABLE;
10741127 os.getsockname(fd, sa, &salen) catch break :syscalls;
......@@ -1553,7 +1606,7 @@ fn resMSendRc(
15531606 },
15541607 else => |e| return e,
15551608 };
1556 defer os.closeSocket(fd);
1609 defer Stream.close(.{ .handle = fd });
15571610
15581611 // Past this point, there are no errors. Each individual query will
15591612 // 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)
17291782}
17301783
17311784pub const Stream = struct {
1732 // Underlying socket descriptor.
1733 // Note that on some platforms this may not be interchangeable with a
1734 // regular files descriptor.
1735 handle: os.socket_t,
1736
1737 pub fn close(self: Stream) void {
1738 os.closeSocket(self.handle);
1785 /// Underlying platform-defined type which may or may not be
1786 /// interchangeable with a file system file descriptor.
1787 handle: posix.socket_t,
1788
1789 pub fn close(s: Stream) void {
1790 switch (builtin.os.tag) {
1791 .windows => std.os.windows.closesocket(s.handle) catch unreachable,
1792 else => posix.close(s.handle),
1793 }
17391794 }
17401795
17411796 pub const ReadError = os.ReadError;
......@@ -1839,156 +1894,38 @@ pub const Stream = struct {
18391894 }
18401895};
18411896
1842pub const StreamServer = 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.
1897pub const Server = struct {
18501898 listen_address: Address,
1899 stream: std.net.Stream,
18511900
1852 sockfd: ?os.socket_t,
1853
1854 pub const Options = struct {
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,
1901 pub const Connection = struct {
1902 stream: std.net.Stream,
1903 address: Address,
18681904 };
18691905
1870 /// After this call succeeds, resources have been acquired and must
1871 /// be released with `deinit`.
1872 pub fn init(options: Options) StreamServer {
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 }
1906 pub fn deinit(s: *Server) void {
1907 s.stream.close();
1908 s.* = undefined;
19351909 }
19361910
1937 pub const AcceptError = error{
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,
1911 pub const AcceptError = posix.AcceptError;
19661912
1967 OperationNotSupported,
1968 } || os.UnexpectedError;
1969
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 {
1913 /// Blocks until a client connects to the server. The returned `Connection` has
1914 /// an open stream.
1915 pub fn accept(s: *Server) AcceptError!Connection {
19771916 var accepted_addr: Address = undefined;
1978 var adr_len: os.socklen_t = @sizeOf(Address);
1979 const accept_result = os.accept(self.sockfd.?, &accepted_addr.any, &adr_len, os.SOCK.CLOEXEC);
1980
1981 if (accept_result) |fd| {
1982 return Connection{
1983 .stream = Stream{ .handle = fd },
1984 .address = accepted_addr,
1985 };
1986 } else |err| {
1987 return err;
1988 }
1917 var addr_len: posix.socklen_t = @sizeOf(Address);
1918 const fd = try posix.accept(s.stream.handle, &accepted_addr.any, &addr_len, posix.SOCK.CLOEXEC);
1919 return .{
1920 .stream = .{ .handle = fd },
1921 .address = accepted_addr,
1922 };
19891923 }
19901924};
19911925
19921926test {
19931927 _ = @import("net/test.zig");
1928 _ = Server;
1929 _ = Stream;
1930 _ = Address;
19941931}
lib/std/net/test.zig+8-18
......@@ -181,11 +181,9 @@ test "listen on a port, send bytes, receive bytes" {
181181 // configured.
182182 const localhost = try net.Address.parseIp("127.0.0.1", 0);
183183
184 var server = net.StreamServer.init(.{});
184 var server = try localhost.listen(.{});
185185 defer server.deinit();
186186
187 try server.listen(localhost);
188
189187 const S = struct {
190188 fn clientFn(server_address: net.Address) !void {
191189 const socket = try net.tcpConnectToAddress(server_address);
......@@ -215,17 +213,11 @@ test "listen on an in use port" {
215213
216214 const localhost = try net.Address.parseIp("127.0.0.1", 0);
217215
218 var server1 = net.StreamServer.init(net.StreamServer.Options{
219 .reuse_port = true,
220 });
216 var server1 = try localhost.listen(.{ .reuse_port = true });
221217 defer server1.deinit();
222 try server1.listen(localhost);
223218
224 var server2 = net.StreamServer.init(net.StreamServer.Options{
225 .reuse_port = true,
226 });
219 var server2 = try server1.listen_address.listen(.{ .reuse_port = true });
227220 defer server2.deinit();
228 try server2.listen(server1.listen_address);
229221}
230222
231223fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
......@@ -252,7 +244,7 @@ fn testClient(addr: net.Address) anyerror!void {
252244 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
253245}
254246
255fn testServer(server: *net.StreamServer) anyerror!void {
247fn testServer(server: *net.Server) anyerror!void {
256248 if (builtin.os.tag == .wasi) return error.SkipZigTest;
257249
258250 var client = try server.accept();
......@@ -274,15 +266,14 @@ test "listen on a unix socket, send bytes, receive bytes" {
274266 }
275267 }
276268
277 var server = net.StreamServer.init(.{});
278 defer server.deinit();
279
280269 const socket_path = try generateFileName("socket.unix");
281270 defer testing.allocator.free(socket_path);
282271
283272 const socket_addr = try net.Address.initUnix(socket_path);
284273 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
287278 const S = struct {
288279 fn clientFn(path: []const u8) !void {
......@@ -323,9 +314,8 @@ test "non-blocking tcp server" {
323314 }
324315
325316 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 });
327318 defer server.deinit();
328 try server.listen(localhost);
329319
330320 const accept_err = server.accept();
331321 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 {
35983598 }
35993599}
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
36093601pub const BindError = error{
36103602 /// The address is protected, and the user is not the superuser.
36113603 /// 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;
44const mem = std.mem;
55const net = std.net;
66const os = std.os;
7const posix = std.posix;
78const linux = os.linux;
89const testing = std.testing;
910
......@@ -3730,8 +3731,8 @@ const SocketTestHarness = struct {
37303731 client: os.socket_t,
37313732
37323733 fn close(self: SocketTestHarness) void {
3733 os.closeSocket(self.client);
3734 os.closeSocket(self.listener);
3734 posix.close(self.client);
3735 posix.close(self.listener);
37353736 }
37363737};
37373738
......@@ -3739,7 +3740,7 @@ fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
37393740 // Create a TCP server socket
37403741 var address = try net.Address.parseIp4("127.0.0.1", 0);
37413742 const listener_socket = try createListenerSocket(&address);
3742 errdefer os.closeSocket(listener_socket);
3743 errdefer posix.close(listener_socket);
37433744
37443745 // Submit 1 accept
37453746 var accept_addr: os.sockaddr = undefined;
......@@ -3748,7 +3749,7 @@ fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
37483749
37493750 // Create a TCP client socket
37503751 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);
37523753 _ = try ring.connect(0xcccccccc, client, &address.any, address.getOsSockLen());
37533754
37543755 try testing.expectEqual(@as(u32, 2), try ring.submit());
......@@ -3788,7 +3789,7 @@ fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
37883789fn createListenerSocket(address: *net.Address) !os.socket_t {
37893790 const kernel_backlog = 1;
37903791 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
37933794 try os.setsockopt(listener_socket, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
37943795 try os.bind(listener_socket, &address.any, address.getOsSockLen());
......@@ -3813,7 +3814,7 @@ test "accept multishot" {
38133814
38143815 var address = try net.Address.parseIp4("127.0.0.1", 0);
38153816 const listener_socket = try createListenerSocket(&address);
3816 defer os.closeSocket(listener_socket);
3817 defer posix.close(listener_socket);
38173818
38183819 // submit multishot accept operation
38193820 var addr: os.sockaddr = undefined;
......@@ -3826,7 +3827,7 @@ test "accept multishot" {
38263827 while (nr > 0) : (nr -= 1) {
38273828 // connect client
38283829 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);
38303831 try os.connect(client, &address.any, address.getOsSockLen());
38313832
38323833 // test accept completion
......@@ -3836,7 +3837,7 @@ test "accept multishot" {
38363837 try testing.expect(cqe.user_data == userdata);
38373838 try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE > 0); // more flag is set
38383839
3839 os.closeSocket(client);
3840 posix.close(client);
38403841 }
38413842}
38423843
......@@ -3909,7 +3910,7 @@ test "accept_direct" {
39093910 try ring.register_files(registered_fds[0..]);
39103911
39113912 const listener_socket = try createListenerSocket(&address);
3912 defer os.closeSocket(listener_socket);
3913 defer posix.close(listener_socket);
39133914
39143915 const accept_userdata: u64 = 0xaaaaaaaa;
39153916 const read_userdata: u64 = 0xbbbbbbbb;
......@@ -3927,7 +3928,7 @@ test "accept_direct" {
39273928 // connect
39283929 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
39293930 try os.connect(client, &address.any, address.getOsSockLen());
3930 defer os.closeSocket(client);
3931 defer posix.close(client);
39313932
39323933 // accept completion
39333934 const cqe_accept = try ring.copy_cqe();
......@@ -3961,7 +3962,7 @@ test "accept_direct" {
39613962 // connect
39623963 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
39633964 try os.connect(client, &address.any, address.getOsSockLen());
3964 defer os.closeSocket(client);
3965 defer posix.close(client);
39653966 // completion with error
39663967 const cqe_accept = try ring.copy_cqe();
39673968 try testing.expect(cqe_accept.user_data == accept_userdata);
......@@ -3989,7 +3990,7 @@ test "accept_multishot_direct" {
39893990 try ring.register_files(registered_fds[0..]);
39903991
39913992 const listener_socket = try createListenerSocket(&address);
3992 defer os.closeSocket(listener_socket);
3993 defer posix.close(listener_socket);
39933994
39943995 const accept_userdata: u64 = 0xaaaaaaaa;
39953996
......@@ -4003,7 +4004,7 @@ test "accept_multishot_direct" {
40034004 // connect
40044005 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
40054006 try os.connect(client, &address.any, address.getOsSockLen());
4006 defer os.closeSocket(client);
4007 defer posix.close(client);
40074008
40084009 // accept completion
40094010 const cqe_accept = try ring.copy_cqe();
......@@ -4018,7 +4019,7 @@ test "accept_multishot_direct" {
40184019 // connect
40194020 const client = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
40204021 try os.connect(client, &address.any, address.getOsSockLen());
4021 defer os.closeSocket(client);
4022 defer posix.close(client);
40224023 // completion with error
40234024 const cqe_accept = try ring.copy_cqe();
40244025 try testing.expect(cqe_accept.user_data == accept_userdata);
......@@ -4092,7 +4093,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {
40924093 // use sockets from registered_fds in connect operation
40934094 var address = try net.Address.parseIp4("127.0.0.1", 0);
40944095 const listener_socket = try createListenerSocket(&address);
4095 defer os.closeSocket(listener_socket);
4096 defer posix.close(listener_socket);
40964097 const accept_userdata: u64 = 0xaaaaaaaa;
40974098 const connect_userdata: u64 = 0xbbbbbbbb;
40984099 const close_userdata: u64 = 0xcccccccc;
lib/std/os/test.zig+1-1
......@@ -817,7 +817,7 @@ test "shutdown socket" {
817817 error.SocketNotConnected => {},
818818 else => |e| return e,
819819 };
820 os.closeSocket(sock);
820 std.net.Stream.close(.{ .handle = sock });
821821}
822822
823823test "sigaction" {
src/main.zig+3-3
......@@ -3322,13 +3322,13 @@ fn buildOutputType(
33223322 .ip4 => |ip4_addr| {
33233323 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(.{
33263328 .reuse_address = true,
33273329 });
33283330 defer server.deinit();
33293331
3330 try server.listen(.{ .in = ip4_addr });
3331
33323332 const conn = try server.accept();
33333333 defer conn.stream.close();
33343334
test/standalone/http.zig+24-29
......@@ -1,8 +1,6 @@
11const std = @import("std");
22
33const http = std.http;
4const Server = http.Server;
5const Client = http.Client;
64
75const mem = std.mem;
86const testing = std.testing;
......@@ -19,9 +17,7 @@ var gpa_client = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 })
1917const salloc = gpa_server.allocator();
2018const calloc = gpa_client.allocator();
2119
22var server: Server = undefined;
23
24fn handleRequest(res: *Server.Response) !void {
20fn handleRequest(res: *http.Server, listen_port: u16) !void {
2521 const log = std.log.scoped(.server);
2622
2723 log.info("{} {s} {s}", .{ res.request.method, @tagName(res.request.version), res.request.target });
......@@ -125,7 +121,9 @@ fn handleRequest(res: *Server.Response) !void {
125121 } else if (mem.eql(u8, res.request.target, "/redirect/3")) {
126122 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 });
129127 defer salloc.free(location);
130128
131129 res.status = .found;
......@@ -168,14 +166,15 @@ fn handleRequest(res: *Server.Response) !void {
168166
169167var handle_new_requests = true;
170168
171fn runServer(srv: *Server) !void {
169fn runServer(server: *std.net.Server) !void {
172170 var client_header_buffer: [1024]u8 = undefined;
173171 outer: while (handle_new_requests) {
174 var res = try srv.accept(.{
175 .allocator = salloc,
172 var connection = try server.accept();
173 defer connection.stream.close();
174
175 var res = http.Server.init(connection, .{
176176 .client_header_buffer = &client_header_buffer,
177177 });
178 defer res.deinit();
179178
180179 while (res.reset() != .closing) {
181180 res.wait() catch |err| switch (err) {
......@@ -184,16 +183,15 @@ fn runServer(srv: *Server) !void {
184183 else => return err,
185184 };
186185
187 try handleRequest(&res);
186 try handleRequest(&res, server.listen_address.getPort());
188187 }
189188 }
190189}
191190
192fn serverThread(srv: *Server) void {
193 defer srv.deinit();
191fn serverThread(server: *std.net.Server) void {
194192 defer _ = gpa_server.deinit();
195193
196 runServer(srv) catch |err| {
194 runServer(server) catch |err| {
197195 std.debug.print("server error: {}\n", .{err});
198196
199197 if (@errorReturnTrace()) |trace| {
......@@ -205,18 +203,10 @@ fn serverThread(srv: *Server) void {
205203 };
206204}
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
215206fn getUnusedTcpPort() !u16 {
216207 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(.{});
218209 defer s.deinit();
219 try s.listen(addr);
220210 return s.listen_address.in.getPort();
221211}
222212
......@@ -225,16 +215,15 @@ pub fn main() !void {
225215
226216 defer _ = gpa_client.deinit();
227217
228 server = Server.init(.{ .reuse_address = true });
229
230218 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
235224 const server_thread = try std.Thread.spawn(.{}, serverThread, .{&server});
236225
237 var client = Client{ .allocator = calloc };
226 var client: http.Client = .{ .allocator = calloc };
238227 errdefer client.deinit();
239228 // defer client.deinit(); handled below
240229
......@@ -691,6 +680,12 @@ pub fn main() !void {
691680
692681 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
695690 server_thread.join();
696691}