Criação de servidores HTTP simples, leves e customizáveis
Warning
Projeto experimental. APIs podem mudar sem aviso.
- Sobre
- Dependências
- Como Começar
- CORS
- Pipeline
- Cache
- Exemplo Completo
- Exemplos
- Roadmap
- Licença
- Contribuindo
Pudim Server é uma lib Lua focada em fornecer APIs simples para criação de servidores web com um sistema de rotas. Feito para aceitar requisições HTTP, interpretá-las, despachar para um handler e devolver uma resposta.
Essa lib está sendo projetada com base no que Davi entende de servidor. Ele pretende expandir a lib para algo mais complexo com o tempo, mas mantendo em foco a simplicidade, leveza e customização.
- Lua >= 5.4
- LuaSocket
- lua-cjson
- loglua
# Instalação local
luarocks install PudimServer --local
# Instalação global
sudo luarocks install PudimServerlocal PudimServer = require("PudimServer")
local server = PudimServer:Create{
ServiceName = "Meu Serviço",
Port = 8080,
Address = "localhost"
}Todos os campos são opcionais. Padrões: ServiceName = "Pudim Server", Port = 8080, Address = "localhost".
Handlers de rota recebem (req, res) e devem retornar res:response(status, body, headers).
-- Página HTML
server:Routes("/", function(req, res)
if req.method == "GET" then
return res:response(200, "<h1>Olá!</h1>", {["Content-Type"] = "text/html"})
end
return res:response(405, {error = "Método não permitido"})
end)
-- API JSON (tabelas são auto-convertidas para JSON)
server:Routes("/api/users", function(req, res)
if req.method == "GET" then
return res:response(200, {
{id = 1, name = "João"},
{id = 2, name = "Maria"}
})
end
if req.method == "POST" then
return res:response(201, {msg = "Usuário criado!", data = req.body})
end
return res:response(405, {error = "Método não permitido"})
end)server:Run()| Propriedade | Tipo | Descrição |
|---|---|---|
method |
string | Método HTTP (GET, POST, PUT, DELETE, etc) |
path |
string | Caminho da requisição (ex: "/api/users") |
version |
string | Versão do HTTP (ex: "HTTP/1.1") |
headers |
table | Headers da requisição (chaves em lowercase) |
body |
string | Corpo da requisição |
query |
table<string, string|string[]>? | Query string parseada |
params |
table<string, string>? | Parâmetros de rota dinâmica |
res:response(status, body, headers):
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
status |
number | ✅ | Código de status HTTP (200, 404, 500, etc) |
body |
string/table | ✅ | Corpo da resposta. Tabelas são auto-convertidas para JSON |
headers |
table | ❌ | Headers customizados da resposta |
Habilite CORS com EnableCors(). Requisições preflight OPTIONS são tratadas automaticamente.
-- Permitir todas as origens (padrão)
server:EnableCors()
-- Configuração restrita
server:EnableCors{
AllowOrigins = {"https://meuapp.com", "https://admin.meuapp.com"},
AllowMethods = {"GET", "POST"},
AllowHeaders = "Content-Type, Authorization",
AllowCredentials = true,
ExposeHeaders = {"X-Total-Count"},
MaxAge = 3600
}| Opção | Tipo | Padrão |
|---|---|---|
AllowOrigins |
string | string[] | "*" |
AllowMethods |
string | string[] | "GET, POST, PUT, DELETE, PATCH, OPTIONS" |
AllowHeaders |
string | string[] | "Content-Type, Authorization" |
ExposeHeaders |
string | string[] | "" |
AllowCredentials |
boolean | false |
MaxAge |
number | 86400 |
O sistema de pipeline permite adicionar handlers de request/response que rodam antes dos handlers de rota. Cada handler recebe (req, res, next) — chame next() para continuar ou retorne diretamente para interromper.
-- Logger de requisições
server:UseHandler{
name = "logger",
Handler = function(req, res, next)
print(req.method .. " " .. req.path)
return next()
end
}
-- Autenticação (interrompe em caso de falha)
server:UseHandler{
name = "auth",
Handler = function(req, res, next)
if req.path:find("^/api/protected") and not req.headers["authorization"] then
return res:response(401, {error = "Não autorizado"})
end
return next()
end
}
-- Wrapper de resposta
server:UseHandler{
name = "timer",
Handler = function(req, res, next)
local start = os.clock()
local response = next()
print(("Requisição levou %.3fs"):format(os.clock() - start))
return response
end
}
-- Remover um handler
server:RemoveHandler("logger")Handlers rodam na ordem em que são registrados. O handler de rota roda por último.
Cache de respostas em memória com TTL e eviction automática. Apenas requisições GET são cacheadas.
local Cache = require("PudimServer.cache")
-- Criar instância do cache
local cache = Cache.new{
MaxSize = 200, -- máximo de entradas (padrão: 100)
DefaultTTL = 120 -- segundos (padrão: 60)
}
-- Adicionar ao pipeline (forma mais fácil)
server:UseHandler(Cache.createPipelineHandler(cache))
-- Ou usar manualmente em uma rota
server:Routes("/api/data", function(req, res)
local cached = cache:get("minha-chave")
if cached then return cached end
local response = res:response(200, {data = "computação pesada"})
cache:set("minha-chave", response, 30) -- TTL customizado
return response
end)
-- Invalidar entradas
cache:invalidate("minha-chave")
cache:clear()| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
MaxSize |
number | 100 |
Máximo de entradas antes da eviction |
DefaultTTL |
number | 60 |
TTL em segundos |
local PudimServer = require("PudimServer")
local Cache = require("PudimServer.cache")
local server = PudimServer:Create{
ServiceName = "Minha API",
Port = 3000,
Address = "localhost"
}
-- Habilitar CORS
server:EnableCors()
-- Logger de requisições
server:UseHandler{
name = "logger",
Handler = function(req, res, next)
print(("[%s] %s %s"):format(os.date("%H:%M:%S"), req.method, req.path))
return next()
end
}
-- Cache (apenas GET, TTL de 30s)
local cache = Cache.new{ DefaultTTL = 30 }
server:UseHandler(Cache.createPipelineHandler(cache))
-- Rotas
server:Routes("/", function(req, res)
if req.method == "GET" then
return res:response(200, "<h1>Bem-vindo!</h1>", {["Content-Type"] = "text/html"})
end
return res:response(405, {error = "Método não permitido"})
end)
server:Routes("/api/data", function(req, res)
if req.method == "GET" then
return res:response(200, {
status = "ok",
timestamp = os.time(),
message = "API funcionando!"
})
end
return res:response(405, {error = "Método não permitido"})
end)
print("Servidor rodando em http://localhost:3000")
server:Run()Exemplos funcionais estão disponíveis no diretório examples/:
| Arquivo | Descrição |
|---|---|
json_response_example.lua |
Auto encoding de tabelas para JSON |
cors_example.lua |
CORS com configuração padrão |
cors_restricted_example.lua |
CORS com origens restritas e credentials |
pipeline_example.lua |
Logger, auth e header customizado no pipeline |
cache_example.lua |
Cache via pipeline e cache manual |
query_dynamic_example.lua |
Parsing de query string e parâmetros de rota dinâmica |
https_concurrency_example.lua |
HTTPS nativo (luasec) com concorrência cooperativa |
hot_reload_example.lua |
Hot reload em desenvolvimento sem file watcher |
complete_featured_example.lua |
Exemplo completo com CORS, pipeline, cache, query, rotas dinâmicas, hot reload e HTTPS opcional |
Execute qualquer exemplo com:
lua ./examples/json_response_example.luaNote
Este projeto está em fase experimental. Confira abaixo o que já foi implementado e o que está planejado.
- Sistema de rotas básico
- Parsing de requisições HTTP (method, path, headers, body, query, params)
- Respostas JSON automáticas (tables convertidas para JSON)
- Sistema de logs configurável
- CORS helpers (Cross-Origin Resource Sharing)
- Pipeline de request/response (middleware no nível HTTP)
- Cache de respostas em memória com TTL
- Rotas dinâmicas com parâmetros (
/users/:id,/posts/:slug) - Parsing automático de query strings (
?page=1&limit=10) - Mais códigos de status HTTP mapeados
- Multi-threading / concorrência (corrotinas cooperativas)
- Suporte HTTPS nativo (luasec)
- Hot reload em desenvolvimento (sem file watcher)
- Suporte a uploads de arquivos (multipart/form-data)
- Servir arquivos estáticos (HTML, CSS, JS, imagens)
- Rate limiting básico
- Suporte a WebSockets
Habilite o hot reload invalidando módulos Lua selecionados de package.loaded a cada requisição.
local server = PudimServer:Create{
Port = 8080,
HotReload = {
Enabled = true,
Modules = {"examples.hot_reload_message"},
Prefixes = {"app."}
}
}Use Modules para nomes exatos de módulo e Prefixes para invalidar grupos. Isso é apenas para desenvolvimento.
Este projeto está sob a licença MIT. Veja o arquivo LICENSE para mais detalhes.
Contribuições são bem-vindas! Veja o guia completo em CONTRIBUTING_PT-BR.MD.
Feito com ❤️ por Davi