Como Fazer Requisições HTTP GET em Zig

Introdução

Requisições HTTP GET são a forma mais comum de buscar dados da web. Em Zig, a biblioteca padrão oferece std.http.Client para fazer requisições HTTP de forma eficiente e com controle total sobre headers, timeouts e tratamento de erros.

Nesta receita, você aprenderá a fazer requisições HTTP GET, processar respostas, lidar com headers e consumir APIs REST.

Pré-requisitos

Requisição GET Simples

O exemplo mais básico de uma requisição HTTP GET:

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Criar cliente HTTP
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    // Fazer a requisição GET
    const uri = try std.Uri.parse("http://httpbin.org/get");

    var header_buf: [4096]u8 = undefined;
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &header_buf,
    });
    defer req.deinit();

    try req.send();
    try req.wait();

    // Verificar status
    if (req.status != .ok) {
        std.debug.print("Erro HTTP: {}\n", .{req.status});
        return;
    }

    // Ler o corpo da resposta
    var body = std.ArrayList(u8).init(allocator);
    defer body.deinit();

    var buf: [4096]u8 = undefined;
    while (true) {
        const bytes_read = try req.reader().read(&buf);
        if (bytes_read == 0) break;
        try body.appendSlice(buf[0..bytes_read]);
    }

    std.debug.print("Status: {}\n", .{req.status});
    std.debug.print("Corpo: {s}\n", .{body.items});
}

Saída esperada

Status: http.Status.ok
Corpo: {
  "args": {},
  "headers": {
    "Host": "httpbin.org",
    ...
  },
  "url": "http://httpbin.org/get"
}

GET com Headers Customizados

Adicione headers personalizados à requisição:

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://api.example.com/data");

    var header_buf: [4096]u8 = undefined;
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &header_buf,
        .extra_headers = &.{
            .{ .name = "Authorization", .value = "Bearer meu-token-aqui" },
            .{ .name = "Accept", .value = "application/json" },
            .{ .name = "User-Agent", .value = "ZigClient/1.0" },
        },
    });
    defer req.deinit();

    try req.send();
    try req.wait();

    std.debug.print("Status: {}\n", .{req.status});
}

Função Reutilizável de GET

Encapsule a lógica em uma função para reutilização:

const std = @import("std");

const HttpResponse = struct {
    status: std.http.Status,
    body: []const u8,
    allocator: std.mem.Allocator,

    pub fn deinit(self: *HttpResponse) void {
        self.allocator.free(self.body);
    }
};

fn httpGet(allocator: std.mem.Allocator, url: []const u8) !HttpResponse {
    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(.GET, uri, .{
        .server_header_buffer = &header_buf,
    });
    defer req.deinit();

    try req.send();
    try req.wait();

    // Ler corpo completo
    const body = try req.reader().readAllAlloc(allocator, 1024 * 1024); // máx 1 MB

    return .{
        .status = req.status,
        .body = body,
        .allocator = allocator,
    };
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Usar a função reutilizável
    var response = try httpGet(allocator, "http://httpbin.org/get");
    defer response.deinit();

    std.debug.print("Status: {}\n", .{response.status});
    std.debug.print("Tamanho do corpo: {d} bytes\n", .{response.body.len});
}

Consumindo uma API REST com JSON

Combine HTTP GET com parsing JSON para consumir APIs:

const std = @import("std");

const Todo = struct {
    userId: i64,
    id: i64,
    title: []const u8,
    completed: bool,
};

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://jsonplaceholder.typicode.com/todos/1");

    var header_buf: [4096]u8 = undefined;
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &header_buf,
    });
    defer req.deinit();

    try req.send();
    try req.wait();

    if (req.status != .ok) {
        std.debug.print("Erro: {}\n", .{req.status});
        return;
    }

    // Ler corpo
    const body = try req.reader().readAllAlloc(allocator, 1024 * 1024);
    defer allocator.free(body);

    // Parsear JSON
    const parsed = try std.json.parseFromSlice(Todo, allocator, body, .{
        .ignore_unknown_fields = true,
    });
    defer parsed.deinit();

    const todo = parsed.value;
    std.debug.print("Todo #{d}:\n", .{todo.id});
    std.debug.print("  Título: {s}\n", .{todo.title});
    std.debug.print("  Completo: {}\n", .{todo.completed});
    std.debug.print("  Usuário: {d}\n", .{todo.userId});
}

Saída esperada

Todo #1:
  Título: delectus aut autem
  Completo: false
  Usuário: 1

Tratamento de Erros HTTP

Trate diferentes códigos de status HTTP adequadamente:

const std = @import("std");

fn handleHttpResponse(status: std.http.Status) void {
    switch (status) {
        .ok => std.debug.print("Sucesso (200)!\n", .{}),
        .not_found => std.debug.print("Recurso não encontrado (404).\n", .{}),
        .unauthorized => std.debug.print("Não autorizado (401). Verifique suas credenciais.\n", .{}),
        .forbidden => std.debug.print("Acesso proibido (403).\n", .{}),
        .internal_server_error => std.debug.print("Erro interno do servidor (500).\n", .{}),
        .too_many_requests => std.debug.print("Muitas requisições (429). Aguarde.\n", .{}),
        else => std.debug.print("Status inesperado: {d}\n", .{@intFromEnum(status)}),
    }
}

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("http://httpbin.org/status/404");

    var header_buf: [4096]u8 = undefined;
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &header_buf,
    });
    defer req.deinit();

    try req.send();
    try req.wait();

    handleHttpResponse(req.status);
}

Dicas e Boas Práticas

  1. Sempre libere recursos: Use defer para client.deinit() e req.deinit().

  2. Verifique o status: Nem toda resposta é 200 OK. Sempre verifique o código de status.

  3. Limite o tamanho da resposta: Use readAllAlloc com um limite máximo para evitar uso excessivo de memória.

  4. Reutilize o client: Se fizer múltiplas requisições, reutilize a instância de std.http.Client.

  5. Para JSON: Combine com parsing JSON em Zig para APIs REST.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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