std.http.Server em Zig: Referência para Servidor HTTP e API REST

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, pools ou um framework leve quando o tráfego exigir concorrência maior.

Esta página é a referência rápida para quem pesquisa por std.http.Server em Zig, zig HTTP server, zig server ou servidor HTTP em Zig. Se você quer um tutorial guiado, comece por Zig Server HTTP: API REST com std.http.Server. Se a aplicação já vai para produção, leia também o checklist de Zig HTTP Server em Produção.

Resposta rápida: quando usar std.http.Server

ObjetivoUse std.http.Server assimPróximo passo
Aprender o fluxo HTTP realAceite TCP com std.net, crie http.Server.init, chame receiveHead e responda com request.respondSiga os exemplos desta referência
Criar uma API REST pequenaFaça roteamento por method e target, responda JSON e valide status explicitamenteVeja Zig Server HTTP
Servir health checks internosResponda /healthz ou /readyz com JSON barato e cache-control: no-storeCombine com produção
Escolher entre stdlib e frameworkComece na stdlib para entender custo, buffers e erros; migre para httpz/zap se precisar de roteamento e middlewareCompare em httpz e Zap
Consumir uma API externaUse std.http.Client, não std.http.ServerLeia std.http.Client

Em resumo: std.http.Server é uma boa escolha para estudo, CLIs que expõem endpoints locais, automações internas, health checks e APIs pequenas. Para um produto público, ele continua útil, mas você precisa documentar limites de body, timeouts, observabilidade, encerramento gracioso e o reverse proxy na frente.

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.

Na prática, a biblioteca padrão não tenta ser um framework completo. Ela entrega primitivas previsíveis: conexão, buffer de headers, leitura da cabeça da requisição, corpo via reader e resposta com status/cabeçalhos. Isso é suficiente para entender como uma API Zig funciona por baixo e para manter serviços pequenos sem dependências extras.

Se você está montando um backend com rotas, JSON, autenticação e observabilidade, pense em camadas:

  1. Borda: Cloudflare, Nginx, Caddy ou Traefik cuidam de TLS, compressão e limites globais.
  2. Processo Zig: std.http.Server recebe a conexão local, valida rota, body e método.
  3. Domínio: código da aplicação trata regras de negócio, banco e filas.
  4. Cliente HTTP: integrações externas usam std.http.Client, com limites e tratamento de status.

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

Checklist antes de copiar o exemplo

Antes de transformar um exemplo em serviço, confira estes pontos:

PontoPor que importa
Buffer de headersDefina tamanho explícito, por exemplo var buf: [8192]u8 = undefined;, e trate erro de headers grandes
Método e rotaValide request.head.method e request.head.target; não assuma que tudo é GET /
Corpo da requisiçãoLeia com limite conhecido; readAllAlloc sem limite adequado vira risco de memória
Tipo de respostaEnvie content-type correto para HTML, texto e JSON
Status HTTPDiferencie 200, 400, 404, 405 e 500; status explícito melhora debug e SEO de endpoints públicos
ConcorrênciaO loop simples atende uma conexão por vez; produção precisa workers, threads, event loop/framework ou proxy agressivo
OperaçãoTenha /healthz, logs, timeout na borda e plano de rollback

O guia de HTTP server em produção aprofunda proxy, logs, limite de body e health check. Esta página fica focada na API da biblioteca padrão.

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

Perguntas frequentes sobre std.http.Server

std.http.Server substitui um framework web?

Para projetos pequenos, sim. Você consegue aceitar conexões, ler método e rota, responder JSON e montar helpers simples de roteamento. Para APIs públicas com muitas rotas, autenticação, middleware, WebSockets ou alto volume, avalie httpz e Zap depois de entender a stdlib.

Como criar uma API REST em Zig?

O caminho mínimo é: abrir um listener TCP com std.net.Address.listen, criar std.http.Server para cada conexão, chamar receiveHead, comparar request.head.method e request.head.target, ler o body com limite quando houver POST/PUT e responder com JSON usando request.respond. O tutorial de Zig Server HTTP mostra esse fluxo em formato guiado.

Posso expor std.http.Server direto na internet?

Para estudo local, sim. Para produção, prefira rodar o binário Zig atrás de Nginx, Caddy, Traefik, Cloudflare Tunnel ou outro proxy. A borda resolve TLS, HTTP/2, certificados, compressão, rate limiting e limites globais, enquanto o processo Zig cuida da lógica da aplicação.

Qual a relação entre std.http.Server e std.http.Client?

std.http.Server recebe requisições. std.http.Client faz requisições para outros serviços. Uma API real muitas vezes usa os dois: server para expor endpoints e client para chamar APIs externas, filas HTTP, webhooks ou serviços internos.

receiveHead lê o corpo da requisição?

Não. receiveHead lê e parseia a linha inicial e os headers. O corpo precisa ser lido depois, via reader da requisição, sempre com limite adequado. Isso evita aceitar payloads grandes demais por acidente.

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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