Fazer requisições HTTP é uma necessidade fundamental em quase todo software moderno. Seja para consumir APIs REST, fazer webhooks, ou integrar com serviços externos, você precisa saber como fazer requisições HTTP eficientemente. Neste tutorial, vamos explorar o std.http.Client de Zig e criar exemplos práticos desde requisições simples até casos de uso avançados.
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!