Ir al contenido

Relay

El módulo wippy/relay proporciona infraestructura de relay WebSocket con una arquitectura de hub de dos niveles. Un hub central gestiona hubs por usuario, los cuales a su vez gestionan conexiones de clientes WebSocket y enrutan mensajes a plugins.

Central Hub
├── User Hub (alice)
│ ├── Plugin: session_
│ ├── Plugin: ai_
│ ├── WebSocket Client 1
│ └── WebSocket Client 2
├── User Hub (bob)
│ ├── Plugin: session_
│ └── WebSocket Client 1
└── ...

El hub central se ejecuta como un servicio. Cuando un cliente WebSocket se conecta, el hub central busca o crea un hub de usuario para ese usuario. El hub de usuario gestiona la vida útil del cliente y enruta mensajes a los plugins basándose en prefijos de comando.

Agregue el módulo a su proyecto:

Ventana de terminal
wippy add wippy/relay
wippy install

Declare la dependencia con los parámetros requeridos:

version: "1.0"
namespace: app
entries:
- name: os_env
kind: env.storage.os
- name: processes
kind: process.host
lifecycle:
auto_start: true
- name: dep.relay
kind: ns.dependency
component: wippy/relay
version: "*"
parameters:
- name: application_host
value: app:processes
- name: env_storage
value: app:os_env
- name: user_security_scope
value: app.security:user_scope
ParámetroRequeridoPredeterminadoDescripción
application_hostHost de procesos para procesos del relay
env_storagenointernoAlmacenamiento de variables de entorno
user_security_scopeÁmbito de seguridad para hubs de usuario
max_connections_per_userno5Conexiones WebSocket por usuario
queue_multiplierno100Cola de mensajes = conexiones × multiplicador
user_hub_inactivity_timeoutno7200sTiempo de inactividad antes de la limpieza del hub
  1. El cliente WebSocket se conecta con user_id en los metadatos
  2. El hub central valida la conexión y verifica los límites por usuario
  3. El hub central crea o reutiliza un hub de usuario para el usuario
  4. El hub de usuario envía un mensaje welcome al cliente:
{
"user_id": "alice",
"client_count": 1,
"plugins": [
{ "prefix": "session_", "process_id": "...", "status": "running" },
{ "prefix": "ai_", "process_id": "...", "status": "pending" }
]
}

El status del plugin es uno de "not_started" (registrado, nunca iniciado), "pending" (inicio en curso), "running", "failed" o "stopped".

Los clientes envían mensajes JSON con un campo type. El hub de usuario compara el prefijo del tipo con los plugins registrados y enruta el mensaje:

{ "type": "session_get_state", "data": { "key": "value" } }

El prefijo session_ coincide con el plugin de sesión. El hub elimina el prefijo y envía el mensaje al proceso del plugin con el tipo despojado como tópico:

-- process topic: "get_state"
-- payload:
{
conn_pid = client_pid,
type = "session_get_state", -- original full type preserved
data = { key = "value" },
request_id = "...",
session_id = "..."
}

Los plugins responden enviando mensajes de vuelta a conn_pid.

Los plugins son entradas process.lua con meta.type: relay.plugin:

entries:
- name: session_plugin
kind: process.lua
meta:
type: relay.plugin
command_prefix: session_
auto_start: true
source: file://session_plugin.lua
modules: [json, time, logger]
method: run
CampoTipoDescripción
meta.typestringDebe ser relay.plugin
meta.command_prefixstringPrefijo del tipo de mensaje que maneja este plugin
meta.auto_startbooleanIniciar cuando se inicialice el hub de usuario
meta.default_hoststringAnular el host de procesos

Los plugins son generados por el hub de usuario. Al arrancar, el plugin recibe:

function run(args)
local user_id = args.user_id
local user_metadata = args.user_metadata
local user_hub_pid = args.user_hub_pid
local config = args.config
end

El plugin session_ recibe mensajes de ciclo de vida:

MensajeCuándo
"resume"Primer cliente se conecta al hub de usuario
"shutdown"Último cliente se desconecta del hub de usuario

Los plugins reciben 1 reinicio automático en caso de fallo. Después de un segundo fallo, el plugin se marca como "failed" y no se reinicia.

Los plugins reciben mensajes en su buzón de proceso. Cada mensaje tiene un tópico (el prefijo de comando despojado) y una carga útil que contiene los datos originales del mensaje junto con conn_pid para enviar respuestas de vuelta al cliente.

local json = require("json")
local function handle_message(topic, payload)
if topic == "get_state" then
process.send(payload.conn_pid, "ws.message", json.encode({
type = "session_state",
data = { status = "active" }
}))
end
end
local function run(args)
local user_id = args.user_id
local inbox = process.inbox()
local events = process.events()
while true do
local result = channel.select({
inbox:case_receive(),
events:case_receive()
})
if not result.ok then break end
if result.channel == inbox then
local msg = result.value
local topic = msg:topic()
local payload = msg:payload():data()
if topic == "resume" then
-- first client connected
elseif topic == "shutdown" then
-- last client disconnected
else
handle_message(topic, payload)
end
elseif result.channel == events then
local event = result.value
if event.kind == process.event.CANCEL then
break
end
end
end
end
return { run = run }

El relay envía mensajes de error estructurados a los clientes:

Código de ErrorDescripción
max_connections_reachedUsuario en el límite de conexiones
missing_user_idSin user_id en los metadatos de conexión
hub_creation_failedFalló la generación del hub de usuario
invalid_jsonError de decodificación del mensaje
unknown_commandMensaje sin campo type
plugin_not_foundNingún plugin coincide con el prefijo del comando
plugin_failedPlugin no disponible o caído

Los hubs de usuario se crean bajo demanda cuando el primer cliente de un usuario se conecta. El hub se genera con el actor de seguridad y el ámbito del usuario.

El hub central verifica periódicamente hubs de usuario inactivos. Un hub sin clientes conectados durante más de user_hub_inactivity_timeout (predeterminado 2 horas) se termina de forma elegante con un timeout de cancelación de 10 segundos.

El intervalo de verificación de GC se deriva automáticamente: inactivity_timeout / 2.5.

El hub central se ejecuta bajo su propio grupo de seguridad (wippy.relay.security:root) con acceso completo. Cada hub de usuario se genera con el user_security_scope configurado, aislando las operaciones a nivel de usuario.

TópicoDirecciónDescripción
ws.joinCliente → Hub Central/UsuarioSolicitud de conexión
ws.leaveCliente → Hub Central/UsuarioDesconexión
ws.messageCliente → Hub de UsuarioMensaje WebSocket
ws.cancelCentral → Hub de UsuarioApagado elegante
ws.controlCentral → Hub de UsuarioControl de enrutamiento
hub.activity_updateHub de Usuario → CentralActualización del conteo de clientes