Skip to documentation
SLOP

tiny.http.Client

Reference tiny.http Client

Defined in tiny.http.

API (20)

Actions

Public operations.

Fields and members

Public fields and members.

No direct callersNo direct callstiny.httpClient
Static calls · unresolved targets: unknown · external targets: unknown.

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;
Called byCallstest sourcelib.http.src.client.runtime.test_Clientrequest: rejects unsupported schemetest sourcelib.http.src.client.runtimetest: Client high-level requests reje...test sourcelib.http.src.client.runtimetest: Client request rejects operatio...private sourcelib.windowing.src.wayland.session.root.Sessiondeinitprivate sourcelib.http.src.client.runtime.ClientdropPersistentClientdeinit
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersClientrequestClientget
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersClientrequestStreamClientgetStream
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.http.src.client.runtime.test_Clientrequest: rejects unsupported schemetest sourcelib.http.src.client.runtimetest: Client high-level requests reje...test sourcelib.http.src.client.runtimetest: Client request rejects operatio...Clientinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersClientOperationprepareClientOperationvalidatePlainprivate sourcelib.http.src.client.runtime.ClientcreateTlsPersistentprivate sourcelib.http.src.client.runtime.ClientensureCertsprivate sourcelib.http.src.client.runtime.ClientWebSocketinit+4 moreClientopenWebSocket
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersClientrequestClientpost
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersClientrequestStreamClientpostStream
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsClientgetClientposttest sourcelib.http.src.client.runtime.test_Clientrequest: rejects unsupported schemetest sourcelib.http.src.client.runtimetest: Client high-level requests reje...test sourcelib.http.src.client.runtimetest: Client request rejects operatio...+13 moreClientOperationprepareClientOperationvalidatePlainprivate sourcelib.http.src.client.runtime.ClientdoPlainRequestprivate sourcelib.http.src.client.runtime.ClientdoTlsRequestprivate sourcelib.http.src.client.runtime.ClientdropPersistent+4 moreClientrequest
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsClientgetStreamClientpostStreamtest sourcelib.http.src.client.runtimetest: Client high-level requests reje...ClientOperationprepareClientOperationvalidatePlainprivate sourcelib.http.src.client.runtime.ClientdoPlainRequestStreamprivate sourcelib.http.src.client.runtime.ClientdoTlsRequestStreamprivate sourcelib.http.src.client.runtime.ClientensureCerts+2 moreClientrequestStream
Static calls · unresolved targets: 0 · external targets: 0.

Complete call list for Client.openWebSocket

9 direct calls.

Complete caller list for Client.request

18 direct callers.

Complete call list for Client.request

9 direct calls.

Complete call list for Client.requestStream

7 direct calls.

Audit

Definitions10
Public names10
Members11
Version26.7.0
Revisiondaab053ee433