std.http.Server em Zig — Referência e Exemplos

std.http.Server — Servidor HTTP

O std.http.Server permite construir servidores HTTP/1.1 diretamente na biblioteca padrão do Zig, sem dependências externas. Ele processa requisições de forma síncrona, parseando cabeçalhos, métodos e caminhos, e permite enviar respostas com status, cabeçalhos e corpo customizados. Para servidores em produção, combine com threads ou pools para atender múltiplos clientes simultaneamente.

Visão Geral

const std = @import("std");
const http = std.http;

O servidor HTTP do Zig funciona sobre o std.net.Server (TCP), adicionando a camada de parsing e geração do protocolo HTTP.

Fluxo Básico

  1. Cria um net.Server (TCP listener)
  2. Aceita uma conexão TCP
  3. Cria um http.Server sobre essa conexão
  4. Recebe o cabeçalho da requisição com receiveHead
  5. Envia a resposta com respond ou respondStreaming

Funções Principais

Criação

// Cria servidor HTTP sobre uma conexão TCP aceita
pub fn init(connection: net.Server.Connection, buf: []u8) Server

Recebimento de Requisições

// Recebe e parseia o cabeçalho da requisição
pub fn receiveHead(self: *Server) !Request

// A struct Request contém:
pub const Request = struct {
    pub const Head = struct {
        method: http.Method,
        target: []const u8,
        version: http.Version,
    };

    head: Head,

    // Envia resposta simples
    pub fn respond(self: *Request, body: []const u8, options: ResponseOptions) !void

    // Inicia resposta streaming
    pub fn respondStreaming(self: *Request, options: ResponseOptions) !ResponseWriter
};

Opções de Resposta

pub const ResponseOptions = struct {
    status: http.Status = .ok,
    extra_headers: []const http.Header = &.{},
    transfer_encoding: TransferEncoding = .none,
};

Exemplo 1: Servidor HTTP com Roteamento

const std = @import("std");
const http = std.http;
const net = std.net;

fn handleRequest(request: *http.Server.Request) !void {
    const target = request.head.target;
    const method = request.head.method;

    if (method == .GET and std.mem.eql(u8, target, "/")) {
        try request.respond(
            \\<html><body>
            \\<h1>Bem-vindo ao servidor Zig!</h1>
            \\<p><a href="/sobre">Sobre</a> | <a href="/api/status">Status API</a></p>
            \\</body></html>
        , .{
            .extra_headers = &.{
                .{ .name = "content-type", .value = "text/html; charset=utf-8" },
            },
        });
    } else if (method == .GET and std.mem.eql(u8, target, "/sobre")) {
        try request.respond("Servidor HTTP escrito em Zig", .{
            .extra_headers = &.{
                .{ .name = "content-type", .value = "text/plain; charset=utf-8" },
            },
        });
    } else if (method == .GET and std.mem.eql(u8, target, "/api/status")) {
        try request.respond(
            \\{"status":"ok","versao":"1.0.0"}
        , .{
            .extra_headers = &.{
                .{ .name = "content-type", .value = "application/json" },
            },
        });
    } else {
        try request.respond("404 - Página não encontrada", .{
            .status = .not_found,
            .extra_headers = &.{
                .{ .name = "content-type", .value = "text/plain; charset=utf-8" },
            },
        });
    }
}

pub fn main() !void {
    const addr = try net.Address.parseIp4("127.0.0.1", 8080);
    var server = try addr.listen(.{ .reuse_address = true });
    defer server.deinit();

    std.debug.print("Servidor em http://127.0.0.1:8080\n", .{});

    while (true) {
        var connection = server.accept() catch continue;
        var buf: [8192]u8 = undefined;
        var http_server = http.Server.init(connection, &buf);

        while (true) {
            var request = http_server.receiveHead() catch break;
            handleRequest(&request) catch break;
        }
    }
}

Exemplo 2: API REST com JSON

const std = @import("std");
const http = std.http;
const net = std.net;

fn responderJson(request: *http.Server.Request, body: []const u8, status: http.Status) !void {
    try request.respond(body, .{
        .status = status,
        .extra_headers = &.{
            .{ .name = "content-type", .value = "application/json" },
            .{ .name = "access-control-allow-origin", .value = "*" },
        },
    });
}

fn handleApi(request: *http.Server.Request, allocator: std.mem.Allocator) !void {
    const target = request.head.target;
    const method = request.head.method;

    if (method == .GET and std.mem.startsWith(u8, target, "/api/usuarios")) {
        const resposta = try std.json.stringifyAlloc(allocator, .{
            .usuarios = [_]struct { id: u32, nome: []const u8 }{
                .{ .id = 1, .nome = "Ana" },
                .{ .id = 2, .nome = "Bruno" },
            },
        }, .{ .whitespace = .indent_2 });
        defer allocator.free(resposta);

        try responderJson(request, resposta, .ok);
    } else if (method == .GET and std.mem.eql(u8, target, "/api/health")) {
        try responderJson(request, "{\"status\":\"healthy\"}", .ok);
    } else {
        try responderJson(
            request,
            "{\"error\":\"Rota não encontrada\"}",
            .not_found,
        );
    }
}

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

    const addr = try net.Address.parseIp4("127.0.0.1", 3000);
    var server = try addr.listen(.{ .reuse_address = true });
    defer server.deinit();

    std.debug.print("API em http://127.0.0.1:3000\n", .{});

    while (true) {
        var connection = server.accept() catch continue;
        var buf: [8192]u8 = undefined;
        var http_server = http.Server.init(connection, &buf);

        while (true) {
            var request = http_server.receiveHead() catch break;
            handleApi(&request, allocator) catch break;
        }
    }
}

Exemplo 3: Servidor com Logging e Middleware

const std = @import("std");
const http = std.http;
const net = std.net;

fn logRequest(method: http.Method, target: []const u8, status: http.Status) void {
    std.debug.print("[{s}] {s} -> {d}\n", .{
        @tagName(method),
        target,
        @intFromEnum(status),
    });
}

fn servirArquivoEstatico(request: *http.Server.Request) !void {
    const target = request.head.target;

    // Mapa simples de rotas para conteúdo
    const rotas = [_]struct { path: []const u8, content: []const u8, mime: []const u8 }{
        .{ .path = "/", .content = "<h1>Home</h1>", .mime = "text/html" },
        .{ .path = "/style.css", .content = "body { font-family: sans-serif; }", .mime = "text/css" },
        .{ .path = "/app.js", .content = "console.log('Zig server');", .mime = "application/javascript" },
    };

    for (rotas) |rota| {
        if (std.mem.eql(u8, target, rota.path)) {
            try request.respond(rota.content, .{
                .extra_headers = &.{
                    .{ .name = "content-type", .value = rota.mime },
                },
            });
            logRequest(request.head.method, target, .ok);
            return;
        }
    }

    try request.respond("Not Found", .{ .status = .not_found });
    logRequest(request.head.method, target, .not_found);
}

pub fn main() !void {
    const addr = try net.Address.parseIp4("0.0.0.0", 8080);
    var server = try addr.listen(.{ .reuse_address = true });
    defer server.deinit();

    std.debug.print("Servidor estático em http://0.0.0.0:8080\n", .{});

    while (true) {
        var connection = server.accept() catch continue;
        var buf: [8192]u8 = undefined;
        var http_server = http.Server.init(connection, &buf);

        while (true) {
            var request = http_server.receiveHead() catch break;
            servirArquivoEstatico(&request) catch break;
        }
    }
}

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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