tiny.http.Client
Defined in tiny.http.
API (20)
Actions
Public operations.
deinitgetgetStreaminit: Builds a client that holdsallocatorto back its certificate authority bundle and retain an open connection for subsequent requests.openWebSocketpostpostStreamrequest: Sends one request and returns the response read from the server.requestStream
Fields and members
Public fields and members.
allocatorca_bundleca_bundle_lockcerts_loadedkeep_alivepersistentpersistent_hostpersistent_host_lenpersistent_porttcp_no_delaytimeout_ms
Source
Source: lib/http/src/client/runtime.zig:47
zig
pub const Client = struct { allocator: Allocator, ca_bundle: tls.Bundle = .empty, ca_bundle_lock: tls.BundleLock = .init, certs_loaded: bool = false, timeout_ms: u32 = default_timeout_ms, keep_alive: bool = false, tcp_no_delay: bool = false, persistent: ?PersistentConnection = null, persistent_host: [client_operation.default_host_bytes]u8 = undefined, persistent_host_len: u16 = 0, persistent_port: u16 = 0, /// Builds a client that holds `allocator` to back its certificate authority /// bundle and retain an open connection for subsequent requests. Building a /// client touches the network in no way. The certificate authority bundle /// is loaded from the local operating system store on the first TLS request /// that requires it rather than during initialization. Calling `deinit` /// closes any retained connection and frees the bundle. pub fn init(allocator: Allocator) Client { return .{ .allocator = allocator }; } pub fn deinit(self: *Client) void { self.dropPersistent(true); if (self.certs_loaded) self.ca_bundle.deinit(self.allocator); } fn ensureCerts(self: *Client) !void { if (!self.certs_loaded) { tls.loadSystemBundle(&self.ca_bundle, self.allocator) catch return error.TlsFailed; self.certs_loaded = true; } } pub fn post( self: *Client, operation_scratch: OperationScratch, response_scratch: ResponseScratch, url: []const u8, headers: []const Header, body: []const u8, ) ClientError!ClientResponse { return self.request(operation_scratch, response_scratch, "POST", url, headers, body); } pub fn postStream( self: *Client, operation_scratch: OperationScratch, stream_scratch: StreamScratch, url: []const u8, headers: []const Header, body: []const u8, handler: StreamHandler, ) anyerror!u16 { return self.requestStream( operation_scratch, stream_scratch, "POST", url, headers, body, handler, ); } pub fn get( self: *Client, operation_scratch: OperationScratch, response_scratch: ResponseScratch, url: []const u8, headers: []const Header, ) ClientError!ClientResponse { return self.request(operation_scratch, response_scratch, "GET", url, headers, ""); } pub fn getStream( self: *Client, operation_scratch: OperationScratch, stream_scratch: StreamScratch, url: []const u8, headers: []const Header, handler: StreamHandler, ) anyerror!u16 { return self.requestStream( operation_scratch, stream_scratch, "GET", url, headers, "", handler, ); } /// Sends one request and returns the response read from the server. /// Returned headers and body slices point directly into `response_scratch`. /// This backing storage must remain alive and unchanged while the response /// is in use, with subsequent parses into that storage serving as one point /// of invalidation. /// /// Parsing the URL into target fields and checking the read and write /// buffers for a plain request occur before opening any socket, so failures /// during these preparation checks leave the network untouched. /// /// A request over TLS loads the local operating system certificate /// authority bundle on the first call requiring it. When keep-alive is /// enabled, the request reuses an existing connection already open to that /// host and port. pub fn request( self: *Client, operation_scratch: OperationScratch, response_scratch: ResponseScratch, method: []const u8, url: []const u8, headers: []const Header, body: []const u8, ) ClientError!ClientResponse { try rejectTunnelMethod(method); const prepared = try ClientOperation.prepare(operation_scratch, url); if (!prepared.is_tls) try ClientOperation.validatePlain(operation_scratch); const alloc = self.allocator; if (prepared.is_tls) try self.ensureCerts(); if (self.keep_alive) { return self.reusableRequest( response_scratch, prepared, method, headers, body, ); } self.dropPersistent(true); const stream = connectToHost( prepared.host, prepared.port, self.timeout_ms, self.tcp_no_delay, ) catch return error.ConnectionFailed; if (prepared.is_tls) { return self.doTlsRequest( alloc, response_scratch, stream, prepared.host, prepared.target, method, headers, body, ); } else { return doPlainRequest( operation_scratch, response_scratch, stream, prepared.host, prepared.target, method, headers, body, ); } } pub fn openWebSocket( self: *Client, operation_scratch: OperationScratch, websocket_scratch: WebSocketScratch, url: []const u8, ) anyerror!ClientWebSocket { const prepared = try ClientOperation.prepare( operation_scratch, url, ); if (!prepared.is_tls) { try ClientOperation.validatePlain( operation_scratch, ); } if (prepared.is_tls) try self.ensureCerts(); const socket = connectToHost( prepared.host, prepared.port, self.timeout_ms, self.tcp_no_delay, ) catch return error.ConnectionFailed; const connection: PersistentConnection = if (prepared.is_tls) .{ .tls = try self.createTlsPersistent( socket, prepared.host, ) } else .{ .plain = try PlainConn.create( self.allocator, socket, ) }; var owned = true; errdefer if (owned) { destroyConnection( self.allocator, connection, false, ); }; try clientWebSocketHandshake( connection, prepared, ); const result = try ClientWebSocket.init( self.allocator, connection, websocket_scratch, ); owned = false; return result; } pub fn requestStream( self: *Client, operation_scratch: OperationScratch, stream_scratch: StreamScratch, method: []const u8, url: []const u8, headers: []const Header, body: []const u8, handler: StreamHandler, ) anyerror!u16 { try rejectTunnelMethod(method); const prepared = try ClientOperation.prepare(operation_scratch, url); if (!prepared.is_tls) try ClientOperation.validatePlain(operation_scratch); const alloc = self.allocator; if (prepared.is_tls) try self.ensureCerts(); const stream = connectToHost( prepared.host, prepared.port, self.timeout_ms, self.tcp_no_delay, ) catch return error.ConnectionFailed; if (prepared.is_tls) { return self.doTlsRequestStream( alloc, stream_scratch, stream, prepared.host, prepared.target, method, headers, body, handler, ); } else { return doPlainRequestStream( operation_scratch, stream_scratch, stream, prepared.host, prepared.target, method, headers, body, handler, ); } } fn reusableRequest( self: *Client, response_scratch: ResponseScratch, prepared: PreparedOperation, method: []const u8, headers: []const Header, body: []const u8, ) ClientError!ClientResponse { try self.ensurePersistent(prepared); const response = self.performReusableRequest( response_scratch, prepared.host, prepared.target, method, headers, body, ) catch |err| { self.dropPersistent(false); return err; }; if (!response.connection_reusable) { self.dropPersistent(true); } return response; } fn ensurePersistent( self: *Client, prepared: PreparedOperation, ) ClientError!void { if (self.persistentMatches(prepared)) return; self.dropPersistent(true); if (prepared.host.len > self.persistent_host.len) { return error.ClientHostCapacityExceeded; } const socket = connectToHost( prepared.host, prepared.port, self.timeout_ms, self.tcp_no_delay, ) catch return error.ConnectionFailed; const connection: PersistentConnection = if (prepared.is_tls) .{ .tls = try self.createTlsPersistent( socket, prepared.host, ) } else .{ .plain = try PlainConn.create( self.allocator, socket, ) }; self.persistent = connection; @memcpy( self.persistent_host[0..prepared.host.len], prepared.host, ); self.persistent_host_len = @intCast(prepared.host.len); self.persistent_port = prepared.port; } fn createTlsPersistent( self: *Client, socket: sys.Socket, host: []const u8, ) ClientError!*TlsConn { const conn = self.allocator.create(TlsConn) catch { sys.close(socket); return error.OutOfMemory; }; errdefer self.allocator.destroy(conn); conn.initAt(socket); errdefer conn.deinit(false); try self.initializeTls(conn, host); return conn; } fn initializeTls( self: *Client, conn: *TlsConn, host: []const u8, ) ClientError!void { var entropy: [tls.Client.Options.entropy_len]u8 = undefined; tls.fillEntropy(&entropy) catch return error.TlsFailed; const now = tls.realtimeNow(); conn.tls_client = tls.Client.init( &conn.socket_reader.reader, &conn.socket_writer.writer, .{ .host = .{ .explicit = host }, .ca = tls.bundleAuthority( self.allocator, &self.ca_bundle_lock, &self.ca_bundle, ), .read_buffer = &conn.tls_read_buf, .write_buffer = &conn.tls_write_buf, .entropy = &entropy, .realtime_now = now, .allow_truncation_attacks = true, }, ) catch return error.TlsFailed; } fn performReusableRequest( self: *Client, response_scratch: ResponseScratch, host: []const u8, target: []const u8, method: []const u8, headers: []const Header, body: []const u8, ) ClientError!ClientResponse { const persistent = self.persistent orelse return error.ConnectionFailed; return switch (persistent) { .plain => |conn| blk: { ClientOperation.writeReusableRequest( &conn.socket_writer.writer, method, host, target, headers, body, ) catch return error.WriteFailed; conn.socket_writer.writer.flush() catch return error.WriteFailed; break :blk try client_response.read( response_scratch, &conn.socket_reader.reader, method, ); }, .tls => |conn| blk: { ClientOperation.writeReusableRequest( &conn.tls_client.?.writer, method, host, target, headers, body, ) catch return error.WriteFailed; conn.tls_client.?.writer.flush() catch return error.WriteFailed; conn.socket_writer.writer.flush() catch return error.WriteFailed; break :blk try client_response.read( response_scratch, &conn.tls_client.?.reader, method, ); }, }; } fn persistentMatches( self: *const Client, prepared: PreparedOperation, ) bool { const persistent = self.persistent orelse return false; const is_tls = switch (persistent) { .plain => false, .tls => true, }; return is_tls == prepared.is_tls and self.persistent_port == prepared.port and std.mem.eql( u8, self.persistent_host[0..self.persistent_host_len], prepared.host, ); } fn dropPersistent( self: *Client, notify: bool, ) void { if (self.persistent) |persistent| { switch (persistent) { .plain => |conn| conn.destroy( self.allocator, ), .tls => |conn| { conn.deinit(notify); self.allocator.destroy(conn); }, } self.persistent = null; } self.persistent_host_len = 0; self.persistent_port = 0; } fn rejectTunnelMethod(method: []const u8) error{TunnelUnsupported}!void { if (isTunnelMethod(method)) return error.TunnelUnsupported; } fn isTunnelMethod(method: []const u8) bool { return std.mem.eql(u8, method, "CONNECT"); } fn doTlsRequest( self: *Client, alloc: Allocator, response_scratch: ResponseScratch, socket: sys.Socket, host: []const u8, path: []const u8, method: []const u8, headers: []const Header, body: []const u8, ) ClientError!ClientResponse { std.debug.assert(!isTunnelMethod(method)); const conn = alloc.create(TlsConn) catch return error.OutOfMemory; var send_close_notify = false; defer { conn.deinit(send_close_notify); alloc.destroy(conn); } conn.initAt(socket); try self.initializeTls(conn, host); ClientOperation.writeRequest( &conn.tls_client.?.writer, method, host, path, headers, body, ) catch return error.WriteFailed; conn.tls_client.?.writer.flush() catch return error.WriteFailed; conn.socket_writer.writer.flush() catch return error.WriteFailed; const parsed = try client_response.read( response_scratch, &conn.tls_client.?.reader, method, ); send_close_notify = true; return parsed; } fn doTlsRequestStream( self: *Client, alloc: Allocator, stream_scratch: StreamScratch, socket: sys.Socket, host: []const u8, path: []const u8, method: []const u8, headers: []const Header, body: []const u8, handler: StreamHandler, ) anyerror!u16 { std.debug.assert(!isTunnelMethod(method)); const conn = alloc.create(TlsConn) catch return error.OutOfMemory; var send_close_notify = false; defer { conn.deinit(send_close_notify); alloc.destroy(conn); } conn.initAt(socket); try self.initializeTls(conn, host); ClientOperation.writeRequest( &conn.tls_client.?.writer, method, host, path, headers, body, ) catch return error.WriteFailed; conn.tls_client.?.writer.flush() catch return error.WriteFailed; conn.socket_writer.writer.flush() catch return error.WriteFailed; const status = try client_stream.ClientStream.readForMethod( stream_scratch, &conn.tls_client.?.reader, method, handler, ); send_close_notify = true; return status; } fn doPlainRequest( operation_scratch: OperationScratch, response_scratch: ResponseScratch, socket: sys.Socket, host: []const u8, path: []const u8, method: []const u8, headers: []const Header, body: []const u8, ) ClientError!ClientResponse { std.debug.assert(!isTunnelMethod(method)); defer sys.close(socket); var socket_writer = SocketWriter.init(socket, operation_scratch.plain_write); ClientOperation.writeRequest( &socket_writer.writer, method, host, path, headers, body, ) catch return error.WriteFailed; socket_writer.writer.flush() catch return error.WriteFailed; var socket_reader = SocketReader.init(socket, operation_scratch.plain_read); return client_response.read(response_scratch, &socket_reader.reader, method); } fn doPlainRequestStream( operation_scratch: OperationScratch, stream_scratch: StreamScratch, socket: sys.Socket, host: []const u8, path: []const u8, method: []const u8, headers: []const Header, body: []const u8, handler: StreamHandler, ) anyerror!u16 { std.debug.assert(!isTunnelMethod(method)); defer sys.close(socket); var socket_writer = SocketWriter.init(socket, operation_scratch.plain_write); ClientOperation.writeRequest( &socket_writer.writer, method, host, path, headers, body, ) catch return error.WriteFailed; socket_writer.writer.flush() catch return error.WriteFailed; var socket_reader = SocketReader.init(socket, operation_scratch.plain_read); return client_stream.ClientStream.readForMethod( stream_scratch, &socket_reader.reader, method, handler, ); }};Source: lib/http/src/root.zig:78
zig
pub const Client = client.Client;Complete call list for Client.openWebSocket
9 direct calls.
tiny.http.ClientOperation.prepare[function] atlib/http/src/client/operation/model.zig:118tiny.http.ClientOperation.validatePlain[function] atlib/http/src/client/operation/model.zig:143lib.http.src.client.runtime.Client.createTlsPersistent[method] — private source atlib/http/src/client/runtime.zig:379in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.Client.ensureCerts[method] — private source atlib/http/src/client/runtime.zig:76in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.ClientWebSocket.init[function] — private source atlib/http/src/client/runtime.zig:720in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.PlainConn.create[function] — private source atlib/http/src/client/runtime.zig:679in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.clientWebSocketHandshake[function] — private source atlib/http/src/client/runtime.zig:970in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.connectToHost[function] — private source atlib/http/src/client/runtime.zig:1138in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.destroyConnection[function] — private source atlib/http/src/client/runtime.zig:1124in nearest public ownerlib.http.src.client.runtime
Complete caller list for Client.request
18 direct callers.
tiny.http.Client.get[method] atlib/http/src/client/runtime.zig:114tiny.http.Client.post[method] atlib/http/src/client/runtime.zig:83lib.http.src.client.runtime.test_Client.request:_rejects_unsupported_scheme[function] — test source atlib/http/src/client/runtime.zig:1717in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.test_Client_high-level_requests_reject_tunnel_ownership_before_preparation[function] — test source atlib/http/src/client/runtime.zig:1733in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.test_Client_request_rejects_operation_capacity_before_network_effects[function] — test source atlib/http/src/client/runtime.zig:1766in nearest public ownerlib.http.src.client.runtimelib.windowing.src.wayland.present.dmabuf.Dmabuf.destroyObject[method] — private source atlib/windowing/src/wayland/present/dmabuf.zig:457in nearest public ownerlib.windowing.src.wayland.present.dmabuflib.windowing.src.wayland.present.dmabuf.Dmabuf.importBuffer[method] — private source atlib/windowing/src/wayland/present/dmabuf.zig:260in nearest public ownerlib.windowing.src.wayland.present.dmabuflib.windowing.src.wayland.present.dmabuf.Dmabuf.importTimeline[method] — private source atlib/windowing/src/wayland/present/dmabuf.zig:304in nearest public ownerlib.windowing.src.wayland.present.dmabuflib.windowing.src.wayland.present.dmabuf.Dmabuf.setSyncPoints[method] — private source atlib/windowing/src/wayland/present/dmabuf.zig:334in nearest public ownerlib.windowing.src.wayland.present.dmabuflib.windowing.src.wayland.seat.clipboard.Clipboard.bindSeat[method] — private source atlib/windowing/src/wayland/seat/clipboard.zig:59in nearest public ownerlib.windowing.src.wayland.seat.clipboardlib.windowing.src.wayland.seat.clipboard.Clipboard.destroyDetachedOffer[method] — private source atlib/windowing/src/wayland/seat/clipboard.zig:226in nearest public ownerlib.windowing.src.wayland.seat.clipboardlib.windowing.src.wayland.seat.clipboard.Clipboard.destroyDetachedSource[method] — private source atlib/windowing/src/wayland/seat/clipboard.zig:203in nearest public ownerlib.windowing.src.wayland.seat.clipboardlib.windowing.src.wayland.seat.clipboard.Clipboard.dispatchDevice[method] — private source atlib/windowing/src/wayland/seat/clipboard.zig:237in nearest public ownerlib.windowing.src.wayland.seat.clipboardlib.windowing.src.wayland.seat.clipboard.Clipboard.getTextAlloc[method] — private source atlib/windowing/src/wayland/seat/clipboard.zig:140in nearest public ownerlib.windowing.src.wayland.seat.clipboardlib.windowing.src.wayland.seat.clipboard.Clipboard.resetDevice[method] — private source atlib/windowing/src/wayland/seat/clipboard.zig:178in nearest public ownerlib.windowing.src.wayland.seat.clipboardlib.windowing.src.wayland.seat.clipboard.Clipboard.setText[method] — private source atlib/windowing/src/wayland/seat/clipboard.zig:100in nearest public ownerlib.windowing.src.wayland.seat.clipboardlib.windowing.src.wayland.session.root.Session.destroyVersionedId[method] — private source atlib/windowing/src/wayland/session/root.zig:348in nearest public ownerlib.windowing.src.wayland.session.rootlib.windowing.src.wayland.session.root.Session.dispatchWmBase[method] — private source atlib/windowing/src/wayland/session/root.zig:248in nearest public ownerlib.windowing.src.wayland.session.root
Complete call list for Client.request
9 direct calls.
tiny.http.ClientOperation.prepare[function] atlib/http/src/client/operation/model.zig:118tiny.http.ClientOperation.validatePlain[function] atlib/http/src/client/operation/model.zig:143lib.http.src.client.runtime.Client.doPlainRequest[function] — private source atlib/http/src/client/runtime.zig:609in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.Client.doTlsRequest[method] — private source atlib/http/src/client/runtime.zig:522in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.Client.dropPersistent[method] — private source atlib/http/src/client/runtime.zig:494in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.Client.ensureCerts[method] — private source atlib/http/src/client/runtime.zig:76in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.Client.rejectTunnelMethod[function] — private source atlib/http/src/client/runtime.zig:514in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.Client.reusableRequest[method] — private source atlib/http/src/client/runtime.zig:317in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.connectToHost[function] — private source atlib/http/src/client/runtime.zig:1138in nearest public ownerlib.http.src.client.runtime
Complete call list for Client.requestStream
7 direct calls.
tiny.http.ClientOperation.prepare[function] atlib/http/src/client/operation/model.zig:118tiny.http.ClientOperation.validatePlain[function] atlib/http/src/client/operation/model.zig:143lib.http.src.client.runtime.Client.doPlainRequestStream[function] — private source atlib/http/src/client/runtime.zig:637in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.Client.doTlsRequestStream[method] — private source atlib/http/src/client/runtime.zig:565in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.Client.ensureCerts[method] — private source atlib/http/src/client/runtime.zig:76in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.Client.rejectTunnelMethod[function] — private source atlib/http/src/client/runtime.zig:514in nearest public ownerlib.http.src.client.runtimelib.http.src.client.runtime.connectToHost[function] — private source atlib/http/src/client/runtime.zig:1138in nearest public ownerlib.http.src.client.runtime
Audit
| Definitions | 10 |
|---|---|
| Public names | 10 |
| Members | 11 |
| Version | 26.7.0 |
| Revision | daab053ee433 |