Como Fazer Requisições HTTP POST em Zig

Introdução

Requisições HTTP POST são usadas para enviar dados ao servidor, como formulários, payloads JSON ou uploads de arquivos. Diferente do GET que apenas solicita dados, o POST modifica ou cria recursos no servidor.

Nesta receita, você aprenderá a fazer requisições POST em Zig usando std.http.Client, enviando diferentes tipos de dados.

Pré-requisitos

POST com Corpo JSON

O caso mais comum: enviar dados JSON para uma API:

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" },
        },
    });
    defer req.deinit();

    // Definir o corpo da requisição
    const json_body =
        \\{"nome": "Maria Silva", "email": "maria@exemplo.com", "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();

    // 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});
}

Saída esperada

Status: http.Status.ok
Resposta: {
  "data": "{\"nome\": \"Maria Silva\", \"email\": \"maria@exemplo.com\", \"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 = "joao@exemplo.com",
        .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, &params);
    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 para facilitar requisições POST:

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 },
        },
    });
    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();

    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

  1. Sempre defina Content-Type: O servidor precisa saber o formato dos dados enviados.

  2. Defina transfer_encoding: Sempre configure o content_length antes de enviar o corpo.

  3. Trate erros de rede: Conexões podem falhar. Use tratamento de erros adequado.

  4. Valide a resposta: Verifique sempre o código de status HTTP retornado.

  5. Serialize corretamente: Use geração de JSON para criar payloads complexos.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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