nginx es uno de los servidores web más usados del mundo. Su modelo de workers asíncronos y no bloqueantes le permite manejar decenas de miles de conexiones simultáneas con una huella de memoria muy pequeña. La única pega es que su módulo de configuración no fue diseñado para lógica de negocio compleja. Ahí entra OpenResty: una distribución de nginx que integra LuaJIT dentro del proceso del servidor, permitiendo ejecutar código Lua con acceso completo a la petición HTTP y a las fases del ciclo de vida de nginx.
Qué es OpenResty
OpenResty no es un fork de nginx; es nginx compilado con un conjunto de módulos adicionales, el más importante de los cuales es ngx_lua (también llamado lua-nginx-module). Este módulo integra LuaJIT en el proceso de nginx y expone la API ngx.* para acceder a la petición, la respuesta, las variables de nginx y los subsistemas de conexión a servicios externos (Redis, MySQL, HTTP, DNS
). Cloudflare, Kong y muchas CDN lo usan en producción.
Bloques de Lua en la configuración de nginx
OpenResty añade directivas Lua a la sintaxis de nginx. Los bloques más habituales son:
init_by_lua_block: se ejecuta una vez al arrancar el master process. Útil para precargar módulos.access_by_lua_block: se ejecuta antes de pasar la petición al upstream. Ideal para autenticación y rate limiting.content_by_lua_block: genera la respuesta directamente desde Lua, sin upstream.header_filter_by_lua_block: modifica las cabeceras de la respuesta.log_by_lua_block: logging personalizado tras enviar la respuesta.
http {
# Precargar módulos compartidos entre workers
init_by_lua_block {
cjson = require("cjson")
redis = require("resty.redis")
}
server {
listen 80;
# Endpoint que responde directamente desde Lua
location /api/saludo {
content_by_lua_block {
local nombre = ngx.var.arg_nombre or "mundo"
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({
mensaje = "Hola, " .. nombre,
timestamp = ngx.time()
}))
}
}
# Autenticación con token antes de pasar al upstream
location /privado/ {
access_by_lua_block {
local token = ngx.req.get_headers()["Authorization"]
if not token or token ~= "Bearer secreto123" then
ngx.status = 401
ngx.say("No autorizado")
ngx.exit(401)
end
}
proxy_pass http://backend;
}
}
}
La API ngx.*
La API que expone ngx_lua cubre prácticamente todo lo que se puede hacer con una petición HTTP:
-- Leer parámetros de la petición
local args = ngx.req.get_uri_args() -- query string
local post = ngx.req.get_post_args() -- body form-urlencoded
local hdrs = ngx.req.get_headers() -- cabeceras
local metodo = ngx.req.get_method() -- GET, POST...
local uri = ngx.var.uri -- /ruta/actual
local ip = ngx.var.remote_addr -- IP del cliente
-- Leer el body raw (hay que llamar read_body antes)
ngx.req.read_body()
local body = ngx.req.get_body_data()
-- Responder
ngx.status = 200
ngx.header["X-Custom"] = "valor"
ngx.header["Content-Type"] = "text/plain"
ngx.say("Primera línea")
ngx.print("Sin salto de línea")
-- Redirigir
ngx.redirect("https://ejemplo.com", 302)
-- Terminar sin respuesta (útil en access_by_lua)
ngx.exit(ngx.HTTP_FORBIDDEN)
-- Logging
ngx.log(ngx.ERR, "Error inesperado: " .. tostring(err))
ngx.log(ngx.INFO, "Petición procesada en " .. ngx.var.request_time .. "s")
Conexión a Redis: cosocketAPI
La gran ventaja de OpenResty sobre un upstream tradicional es la cosocket API: operaciones de red no bloqueantes que usan corrutinas de Lua internamente. El código parece síncrono pero no bloquea el worker de nginx:
-- Rate limiting con Redis (en access_by_lua_block)
local redis = require("resty.redis")
local r = redis:new()
r:set_timeout(100) -- 100ms de timeout
local ok, err = r:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis no disponible: " .. err)
return -- dejar pasar si Redis está caído (fail open)
end
local ip = ngx.var.remote_addr
local key = "rl:" .. ip
local count, err = r:incr(key)
if count == 1 then
r:expire(key, 60) -- ventana de 60 segundos
end
r:close() -- devolver la conexión al pool
if count > 100 then
ngx.status = 429
ngx.header["Retry-After"] = "60"
ngx.say("Demasiadas peticiones")
ngx.exit(429)
end
Kong API Gateway
Kong es el API gateway open source más popular, y está construido enteramente sobre OpenResty. Cada plugin de Kong es un módulo Lua que implementa alguna de las fases del ciclo de vida (access, header_filter, log
). Escribir un plugin personalizado es tan sencillo como crear una tabla Lua con los métodos de las fases que nos interesan:
-- Plugin Kong mínimo: añade una cabecera de respuesta
local MiPlugin = {}
MiPlugin.PRIORITY = 1000
MiPlugin.VERSION = "1.0.0"
function MiPlugin:header_filter(config)
kong.response.set_header("X-Mi-Plugin", config.valor_cabecera)
end
return MiPlugin
OpenResty es una de las aplicaciones más interesantes de Lua en producción. Combina la eficiencia de nginx con la flexibilidad de un lenguaje de scripting sin sacrificar rendimiento, gracias a LuaJIT. Si quieres profundizar en las capacidades de LuaJIT más allá del contexto web, el artículo sobre LuaJIT y la FFI explica cómo alcanzar rendimiento cercano a C desde Lua.
Imagen: Pexels / Muhammed Ensar
