authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-12-14 15:52:39-06:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-13 18:51:38-08:00
logb723296e1fa65b73a43b0790bdddcbfcea7d656d
tree33173696d644f655374e134b73df3d694e4a1a28
parent832f6d8f7f7f7b10b86b109a8b26bb5eacc8d13e

std.http: add missing documentation and a few examples


4 files changed, 141 insertions(+), 13 deletions(-)

lib/std/http/Client.zig+50-3
......@@ -1,4 +1,8 @@
1//! Connecting and opening requests are threadsafe. Individual requests are not.
1//! HTTP(S) Client implementation.
2//!
3//! Connections are opened in a thread-safe manner, but individual Requests are not.
4//!
5//! TLS support may be disabled via `std.options.http_disable_tls`.
26
37const std = @import("../std.zig");
48const builtin = @import("builtin");
......@@ -157,6 +161,9 @@ pub const ConnectionPool = struct {
157161 pool.free_size = new_size;
158162 }
159163
164 /// Frees the connection pool and closes all connections within. This function is threadsafe.
165 ///
166 /// All future operations on the connection pool will deadlock.
160167 pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void {
161168 pool.mutex.lock();
162169
......@@ -191,11 +198,19 @@ pub const Connection = struct {
191198 /// undefined unless protocol is tls.
192199 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
193200
201 /// The protocol that this connection is using.
194202 protocol: Protocol,
203
204 /// The host that this connection is connected to.
195205 host: []u8,
206
207 /// The port that this connection is connected to.
196208 port: u16,
197209
210 /// Whether this connection is proxied and is not directly connected.
198211 proxied: bool = false,
212
213 /// Whether this connection is closing when we're done with it.
199214 closing: bool = false,
200215
201216 read_start: BufferSize = 0,
......@@ -232,6 +247,7 @@ pub const Connection = struct {
232247 };
233248 }
234249
250 /// Refills the read buffer with data from the connection.
235251 pub fn fill(conn: *Connection) ReadError!void {
236252 if (conn.read_end != conn.read_start) return;
237253
......@@ -244,14 +260,17 @@ pub const Connection = struct {
244260 conn.read_end = @intCast(nread);
245261 }
246262
263 /// Returns the current slice of buffered data.
247264 pub fn peek(conn: *Connection) []const u8 {
248265 return conn.read_buf[conn.read_start..conn.read_end];
249266 }
250267
268 /// Discards the given number of bytes from the read buffer.
251269 pub fn drop(conn: *Connection, num: BufferSize) void {
252270 conn.read_start += num;
253271 }
254272
273 /// Reads data from the connection into the given buffer.
255274 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
256275 const available_read = conn.read_end - conn.read_start;
257276 const available_buffer = buffer.len;
......@@ -318,6 +337,7 @@ pub const Connection = struct {
318337 };
319338 }
320339
340 /// Writes the given buffer to the connection.
321341 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
322342 if (conn.write_end + buffer.len > conn.write_buf.len) {
323343 try conn.flush();
......@@ -334,6 +354,7 @@ pub const Connection = struct {
334354 return buffer.len;
335355 }
336356
357 /// Flushes the write buffer to the connection.
337358 pub fn flush(conn: *Connection) WriteError!void {
338359 if (conn.write_end == 0) return;
339360
......@@ -352,6 +373,7 @@ pub const Connection = struct {
352373 return Writer{ .context = conn };
353374 }
354375
376 /// Closes the connection.
355377 pub fn close(conn: *Connection, allocator: Allocator) void {
356378 if (conn.protocol == .tls) {
357379 if (disable_tls) unreachable;
......@@ -502,8 +524,13 @@ pub const Response = struct {
502524 try expectEqual(@as(u10, 999), parseInt3("999"));
503525 }
504526
527 /// The HTTP version this response is using.
505528 version: http.Version,
529
530 /// The status code of the response.
506531 status: http.Status,
532
533 /// The reason phrase of the response.
507534 reason: []const u8,
508535
509536 /// If present, the number of bytes in the response body.
......@@ -528,22 +555,36 @@ pub const Response = struct {
528555///
529556/// Order of operations: open -> send[ -> write -> finish] -> wait -> read
530557pub const Request = struct {
558 /// The uri that this request is being sent to.
531559 uri: Uri,
560
561 /// The client that this request was created from.
532562 client: *Client,
533 /// is null when this connection is released
563
564 /// Underlying connection to the server. This is null when the connection is released.
534565 connection: ?*Connection,
535566
536567 method: http.Method,
537568 version: http.Version = .@"HTTP/1.1",
569
570 /// The list of HTTP request headers.
538571 headers: http.Headers,
539572
540573 /// The transfer encoding of the request body.
541574 transfer_encoding: RequestTransfer = .none,
542575
576 /// The redirect quota left for this request.
543577 redirects_left: u32,
578
579 /// Whether the request should follow redirects.
544580 handle_redirects: bool,
581
582 /// Whether the request should handle a 100-continue response before sending the request body.
545583 handle_continue: bool,
546584
585 /// The response associated with this request.
586 ///
587 /// This field is undefined until `wait` is called.
547588 response: Response,
548589
549590 /// Used as a allocator for resolving redirects locations.
......@@ -993,6 +1034,7 @@ pub const Request = struct {
9931034 }
9941035};
9951036
1037/// A HTTP proxy server.
9961038pub const Proxy = struct {
9971039 allocator: Allocator,
9981040 headers: http.Headers,
......@@ -1144,6 +1186,7 @@ pub fn loadDefaultProxies(client: *Client) !void {
11441186pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };
11451187
11461188/// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open.
1189///
11471190/// This function is threadsafe.
11481191pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectTcpError!*Connection {
11491192 if (client.connection_pool.findConnection(.{
......@@ -1203,6 +1246,7 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
12031246pub const ConnectUnixError = Allocator.Error || std.os.SocketError || error{ NameTooLong, Unsupported } || std.os.ConnectError;
12041247
12051248/// Connect to `path` as a unix domain socket. This will reuse a connection if one is already open.
1249///
12061250/// This function is threadsafe.
12071251pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connection {
12081252 if (!net.has_unix_sockets) return error.Unsupported;
......@@ -1237,6 +1281,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
12371281}
12381282
12391283/// Connect to `tunnel_host:tunnel_port` using the specified proxy with HTTP CONNECT. This will reuse a connection if one is already open.
1284///
12401285/// This function is threadsafe.
12411286pub fn connectTunnel(
12421287 client: *Client,
......@@ -1318,7 +1363,6 @@ const ConnectErrorPartial = ConnectTcpError || error{ UnsupportedUrlScheme, Conn
13181363pub const ConnectError = ConnectErrorPartial || RequestError;
13191364
13201365/// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open.
1321///
13221366/// If a proxy is configured for the client, then the proxy will be used to connect to the host.
13231367///
13241368/// This function is threadsafe.
......@@ -1375,7 +1419,10 @@ pub const RequestOptions = struct {
13751419 /// request, then the request *will* deadlock.
13761420 handle_continue: bool = true,
13771421
1422 /// Automatically follow redirects. This will only follow redirects for repeatable requests (ie. with no payload or the server has acknowledged the payload)
13781423 handle_redirects: bool = true,
1424
1425 /// How many redirects to follow before returning an error.
13791426 max_redirects: u32 = 3,
13801427 header_strategy: StorageStrategy = .{ .dynamic = 16 * 1024 },
13811428
lib/std/http/Headers.zig+12-1
......@@ -35,6 +35,7 @@ pub const CaseInsensitiveStringContext = struct {
3535 }
3636};
3737
38/// A single HTTP header field.
3839pub const Field = struct {
3940 name: []const u8,
4041 value: []const u8,
......@@ -47,6 +48,7 @@ pub const Field = struct {
4748 }
4849};
4950
51/// A list of HTTP header fields.
5052pub const Headers = struct {
5153 allocator: Allocator,
5254 list: HeaderList = .{},
......@@ -56,10 +58,12 @@ pub const Headers = struct {
5658 /// Use with caution.
5759 owned: bool = true,
5860
61 /// Initialize an empty list of headers.
5962 pub fn init(allocator: Allocator) Headers {
6063 return .{ .allocator = allocator };
6164 }
6265
66 /// Initialize a pre-populated list of headers from a list of fields.
6367 pub fn initList(allocator: Allocator, list: []const Field) !Headers {
6468 var new = Headers.init(allocator);
6569
......@@ -72,6 +76,9 @@ pub const Headers = struct {
7276 return new;
7377 }
7478
79 /// Deallocate all memory associated with the headers.
80 ///
81 /// If the `owned` field is false, this will not free the names and values of the headers.
7582 pub fn deinit(headers: *Headers) void {
7683 headers.deallocateIndexListsAndFields();
7784 headers.index.deinit(headers.allocator);
......@@ -80,7 +87,9 @@ pub const Headers = struct {
8087 headers.* = undefined;
8188 }
8289
83 /// Appends a header to the list. Both name and value are copied.
90 /// Appends a header to the list.
91 ///
92 /// If the `owned` field is true, both name and value will be copied.
8493 pub fn append(headers: *Headers, name: []const u8, value: []const u8) !void {
8594 const n = headers.list.items.len;
8695
......@@ -108,6 +117,7 @@ pub const Headers = struct {
108117 try headers.list.append(headers.allocator, entry);
109118 }
110119
120 /// Returns true if this list of headers contains the given name.
111121 pub fn contains(headers: Headers, name: []const u8) bool {
112122 return headers.index.contains(name);
113123 }
......@@ -285,6 +295,7 @@ pub const Headers = struct {
285295 headers.list.clearRetainingCapacity();
286296 }
287297
298 /// Creates a copy of the headers using the provided allocator.
288299 pub fn clone(headers: Headers, allocator: Allocator) !Headers {
289300 var new = Headers.init(allocator);
290301
lib/std/http/Server.zig+78-8
......@@ -1,3 +1,44 @@
1//! HTTP Server implementation.
2//!
3//! This server assumes *all* clients are well behaved and standard compliant; it can and will deadlock if a client holds a connection open without sending a request.
4//!
5//! Example usage:
6//!
7//! ```zig
8//! var server = Server.init(.{ .reuse_address = true });
9//! defer server.deinit();
10//!
11//! try server.listen(bind_addr);
12//!
13//! while (true) {
14//! var res = try server.accept(.{ .allocator = gpa });
15//! defer res.deinit();
16//!
17//! while (res.reset() != .closing) {
18//! res.wait() catch |err| switch (err) {
19//! error.HttpHeadersInvalid => break,
20//! error.HttpHeadersExceededSizeLimit => {
21//! res.status = .request_header_fields_too_large;
22//! res.send() catch break;
23//! break;
24//! },
25//! else => {
26//! res.status = .bad_request;
27//! res.send() catch break;
28//! break;
29//! },
30//! }
31//!
32//! res.status = .ok;
33//! res.transfer_encoding = .chunked;
34//!
35//! try res.send();
36//! try res.writeAll("Hello, World!\n");
37//! try res.finish();
38//! }
39//! }
40//! ```
41
142const std = @import("../std.zig");
243const testing = std.testing;
344const http = std.http;
......@@ -10,8 +51,7 @@ const assert = std.debug.assert;
1051const Server = @This();
1152const proto = @import("protocol.zig");
1253
13allocator: Allocator,
14
54/// The underlying server socket.
1555socket: net.StreamServer,
1656
1757/// An interface to a plain connection.
......@@ -269,8 +309,13 @@ pub const Request = struct {
269309 return @as(u64, @bitCast(array.*));
270310 }
271311
312 /// The HTTP request method.
272313 method: http.Method,
314
315 /// The HTTP request target.
273316 target: []const u8,
317
318 /// The HTTP version of this request.
274319 version: http.Version,
275320
276321 /// The length of the request body, if known.
......@@ -282,16 +327,21 @@ pub const Request = struct {
282327 /// The compression of the request body, or .identity (no compression) if not present.
283328 transfer_compression: http.ContentEncoding = .identity,
284329
330 /// The list of HTTP request headers
285331 headers: http.Headers,
332
286333 parser: proto.HeadersParser,
287334 compression: Compression = .none,
288335};
289336
290337/// A HTTP response waiting to be sent.
291338///
292/// [/ <----------------------------------- \]
293/// Order of operations: accept -> wait -> send [ -> write -> finish][ -> reset /]
294/// \ -> read /
339/// Order of operations:
340/// ```
341/// [/ <--------------------------------------- \]
342/// accept -> wait -> send [ -> write -> finish][ -> reset /]
343/// \ -> read /
344/// ```
295345pub const Response = struct {
296346 version: http.Version = .@"HTTP/1.1",
297347 status: http.Status = .ok,
......@@ -299,11 +349,21 @@ pub const Response = struct {
299349
300350 transfer_encoding: ResponseTransfer = .none,
301351
352 /// The allocator responsible for allocating memory for this response.
302353 allocator: Allocator,
354
355 /// The peer's address
303356 address: net.Address,
357
358 /// The underlying connection for this response.
304359 connection: Connection,
305360
361 /// The HTTP response headers
306362 headers: http.Headers,
363
364 /// The HTTP request that this response is responding to.
365 ///
366 /// This field is only valid after calling `wait`.
307367 request: Request,
308368
309369 state: State = .first,
......@@ -495,6 +555,17 @@ pub const Response = struct {
495555 pub const WaitError = Connection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Request.ParseError || error{ CompressionInitializationFailed, CompressionNotSupported };
496556
497557 /// Wait for the client to send a complete request head.
558 ///
559 /// For correct behavior, the following rules must be followed:
560 ///
561 /// * If this returns any error in `Connection.ReadError`, you MUST immediately close the connection by calling `deinit`.
562 /// * If this returns `error.HttpHeadersInvalid`, you MAY immediately close the connection by calling `deinit`.
563 /// * If this returns `error.HttpHeadersExceededSizeLimit`, you MUST respond with a 431 status code and then call `deinit`.
564 /// * If this returns any error in `Request.ParseError`, you MUST respond with a 400 status code and then call `deinit`.
565 /// * If this returns any other error, you MUST respond with a 400 status code and then call `deinit`.
566 /// * If the request has an Expect header containing 100-continue, you MUST either:
567 /// * Respond with a 100 status code, then call `wait` again.
568 /// * Respond with a 417 status code.
498569 pub fn wait(res: *Response) WaitError!void {
499570 switch (res.state) {
500571 .first, .start => res.state = .waited,
......@@ -664,9 +735,8 @@ pub const Response = struct {
664735};
665736
666737/// Create a new HTTP server.
667pub fn init(allocator: Allocator, options: net.StreamServer.Options) Server {
738pub fn init(options: net.StreamServer.Options) Server {
668739 return .{
669 .allocator = allocator,
670740 .socket = net.StreamServer.init(options),
671741 };
672742}
......@@ -748,7 +818,7 @@ test "HTTP server handles a chunked transfer coding request" {
748818 const expect = std.testing.expect;
749819
750820 const max_header_size = 8192;
751 var server = std.http.Server.init(allocator, .{ .reuse_address = true });
821 var server = std.http.Server.init(.{ .reuse_address = true });
752822 defer server.deinit();
753823
754824 const address = try std.net.Address.parseIp("127.0.0.1", 0);
test/standalone/http.zig+1-1
......@@ -220,7 +220,7 @@ pub fn main() !void {
220220
221221 defer _ = gpa_client.deinit();
222222
223 server = Server.init(salloc, .{ .reuse_address = true });
223 server = Server.init(.{ .reuse_address = true });
224224
225225 const addr = std.net.Address.parseIp("127.0.0.1", 0) catch unreachable;
226226 try server.listen(addr);