No artigo anterior, construímos uma API REST com CRUD. Agora vamos adicionar uma camada essencial: middlewares. Middlewares são funções que processam a requisição antes (ou depois) do handler principal, permitindo adicionar funcionalidades transversais de forma modular.
O Conceito de Middleware
Um middleware em Zig segue um padrão simples: recebe o contexto, faz algum processamento, e decide se passa a requisição adiante ou retorna uma resposta.
const std = @import("std");
const Context = struct {
request: *std.http.Server.Request,
allocator: std.mem.Allocator,
headers_extras: std.ArrayList(std.http.Header),
timestamp_inicio: i64,
fn init(alloc: std.mem.Allocator, req: *std.http.Server.Request) Context {
return .{
.request = req,
.allocator = alloc,
.headers_extras = std.ArrayList(std.http.Header).init(alloc),
.timestamp_inicio = std.time.milliTimestamp(),
};
}
fn addHeader(self: *Context, nome: []const u8, valor: []const u8) !void {
try self.headers_extras.append(.{ .name = nome, .value = valor });
}
};
// Tipo de middleware: recebe contexto e next, retorna se deve continuar
const Middleware = *const fn (*Context, NextFn) anyerror!void;
const NextFn = *const fn (*Context) anyerror!void;
Implementando uma Cadeia de Middlewares
const MiddlewareChain = struct {
middlewares: std.ArrayList(Middleware),
handler_final: NextFn,
fn init(allocator: std.mem.Allocator, handler: NextFn) MiddlewareChain {
return .{
.middlewares = std.ArrayList(Middleware).init(allocator),
.handler_final = handler,
};
}
fn deinit(self: *MiddlewareChain) void {
self.middlewares.deinit();
}
fn use(self: *MiddlewareChain, mw: Middleware) !void {
try self.middlewares.append(mw);
}
fn executar(self: *MiddlewareChain, ctx: *Context) !void {
if (self.middlewares.items.len == 0) {
try self.handler_final(ctx);
return;
}
// Executar middlewares em sequência
var indice: usize = 0;
const chain = self;
const Executor = struct {
fn next(c: *Context) anyerror!void {
_ = c;
// Simplificado: em produção, use closure ou state machine
}
};
// Executar primeiro middleware
try chain.middlewares.items[indice](ctx, Executor.next);
}
};
Middleware 1: Logger de Requisições
fn loggingMiddleware(ctx: *Context, next: NextFn) anyerror!void {
const inicio = std.time.milliTimestamp();
const metodo = @tagName(ctx.request.head.method);
const caminho = ctx.request.head.target;
std.debug.print("[{d}] {s} {s}", .{ inicio, metodo, caminho });
// Executar próximo middleware/handler
try next(ctx);
// Log de conclusão
const duracao = std.time.milliTimestamp() - inicio;
std.debug.print(" -> {d}ms\n", .{duracao});
}
Saída de exemplo:
[1708531200000] GET /api/tarefas -> 2ms
[1708531200050] POST /api/tarefas -> 5ms
Middleware 2: CORS (Cross-Origin Resource Sharing)
const CorsConfig = struct {
origens_permitidas: []const []const u8,
metodos_permitidos: []const u8,
headers_permitidos: []const u8,
max_age: []const u8,
};
fn corsMiddleware(ctx: *Context, next: NextFn) anyerror!void {
// Adicionar headers CORS
try ctx.addHeader("Access-Control-Allow-Origin", "*");
try ctx.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
try ctx.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
try ctx.addHeader("Access-Control-Max-Age", "86400");
// Se for preflight (OPTIONS), responder imediatamente
if (ctx.request.head.method == .OPTIONS) {
try ctx.request.respond("", .{
.status = .no_content,
.extra_headers = ctx.headers_extras.items,
});
return; // Não chama next — resposta já enviada
}
// Para outros métodos, continuar a cadeia
try next(ctx);
}
Middleware 3: Autenticação por Token
const AuthConfig = struct {
token_valido: []const u8,
rotas_publicas: []const []const u8,
};
fn criarAuthMiddleware(config: AuthConfig) Middleware {
const Impl = struct {
var cfg: AuthConfig = undefined;
fn middleware(ctx: *Context, next: NextFn) anyerror!void {
// Verificar se a rota é pública
for (cfg.rotas_publicas) |rota| {
if (std.mem.eql(u8, ctx.request.head.target, rota)) {
try next(ctx);
return;
}
}
// Buscar header Authorization
var auth_header: ?[]const u8 = null;
var it = ctx.request.head.iterateHeaders();
while (it.next()) |header| {
if (std.mem.eql(u8, header.name, "authorization")) {
auth_header = header.value;
break;
}
}
if (auth_header) |auth| {
// Espera formato: "Bearer <token>"
if (std.mem.startsWith(u8, auth, "Bearer ")) {
const token = auth[7..];
if (std.mem.eql(u8, token, cfg.token_valido)) {
try next(ctx);
return;
}
}
}
// Autenticação falhou
try ctx.request.respond(
"{\"erro\": \"Não autorizado\", \"codigo\": 401}",
.{
.status = .unauthorized,
.extra_headers = &.{
.{ .name = "content-type", .value = "application/json" },
.{ .name = "www-authenticate", .value = "Bearer" },
},
},
);
}
};
Impl.cfg = config;
return Impl.middleware;
}
Middleware 4: Rate Limiter
const RateLimiter = struct {
contadores: std.StringHashMap(ContadorIP),
limite_por_minuto: u32,
allocator: std.mem.Allocator,
const ContadorIP = struct {
contagem: u32,
janela_inicio: i64,
};
fn init(allocator: std.mem.Allocator, limite: u32) RateLimiter {
return .{
.contadores = std.StringHashMap(ContadorIP).init(allocator),
.limite_por_minuto = limite,
.allocator = allocator,
};
}
fn deinit(self: *RateLimiter) void {
self.contadores.deinit();
}
fn verificar(self: *RateLimiter, ip: []const u8) bool {
const agora = std.time.timestamp();
if (self.contadores.getPtr(ip)) |contador| {
// Resetar janela se passou 1 minuto
if (agora - contador.janela_inicio >= 60) {
contador.contagem = 1;
contador.janela_inicio = agora;
return true;
}
// Verificar limite
if (contador.contagem >= self.limite_por_minuto) {
return false;
}
contador.contagem += 1;
return true;
}
// Novo IP
self.contadores.put(ip, .{
.contagem = 1,
.janela_inicio = agora,
}) catch return false;
return true;
}
};
Combinando Middlewares: Aplicação Completa
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var rate_limiter = RateLimiter.init(allocator, 100);
defer rate_limiter.deinit();
const endereco = std.net.Address.parseIp("127.0.0.1", 8080) catch unreachable;
var servidor = try endereco.listen(.{ .reuse_address = true });
defer servidor.deinit();
std.debug.print("API com middlewares rodando em http://127.0.0.1:8080\n", .{});
std.debug.print("Middlewares ativos: logging, CORS, rate-limiting\n", .{});
while (true) {
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
var conexao = try servidor.accept();
defer conexao.stream.close();
var buf: [8192]u8 = undefined;
var http = std.http.Server.init(conexao, &buf);
var request = http.receiveHead() catch continue;
var ctx = Context.init(arena.allocator(), &request);
const inicio = std.time.milliTimestamp();
// Middleware 1: Rate limiting
if (!rate_limiter.verificar("127.0.0.1")) {
try request.respond(
"{\"erro\": \"Limite de requisições excedido\"}",
.{ .status = .too_many_requests },
);
continue;
}
// Middleware 2: CORS headers
try ctx.addHeader("Access-Control-Allow-Origin", "*");
if (request.head.method == .OPTIONS) {
try request.respond("", .{ .status = .no_content });
continue;
}
// Handler principal
const caminho = request.head.target;
if (std.mem.eql(u8, caminho, "/api/status")) {
try request.respond(
"{\"status\": \"online\", \"middlewares\": [\"logging\", \"cors\", \"rate-limit\"]}",
.{ .extra_headers = &.{
.{ .name = "content-type", .value = "application/json" },
} },
);
} else {
try request.respond(
"{\"erro\": \"Rota não encontrada\"}",
.{ .status = .not_found },
);
}
// Middleware logging (pós-processamento)
const duracao = std.time.milliTimestamp() - inicio;
std.debug.print("[LOG] {s} {s} -> {d}ms\n", .{
@tagName(request.head.method),
caminho,
duracao,
});
}
}
Padrão de Composição de Middlewares
A verdadeira potência vem da composição. Podemos criar “grupos” de middlewares para diferentes rotas:
const MiddlewareGroup = struct {
nome: []const u8,
middlewares: []const Middleware,
fn aplicar(self: MiddlewareGroup, ctx: *Context, handler: NextFn) !void {
// Executar cada middleware na ordem
for (self.middlewares) |mw| {
try mw(ctx, handler);
}
}
};
// Exemplo de uso:
// const grupo_api = MiddlewareGroup{
// .nome = "api",
// .middlewares = &.{ loggingMiddleware, corsMiddleware, authMiddleware },
// };
//
// const grupo_publico = MiddlewareGroup{
// .nome = "publico",
// .middlewares = &.{ loggingMiddleware, corsMiddleware },
// };
Próximos Passos
No artigo final da série, vamos preparar a aplicação para deploy em produção: configuração via variáveis de ambiente, graceful shutdown, logging estruturado e containerização com Docker.
Referências
- API REST (Artigo 3) — Artigo anterior
- Testes Avançados — Testando middlewares
- Design Patterns em Zig — Mais padrões de design