Introdução
Requisições HTTP POST são usadas para enviar dados ao servidor, como payloads JSON, formulários e comandos para uma API REST. Diferente do GET, que apenas solicita dados, o POST normalmente cria recursos, dispara uma ação ou envia um corpo que precisa ser validado no outro lado.
Nesta receita, você aprenderá a fazer HTTP POST em Zig usando std.http.Client, com Content-Type, content_length, escrita do corpo, validação de status e leitura segura da resposta. Se você quer o guia completo de cliente HTTP, leia Zig HTTP Client: GET, POST, JSON e APIs REST em Zig. Se o objetivo é receber esse POST em um serviço próprio, combine com Zig Server HTTP: API REST com std.http.Server.
Resposta rápida: POST em Zig com std.http.Client
| Objetivo | Caminho em Zig | Próximo link |
|---|---|---|
| Enviar JSON para uma API REST | client.open(.POST, uri, ...), header Content-Type: application/json, content_length e req.write() | Continue nesta receita |
| Gerar JSON a partir de uma struct | std.json.stringify em um ArrayList(u8) antes de abrir o POST | Como gerar JSON em Zig |
| Fazer um GET antes do POST | client.open(.GET, ...) e validação de status | Como fazer requisições HTTP GET em Zig |
| Entender a API da biblioteca padrão | std.http.Client.fetch, open, headers, TLS e storage da resposta | std.http.Client em Zig |
| Criar a API que recebe o POST | std.http.Server, roteamento, JSON e respostas explícitas | Zig Server HTTP: API REST com std.http.Server |
O fluxo mínimo é sempre o mesmo: parsear a URL, abrir a requisição com método .POST, declarar os headers, definir o tamanho do corpo, chamar send(), escrever o payload, finalizar com finish(), aguardar com wait() e validar req.status antes de confiar na resposta.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
- Familiaridade com requisições HTTP GET
- Noções de JSON. Consulte Parsing JSON em Zig quando a resposta também precisar ser interpretada.
POST com Corpo JSON
O caso mais comum é enviar JSON para uma API. Em Zig, a parte importante é tornar o contrato HTTP explícito: header correto, content_length definido e status validado depois da resposta.
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();
const uri = try std.Uri.parse("https://httpbin.org/post");
var header_buf: [4096]u8 = undefined;
var req = try client.open(.POST, uri, .{
.server_header_buffer = &header_buf,
.extra_headers = &.{
.{ .name = "Content-Type", .value = "application/json" },
.{ .name = "Accept", .value = "application/json" },
.{ .name = "User-Agent", .value = "ZigBrasilPost/1.0" },
},
});
defer req.deinit();
// Definir o corpo da requisição
const json_body =
\\{"nome": "Maria Silva", "email": "[email protected]", "idade": 28}
;
req.transfer_encoding = .{ .content_length = json_body.len };
try req.send();
// Escrever o corpo
var written: usize = 0;
while (written < json_body.len) {
written += try req.write(json_body[written..]);
}
try req.finish();
try req.wait();
if (req.status != .ok and req.status != .created) {
std.debug.print("Erro HTTP: {}\n", .{req.status});
return;
}
// 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("Resposta: {s}\n", .{body});
}
Esse exemplo usa httpbin.org apenas como destino de teste. Em produção, prefira URLs configuráveis, timeout definido na borda da aplicação e limite de resposta menor quando você conhece o tamanho esperado. O guia Zig HTTP Server em Produção cobre o outro lado desse contrato: limites de corpo, logs, health check e proxy.
Saída esperada
Status: http.Status.ok
Resposta: {
"data": "{\"nome\": \"Maria Silva\", \"email\": \"[email protected]\", \"idade\": 28}",
...
}
POST com Struct Serializada para JSON
Serialize uma struct Zig diretamente para JSON antes de enviar:
const std = @import("std");
const Usuario = struct {
nome: []const u8,
email: []const u8,
idade: u32,
ativo: bool,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Criar o objeto a ser enviado
const usuario = Usuario{
.nome = "João Santos",
.email = "[email protected]",
.idade = 35,
.ativo = true,
};
// Serializar para JSON
var json_buf = std.ArrayList(u8).init(allocator);
defer json_buf.deinit();
try std.json.stringify(usuario, .{}, json_buf.writer());
const json_body = json_buf.items;
std.debug.print("JSON a enviar: {s}\n", .{json_body});
// Enviar a requisição POST
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
const uri = try std.Uri.parse("https://httpbin.org/post");
var header_buf: [4096]u8 = undefined;
var req = try client.open(.POST, uri, .{
.server_header_buffer = &header_buf,
.extra_headers = &.{
.{ .name = "Content-Type", .value = "application/json" },
.{ .name = "Accept", .value = "application/json" },
},
});
defer req.deinit();
req.transfer_encoding = .{ .content_length = json_body.len };
try req.send();
var written: usize = 0;
while (written < json_body.len) {
written += try req.write(json_body[written..]);
}
try req.finish();
try req.wait();
if (req.status == .ok or req.status == .created) {
std.debug.print("Usuário criado com sucesso!\n", .{});
} else {
std.debug.print("Erro: {}\n", .{req.status});
}
}
POST com Form URL-Encoded
Envie dados de formulário no formato application/x-www-form-urlencoded:
const std = @import("std");
fn urlEncode(allocator: std.mem.Allocator, params: []const [2][]const u8) ![]u8 {
var result = std.ArrayList(u8).init(allocator);
errdefer result.deinit();
for (params, 0..) |param, i| {
if (i > 0) try result.append('&');
// Chave
for (param[0]) |c| {
if (std.ascii.isAlphanumeric(c) or c == '-' or c == '_' or c == '.' or c == '~') {
try result.append(c);
} else if (c == ' ') {
try result.append('+');
} else {
try result.appendSlice(&.{ '%', std.fmt.digitToChar(c >> 4, .upper), std.fmt.digitToChar(c & 0xf, .upper) });
}
}
try result.append('=');
// Valor
for (param[1]) |c| {
if (std.ascii.isAlphanumeric(c) or c == '-' or c == '_' or c == '.' or c == '~') {
try result.append(c);
} else if (c == ' ') {
try result.append('+');
} else {
try result.appendSlice(&.{ '%', std.fmt.digitToChar(c >> 4, .upper), std.fmt.digitToChar(c & 0xf, .upper) });
}
}
}
return result.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Codificar parâmetros do formulário
const params = [_][2][]const u8{
.{ "usuario", "maria_silva" },
.{ "senha", "minha_senha_123" },
.{ "lembrar", "true" },
};
const form_body = try urlEncode(allocator, ¶ms);
defer allocator.free(form_body);
std.debug.print("Form body: {s}\n", .{form_body});
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
const uri = try std.Uri.parse("https://httpbin.org/post");
var header_buf: [4096]u8 = undefined;
var req = try client.open(.POST, uri, .{
.server_header_buffer = &header_buf,
.extra_headers = &.{
.{ .name = "Content-Type", .value = "application/x-www-form-urlencoded" },
},
});
defer req.deinit();
req.transfer_encoding = .{ .content_length = form_body.len };
try req.send();
var written: usize = 0;
while (written < form_body.len) {
written += try req.write(form_body[written..]);
}
try req.finish();
try req.wait();
std.debug.print("Status: {}\n", .{req.status});
}
Função POST Reutilizável
Uma função genérica ajuda quando o POST aparece em mais de um ponto do programa. Mantenha o tamanho máximo da resposta e os status aceitos perto da função, para não espalhar política HTTP pelo código.
const std = @import("std");
const PostResponse = struct {
status: std.http.Status,
body: []const u8,
allocator: std.mem.Allocator,
pub fn deinit(self: *PostResponse) void {
self.allocator.free(self.body);
}
};
fn httpPost(
allocator: std.mem.Allocator,
url: []const u8,
content_type: []const u8,
payload: []const u8,
) !PostResponse {
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
const uri = try std.Uri.parse(url);
var header_buf: [8192]u8 = undefined;
var req = try client.open(.POST, uri, .{
.server_header_buffer = &header_buf,
.extra_headers = &.{
.{ .name = "Content-Type", .value = content_type },
.{ .name = "Accept", .value = "application/json" },
.{ .name = "User-Agent", .value = "ZigBrasilPost/1.0" },
},
});
defer req.deinit();
req.transfer_encoding = .{ .content_length = payload.len };
try req.send();
var written: usize = 0;
while (written < payload.len) {
written += try req.write(payload[written..]);
}
try req.finish();
try req.wait();
if (req.status != .ok and req.status != .created and req.status != .accepted) {
return error.HttpStatusRejected;
}
const body = try req.reader().readAllAlloc(allocator, 4 * 1024 * 1024);
return .{
.status = req.status,
.body = body,
.allocator = allocator,
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const json =
\\{"titulo": "Meu Post", "conteudo": "Olá mundo!"}
;
var resp = try httpPost(
allocator,
"https://httpbin.org/post",
"application/json",
json,
);
defer resp.deinit();
std.debug.print("Status: {}\n", .{resp.status});
std.debug.print("Corpo ({d} bytes)\n", .{resp.body.len});
}
Dicas e Boas Práticas
Sempre defina Content-Type: O servidor precisa saber o formato dos dados enviados.
Defina
transfer_encoding: Sempre configure ocontent_lengthantes de enviar o corpo.Defina
AccepteUser-Agent: APIs públicas e internas ficam mais fáceis de depurar quando o cliente declara o formato esperado e se identifica.Trate erros de rede: Conexões podem falhar. Use tratamento de erros adequado e diferencie erro de conexão, status HTTP rejeitado e JSON inválido.
Valide a resposta: Verifique sempre o código de status HTTP retornado antes de fazer parse do corpo.
Limite memória de resposta:
readAllAllocdeve receber um teto coerente com a API. Não use limites enormes por padrão em CLIs, workers ou serviços.Serialize corretamente: Use geração de JSON para criar payloads complexos em vez de montar JSON manualmente.
Conecte com o resto da aplicação: Se o POST faz parte de um backend, leia também filas e workers em background e observabilidade em Zig para decidir retry, logs e métricas.
Receitas Relacionadas
- Como fazer requisições HTTP GET em Zig - Requisições de leitura
- Como parsear JSON em Zig - Processar respostas JSON
- Como gerar JSON em Zig - Serializar dados para envio
- Como parsear URLs em Zig - Manipulação de URLs
- std.http.Client em Zig - Referência da API padrão