Clientes HTTP em Zig — Requisições, TLS e APIs REST

Clientes HTTP em Zig — Requisições, TLS e APIs REST

O consumo de APIs e serviços web é uma necessidade fundamental em aplicações modernas. O Zig oferece um cliente HTTP robusto na biblioteca padrão (std.http.Client) com suporte a TLS, além de bibliotecas de terceiros para cenários especializados. Este guia cobre desde requisições básicas até padrões avançados para consumo de APIs REST.

std.http.Client — O Cliente Padrão

A biblioteca padrão inclui um cliente HTTP completo com suporte a HTTP/1.1, TLS 1.3, redirects e conexões keep-alive:

GET Simples

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    // Requisição GET
    const uri = try std.Uri.parse("https://api.exemplo.com/dados");

    var buf: [8192]u8 = undefined;
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &buf,
    });
    defer req.deinit();

    try req.send();
    try req.wait();

    // Ler resposta
    const body = try req.reader().readAllAlloc(allocator, 1024 * 1024);
    defer allocator.free(body);

    std.debug.print("Status: {}\n", .{req.status});
    std.debug.print("Body: {s}\n", .{body});
}

POST com JSON

pub fn postJson(allocator: std.mem.Allocator, url: []const u8, payload: []const u8) ![]u8 {
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    const uri = try std.Uri.parse(url);

    var buf: [8192]u8 = undefined;
    var req = try client.open(.POST, uri, .{
        .server_header_buffer = &buf,
        .extra_headers = &.{
            .{ .name = "Content-Type", .value = "application/json" },
            .{ .name = "Accept", .value = "application/json" },
        },
    });
    defer req.deinit();

    req.transfer_encoding = .{ .content_length = payload.len };
    try req.send();
    try req.writer().writeAll(payload);
    try req.finish();
    try req.wait();

    if (req.status != .ok and req.status != .created) {
        return error.HttpError;
    }

    return try req.reader().readAllAlloc(allocator, 1024 * 1024);
}

// Uso
pub fn criarUsuario(allocator: std.mem.Allocator) !void {
    const payload =
        \\{"nome": "Maria Silva", "email": "maria@exemplo.com"}
    ;

    const resposta = try postJson(
        allocator,
        "https://api.exemplo.com/usuarios",
        payload,
    );
    defer allocator.free(resposta);

    std.debug.print("Resposta: {s}\n", .{resposta});
}

Headers de Autenticação

pub fn getComAuth(allocator: std.mem.Allocator, url: []const u8, token: []const u8) ![]u8 {
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    var auth_header_buf: [256]u8 = undefined;
    const auth_value = try std.fmt.bufPrint(&auth_header_buf, "Bearer {s}", .{token});

    const uri = try std.Uri.parse(url);

    var buf: [8192]u8 = undefined;
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &buf,
        .extra_headers = &.{
            .{ .name = "Authorization", .value = auth_value },
        },
    });
    defer req.deinit();

    try req.send();
    try req.wait();

    return try req.reader().readAllAlloc(allocator, 1024 * 1024);
}

Cliente HTTP com Tipagem Forte

Construindo um wrapper tipado para APIs:

fn ApiClient(comptime base_url: []const u8) type {
    return struct {
        allocator: std.mem.Allocator,
        token: ?[]const u8 = null,

        const Self = @This();

        pub fn init(allocator: std.mem.Allocator) Self {
            return .{ .allocator = allocator };
        }

        pub fn comAuth(self: *Self, token: []const u8) *Self {
            self.token = token;
            return self;
        }

        pub fn get(self: *Self, comptime T: type, path: []const u8) !T {
            var url_buf: [512]u8 = undefined;
            const url = try std.fmt.bufPrint(&url_buf, "{s}{s}", .{ base_url, path });

            var client = std.http.Client{ .allocator = self.allocator };
            defer client.deinit();

            const uri = try std.Uri.parse(url);
            var header_buf: [8192]u8 = undefined;
            var req = try client.open(.GET, uri, .{
                .server_header_buffer = &header_buf,
            });
            defer req.deinit();

            try req.send();
            try req.wait();

            const body = try req.reader().readAllAlloc(self.allocator, 1024 * 1024);
            defer self.allocator.free(body);

            const parsed = try std.json.parseFromSlice(T, self.allocator, body, .{
                .ignore_unknown_fields = true,
            });
            return parsed.value;
        }
    };
}

// Uso
const Api = ApiClient("https://api.exemplo.com");

const Usuario = struct {
    id: u64,
    nome: []const u8,
    email: []const u8,
};

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    var api = Api.init(allocator);

    const usuario = try api.get(Usuario, "/usuarios/1");
    std.debug.print("Usuário: {s}\n", .{usuario.nome});
}

Download de Arquivos

pub fn downloadArquivo(
    allocator: std.mem.Allocator,
    url: []const u8,
    caminho_saida: []const u8,
) !void {
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    const uri = try std.Uri.parse(url);
    var buf: [8192]u8 = undefined;
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &buf,
    });
    defer req.deinit();

    try req.send();
    try req.wait();

    // Salvar em arquivo
    const arquivo = try std.fs.cwd().createFile(caminho_saida, .{});
    defer arquivo.close();

    var read_buf: [8192]u8 = undefined;
    var reader = req.reader();
    while (true) {
        const n = try reader.read(&read_buf);
        if (n == 0) break;
        try arquivo.writeAll(read_buf[0..n]);
    }

    std.debug.print("Download concluído: {s}\n", .{caminho_saida});
}

Retry com Backoff Exponencial

pub fn fetchComRetry(
    allocator: std.mem.Allocator,
    url: []const u8,
    max_tentativas: u32,
) ![]u8 {
    var tentativa: u32 = 0;
    while (tentativa < max_tentativas) : (tentativa += 1) {
        var client = std.http.Client{ .allocator = allocator };
        defer client.deinit();

        const uri = try std.Uri.parse(url);
        var buf: [8192]u8 = undefined;
        var req = client.open(.GET, uri, .{
            .server_header_buffer = &buf,
        }) catch |err| {
            std.debug.print("Tentativa {}: erro={}\n", .{ tentativa + 1, err });
            const delay = std.math.pow(u64, 2, tentativa) * std.time.ns_per_s;
            std.time.sleep(delay);
            continue;
        };
        defer req.deinit();

        req.send() catch continue;
        req.wait() catch continue;

        if (req.status == .ok) {
            return try req.reader().readAllAlloc(allocator, 1024 * 1024);
        }

        const delay = std.math.pow(u64, 2, tentativa) * std.time.ns_per_s;
        std.time.sleep(delay);
    }

    return error.MaxRetriesExceeded;
}

Boas Práticas

  1. Use defer client.deinit(): Sempre libere recursos do cliente
  2. Trate status HTTP: Verifique códigos de resposta antes de processar body
  3. Implemente timeouts: Configure limites de tempo para evitar bloqueios
  4. Use keep-alive: Reutilize conexões para múltiplas requisições ao mesmo host
  5. Valide certificados TLS: O std.http.Client valida certificados por padrão

Próximos Passos

Explore os frameworks web para o lado servidor, as bibliotecas JSON para parsing de respostas, e as bibliotecas de rede para protocolos customizados. Veja como Cloudflare usa Zig em serviços HTTP e consulte nossos tutoriais.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.