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
- Use
defer client.deinit(): Sempre libere recursos do cliente - Trate status HTTP: Verifique códigos de resposta antes de processar body
- Implemente timeouts: Configure limites de tempo para evitar bloqueios
- Use keep-alive: Reutilize conexões para múltiplas requisições ao mesmo host
- 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.