Fazer requisições HTTP é uma necessidade fundamental em quase todo software moderno. Seja para consumir APIs REST, fazer webhooks, baixar artefatos ou integrar com serviços externos, você precisa saber como usar o std.http.Client de Zig com controle de memória, limites de resposta e tratamento explícito de erros.
Este tutorial é o guia prático para quem pesquisa por zig HTTP client, std.http.Client, GET e POST em Zig ou consumir API REST com Zig. Vamos sair de uma requisição GET simples, passar por JSON e autenticação, e chegar em padrões reutilizáveis para CLIs, workers, integrações internas e serviços backend.
Resposta rápida: como fazer HTTP em Zig
| Objetivo | Caminho recomendado | Link interno |
|---|---|---|
| Fazer um GET simples | std.http.Client, std.Uri.parse, client.request(.GET, ...) e limite explícito no readAllAlloc | Veja também a receita de HTTP GET |
| Enviar JSON com POST | Serializar com std.json.stringify, definir Content-Type: application/json e escrever no body antes de finish() | Combine com Parsing JSON em Zig |
| Criar uma API REST em Zig | Use este client para consumir APIs; use std.http.Server para recebê-las | Zig Server HTTP: API REST com std.http.Server |
| Rodar em produção | Defina User-Agent, valide status, limite corpo, trate retries e observe logs | Zig HTTP Server em Produção |
Se você está montando uma aplicação completa, pense no par: std.http.Client para chamar APIs externas e std.http.Server para expor endpoints internos. Essa ligação também aparece em guias de networking com sockets TCP/UDP e em projetos de filas e workers em background, onde chamadas HTTP precisam ser previsíveis e fáceis de depurar.
Quando usar este guia, uma receita ou a referência
A busca por zig http client costuma misturar três intenções: copiar um exemplo curto, entender a API da stdlib e montar uma integração REST mais robusta. Use esta página como trilha principal e pule para o material certo conforme o seu estágio:
| Intenção de busca | Melhor próximo passo | Por que ajuda |
|---|---|---|
| “Quero só fazer um GET agora” | Como fazer requisições HTTP GET em Zig | Receita curta, direta e fácil de adaptar para scripts e CLIs |
| “Preciso enviar JSON com POST” | Como fazer requisições HTTP POST em Zig | Mostra Content-Type, content_length, escrita do body e validação de status |
| “Quero entender a API oficial” | std.http.Client em Zig | Referência focada em fetch, opções, headers, TLS e armazenamento da resposta |
| “Estou escolhendo biblioteca HTTP” | Clientes HTTP em Zig | Compara stdlib e alternativas quando o projeto exige ergonomia ou recursos extras |
| “Meu serviço também recebe requests” | Zig Server HTTP: API REST com std.http.Server | Fecha o ciclo entre consumir APIs externas e expor endpoints internos |
Para produção, não trate o cliente HTTP como detalhe de infraestrutura invisível. Defina um User-Agent, limite o tamanho da resposta, valide status antes de fazer parse, registre contexto suficiente para depuração e diferencie erro de rede, erro HTTP e JSON inválido. Se o client roda dentro de um backend, combine este checklist com o guia de HTTP server em produção para alinhar timeouts, retries, logs e health checks.
Introdução ao std.http.Client
Por Que Não Usar uma Biblioteca Externa?
Zig vem com um cliente HTTP completo na biblioteca padrão (std.http.Client). Isso significa:
- ✅ Zero dependências externas para a maioria dos casos de uso
- ✅ Performance nativa — código Zig puro, sem bindings
- ✅ Cross-compilation funciona perfeitamente
- ✅ Integração natural com o ecossistema Zig (allocators, errors, etc.)
Arquitetura do Cliente HTTP
O std.http.Client é construído em camadas:
┌─────────────────────────────────────┐
│ std.http.Client │ ← Cliente principal, gerencia conexões
├─────────────────────────────────────┤
│ std.http.Client.Connection │ ← Conexão persistente com servidor
├─────────────────────────────────────┤
│ std.http.Request/Response │ ← Requisição e resposta HTTP
├─────────────────────────────────────┤
│ std.crypto.tls │ ← TLS/HTTPS (quando necessário)
└─────────────────────────────────────┘
Requisição GET Simples
O Básico: Fetch de uma URL
Vamos começar com o exemplo mais simples — fazer um GET e imprimir a resposta:
const std = @import("std");
pub fn main() !void {
// Allocator necessário para o cliente HTTP
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Criar o cliente HTTP
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
// Fazer a requisição GET
const uri = try std.Uri.parse("https://api.github.com/users/ziglang");
var req = try client.request(.GET, uri, .{
.allocator = allocator,
}, .{});
defer req.deinit();
// Adicionar headers
try req.headers.append("User-Agent", "ZigHTTPClient/1.0");
// Enviar e finalizar a requisição
try req.start();
try req.finish();
try req.wait();
// Ler a resposta
const body = try req.reader().readAllAlloc(allocator, 10 * 1024 * 1024); // Max 10MB
defer allocator.free(body);
// Imprimir
std.debug.print("Status: {d}\n", .{req.response.status});
std.debug.print("Body: {s}\n", .{body});
}
Explicação Passo a Passo
- Criar o cliente:
std.http.Clientprecisa de um allocator para gerenciar buffers - Parsear a URI: Convertemos a string URL para o tipo
std.Uri - Criar a requisição:
.request()configura o método, URI e opções - Adicionar headers: Headers HTTP como User-Agent são importantes
- start/finish/wait: Sequência necessária para enviar e receber
- Ler resposta: O body pode ser lido via
req.reader()
Trabalhando com JSON
Parsing JSON da Resposta
Consumir APIs geralmente significa trabalhar com JSON. Vamos integrar com parsing:
const std = @import("std");
// Struct que representa a resposta da API
const User = struct {
login: []const u8,
id: u64,
avatar_url: []const u8,
html_url: []const u8,
public_repos: u32,
followers: u32,
following: u32,
created_at: []const u8,
};
pub fn fetchUser(allocator: std.mem.Allocator, username: []const u8) !User {
// Construir URL
const base_url = "https://api.github.com/users/";
const url = try std.fmt.allocPrint(allocator, "{s}{s}", .{ base_url, username });
defer allocator.free(url);
// Criar cliente
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
// Configurar requisição
const uri = try std.Uri.parse(url);
var req = try client.request(.GET, uri, .{
.allocator = allocator,
}, .{});
defer req.deinit();
// Headers necessários para GitHub API
try req.headers.append("User-Agent", "ZigTutorial");
try req.headers.append("Accept", "application/vnd.github.v3+json");
// Executar
try req.start();
try req.finish();
try req.wait();
// Verificar status
if (req.response.status != .ok) {
return error.RequestFailed;
}
// Ler body
const body = try req.reader().readAllAlloc(allocator, 1024 * 1024);
defer allocator.free(body);
// Parse JSON
const parsed = try std.json.parseFromSlice(User, allocator, body, .{
.ignore_unknown_fields = true,
});
defer parsed.deinit();
return parsed.value;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const user = try fetchUser(allocator, "ziglang");
std.debug.print("Usuário: {s}\n", .{user.login});
std.debug.print("ID: {d}\n", .{user.id});
std.debug.print("Repositórios públicos: {d}\n", .{user.public_repos});
std.debug.print("Seguidores: {d}\n", .{user.followers});
std.debug.print("URL: {s}\n", .{user.html_url});
}
Requisições POST com JSON
Enviando Dados para uma API
POST é usado para criar recursos. Vamos ver como enviar JSON:
const std = @import("std");
const Post = struct {
title: []const u8,
body: []const u8,
userId: u32,
};
const CreatedPost = struct {
title: []const u8,
body: []const u8,
userId: u32,
id: u32,
};
pub fn createPost(allocator: std.mem.Allocator, post: Post) !CreatedPost {
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
const uri = try std.Uri.parse("https://jsonplaceholder.typicode.com/posts");
// Serializar JSON
var json_buffer = std.ArrayList(u8).init(allocator);
defer json_buffer.deinit();
try std.json.stringify(post, .{}, json_buffer.writer());
var req = try client.request(.POST, uri, .{
.allocator = allocator,
}, .{});
defer req.deinit();
// Headers importantes para POST JSON
try req.headers.append("User-Agent", "ZigHTTPClient");
try req.headers.append("Content-Type", "application/json");
try req.headers.append("Accept", "application/json");
// Enviar body
try req.start();
try req.writer().writeAll(json_buffer.items);
try req.finish();
try req.wait();
// Ler resposta
const body = try req.reader().readAllAlloc(allocator, 1024 * 1024);
defer allocator.free(body);
// Parse resposta
const parsed = try std.json.parseFromSlice(CreatedPost, allocator, body, .{});
defer parsed.deinit();
return parsed.value;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const new_post = Post{
.title = "Aprendendo Zig HTTP",
.body = "Este é um post criado via API usando Zig!",
.userId = 1,
};
const created = try createPost(allocator, new_post);
std.debug.print("Post criado!\n", .{});
std.debug.print("ID: {d}\n", .{created.id});
std.debug.print("Título: {s}\n", .{created.title});
}
Autenticação
Bearer Token (JWT/OAuth)
APIs modernas geralmente usam tokens Bearer:
const AuthenticatedClient = struct {
client: std.http.Client,
base_url: []const u8,
token: []const u8,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, base_url: []const u8, token: []const u8) AuthenticatedClient {
return .{
.client = std.http.Client{ .allocator = allocator },
.base_url = base_url,
.token = token,
.allocator = allocator,
};
}
pub fn deinit(self: *AuthenticatedClient) void {
self.client.deinit();
}
pub fn get(self: *AuthenticatedClient, endpoint: []const u8) ![]u8 {
const url = try std.fmt.allocPrint(
self.allocator,
"{s}{s}",
.{ self.base_url, endpoint }
);
defer self.allocator.free(url);
const uri = try std.Uri.parse(url);
var req = try self.client.request(.GET, uri, .{
.allocator = self.allocator,
}, .{});
defer req.deinit();
// Adicionar token de autenticação
const auth_header = try std.fmt.allocPrint(
self.allocator,
"Bearer {s}",
.{self.token}
);
defer self.allocator.free(auth_header);
try req.headers.append("Authorization", auth_header);
try req.headers.append("User-Agent", "ZigApp/1.0");
try req.headers.append("Accept", "application/json");
try req.start();
try req.finish();
try req.wait();
if (req.response.status != .ok) {
return error.RequestFailed;
}
return try req.reader().readAllAlloc(self.allocator, 10 * 1024 * 1024);
}
};
API Key
Para APIs que usam chaves de API:
pub fn requestWithApiKey(
allocator: std.mem.Allocator,
url: []const u8,
api_key: []const u8,
) ![]u8 {
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
const uri = try std.Uri.parse(url);
var req = try client.request(.GET, uri, .{
.allocator = allocator,
}, .{});
defer req.deinit();
// Método 1: Header X-API-Key
try req.headers.append("X-API-Key", api_key);
// Método 2: Query parameter (para algumas APIs)
// const url_with_key = try std.fmt.allocPrint(allocator, "{s}?api_key={s}", .{url, api_key});
try req.start();
try req.finish();
try req.wait();
return try req.reader().readAllAlloc(allocator, 10 * 1024 * 1024);
}
Tratamento de Erros em HTTP
Erros Comuns e Como Tratá-los
const HttpError = error{
ConnectionFailed,
Timeout,
InvalidResponse,
ServerError,
NotFound,
Unauthorized,
TooManyRequests,
};
pub fn robustRequest(
allocator: std.mem.Allocator,
method: std.http.Method,
url: []const u8,
) ![]u8 {
var client = std.http.Client{
.allocator = allocator,
};
defer client.deinit();
const uri = std.Uri.parse(url) catch {
return error.InvalidResponse;
};
var req = client.request(method, uri, .{
.allocator = allocator,
}, .{}) catch {
return error.ConnectionFailed;
};
defer req.deinit();
try req.headers.append("User-Agent", "ZigRobustClient/1.0");
req.start() catch {
return error.ConnectionFailed;
};
req.finish() catch {
return error.ConnectionFailed;
};
req.wait() catch {
return error.Timeout;
};
// Tratamento específico por status code
switch (req.response.status) {
.ok => {},
.not_found => return error.NotFound,
.unauthorized => return error.Unauthorized,
.too_many_requests => return error.TooManyRequests,
.internal_server_error => return error.ServerError,
.bad_gateway => return error.ServerError,
.service_unavailable => return error.ServerError,
else => {
if (@intFromEnum(req.response.status) >= 500) {
return error.ServerError;
}
return error.InvalidResponse;
},
}
return req.reader().readAllAlloc(allocator, 10 * 1024 * 1024) catch {
return error.InvalidResponse;
};
}
Retry com Backoff
const RetryConfig = struct {
max_retries: u32 = 3,
base_delay_ms: u32 = 1000,
max_delay_ms: u32 = 30000,
};
pub fn requestWithRetry(
allocator: std.mem.Allocator,
url: []const u8,
config: RetryConfig,
) ![]u8 {
var delay_ms = config.base_delay_ms;
var last_error: anyerror = error.Unknown;
var i: u32 = 0;
while (i < config.max_retries) : (i += 1) {
return robustRequest(allocator, .GET, url) catch |err| {
last_error = err;
// Não retry em erros cliente (4xx)
if (err == error.NotFound or err == error.Unauthorized) {
return err;
}
// Esperar antes de retry (exceto na última tentativa)
if (i < config.max_retries - 1) {
std.time.sleep(delay_ms * std.time.ns_per_ms);
delay_ms = @min(delay_ms * 2, config.max_delay_ms); // Exponential backoff
}
continue;
};
}
return last_error;
}
Streaming e Grandes Respostas
Lendo Respostas Sem Carregar Tudo na Memória
Para arquivos grandes ou streams:
pub fn downloadFile(
allocator: std.mem.Allocator,
url: []const u8,
output_path: []const u8,
) !void {
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
const uri = try std.Uri.parse(url);
var req = try client.request(.GET, uri, .{
.allocator = allocator,
}, .{});
defer req.deinit();
try req.headers.append("User-Agent", "ZigDownloader/1.0");
try req.start();
try req.finish();
try req.wait();
if (req.response.status != .ok) {
return error.DownloadFailed;
}
// Criar arquivo de saída
const file = try std.fs.cwd().createFile(output_path, .{});
defer file.close();
// Ler em chunks para não carregar tudo na memória
var buf: [8192]u8 = undefined;
var reader = req.reader();
var writer = file.writer();
while (true) {
const bytes_read = try reader.read(&buf);
if (bytes_read == 0) break;
try writer.writeAll(buf[0..bytes_read]);
}
std.debug.print("Download completo: {s}\n", .{output_path});
}
Cliente HTTP Reutilizável
Padrão: Wrapper Idiomático
Para aplicações reais, crie um wrapper reutilizável:
const ApiClient = struct {
client: std.http.Client,
base_url: []const u8,
default_headers: std.http.Headers,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, base_url: []const u8) !ApiClient {
var headers = std.http.Headers.init(allocator);
try headers.append("User-Agent", "MyZigApp/1.0");
try headers.append("Accept", "application/json");
return ApiClient{
.client = std.http.Client{ .allocator = allocator },
.base_url = try allocator.dupe(u8, base_url),
.default_headers = headers,
.allocator = allocator,
};
}
pub fn deinit(self: *ApiClient) void {
self.client.deinit();
self.default_headers.deinit();
self.allocator.free(self.base_url);
}
pub fn get(self: *ApiClient, endpoint: []const u8) !Response {
return self.request(.GET, endpoint, null);
}
pub fn post(
self: *ApiClient,
endpoint: []const u8,
body: ?[]const u8,
) !Response {
return self.request(.POST, endpoint, body);
}
fn request(
self: *ApiClient,
method: std.http.Method,
endpoint: []const u8,
body: ?[]const u8,
) !Response {
const url = try std.fmt.allocPrint(
self.allocator,
"{s}{s}",
.{ self.base_url, endpoint }
);
defer self.allocator.free(url);
const uri = try std.Uri.parse(url);
var req = try self.client.request(method, uri, .{
.allocator = self.allocator,
.headers = self.default_headers,
}, .{});
defer req.deinit();
if (body) |b| {
try req.headers.append("Content-Type", "application/json");
}
try req.start();
if (body) |b| {
try req.writer().writeAll(b);
}
try req.finish();
try req.wait();
const response_body = try req.reader().readAllAlloc(
self.allocator,
10 * 1024 * 1024
);
return Response{
.status = req.response.status,
.body = response_body,
.allocator = self.allocator,
};
}
};
const Response = struct {
status: std.http.Status,
body: []u8,
allocator: std.mem.Allocator,
pub fn deinit(self: Response) void {
self.allocator.free(self.body);
}
pub fn json(self: Response, comptime T: type) !T {
const parsed = try std.json.parseFromSlice(T, self.allocator, self.body, .{
.ignore_unknown_fields = true,
});
defer parsed.deinit();
return parsed.value;
}
};
Exemplo Completo: CLI de Previsão do Tempo
Vamos criar uma aplicação CLI completa que consome uma API de previsão do tempo:
const std = @import("std");
const WeatherResponse = struct {
current: CurrentWeather,
location: Location,
};
const CurrentWeather = struct {
temp_c: f32,
condition: Condition,
humidity: u32,
wind_kph: f32,
};
const Condition = struct {
text: []const u8,
};
const Location = struct {
name: []const u8,
country: []const u8,
};
pub fn fetchWeather(
allocator: std.mem.Allocator,
city: []const u8,
api_key: []const u8,
) !WeatherResponse {
// URL encode da cidade
var encoded_city = std.ArrayList(u8).init(allocator);
defer encoded_city.deinit();
for (city) |c| {
if (c == ' ') {
try encoded_city.append('%');
try encoded_city.append('2');
try encoded_city.append('0');
} else {
try encoded_city.append(c);
}
}
const url = try std.fmt.allocPrint(
allocator,
"https://api.weatherapi.com/v1/current.json?key={s}&q={s}&aqi=no",
.{ api_key, encoded_city.items }
);
defer allocator.free(url);
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
const uri = try std.Uri.parse(url);
var req = try client.request(.GET, uri, .{
.allocator = allocator,
}, .{});
defer req.deinit();
try req.start();
try req.finish();
try req.wait();
if (req.response.status != .ok) {
return error.WeatherApiError;
}
const body = try req.reader().readAllAlloc(allocator, 1024 * 1024);
defer allocator.free(body);
const parsed = try std.json.parseFromSlice(WeatherResponse, allocator, body, .{
.ignore_unknown_fields = true,
});
defer parsed.deinit();
return parsed.value;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Argumentos da linha de comando
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
if (args.len < 3) {
std.debug.print("Uso: {s} <cidade> <api_key>\n", .{args[0]});
std.process.exit(1);
}
const city = args[1];
const api_key = args[2];
std.debug.print("Buscando previsão para: {s}...\n", .{city});
const weather = fetchWeather(allocator, city, api_key) catch |err| {
std.debug.print("Erro ao buscar previsão: {}\n", .{err});
std.process.exit(1);
};
std.debug.print("\n📍 {s}, {s}\n", .{ weather.location.name, weather.location.country });
std.debug.print("🌡️ Temperatura: {d:.1}°C\n", .{weather.current.temp_c});
std.debug.print("☁️ Condição: {s}\n", .{weather.current.condition.text});
std.debug.print("💧 Umidade: {d}%\n", .{weather.current.humidity});
std.debug.print("💨 Vento: {d:.1} km/h\n", .{weather.current.wind_kph});
}
Melhores Práticas
1. Sempre Use defer para Cleanup
// ✅ Bom: cleanup automático
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
var req = try client.request(.GET, uri, .{ .allocator = allocator }, .{});
defer req.deinit();
// ❌ Ruim: esquecer de deinit pode vazar memória
2. Limite o Tamanho das Respostas
// ✅ Bom: limite para evitar OOM
const body = try req.reader().readAllAlloc(allocator, 10 * 1024 * 1024);
// ❌ Ruim: sem limite pode causar problemas
// const body = try req.reader().readAllAlloc(allocator, std.math.maxInt(usize));
3. Reuse Conexões Quando Possível
// ✅ Bom: cliente reutilizado
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
// Múltiplas requisições com o mesmo cliente
for (urls) |url| {
// req = client.request(...) reusa conexões
}
4. Trate Timeouts Adequadamente
// Configurar timeout na criação do cliente
var client = std.http.Client{
.allocator = allocator,
};
// HTTP client Zig atual não expõe timeout diretamente
// Considere usar threads async para timeout manual
5. Valide Status Codes
// ✅ Bom: tratar diferentes status
switch (req.response.status) {
.ok => {},
.not_found => return error.NotFound,
.unauthorized => return error.Unauthorized,
else => return error.RequestFailed,
}
Limitações Atuais
O Que Ainda Está Evoluindo
- HTTP/2: Suporte ainda parcial em algumas versões
- WebSockets: Não há suporte nativo, use bibliotecas externas
- Client-side certificates: Suporte limitado
- Proxy configuration: Configuração manual necessária
Para casos avançados, considere bibliotecas como zig-network ou integrações com libcurl via C interop.
Próximos Passos
Agora que você domina requisições HTTP em Zig, explore:
- Criando um Servidor HTTP em Zig — Aprenda a receber requisições, não apenas fazer
- Parsing JSON em Zig — Aprofunde o trabalho com APIs REST
- Tratamento de Erros em Zig — Padrões avançados de error handling
- Async/Await em Zig — Para requests HTTP concorrentes
Consumindo APIs com Zig é simples e eficiente. Compartilhe o que você construiu com a comunidade!