Pular para o conteúdo

Relay

O módulo wippy/relay fornece infraestrutura de relay WebSocket com uma arquitetura de hub de duas camadas. Um hub central gerencia hubs por usuário, que por sua vez gerenciam conexões de clientes WebSocket e roteiam mensagens para plugins.

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

O hub central executa como um serviço. Quando um cliente WebSocket se conecta, o hub central busca ou cria um hub de usuário para aquele usuário. O hub de usuário gerencia o tempo de vida do cliente e roteia mensagens para plugins com base em prefixos de comando.

Adicione o módulo ao seu projeto:

Terminal window
wippy add wippy/relay
wippy install

Declare a dependência com os parâmetros obrigatórios:

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âmetroObrigatórioPadrãoDescrição
application_hostsimHost de processos para os processos do relay
env_storagenãointernoArmazenamento de variáveis de ambiente
user_security_scopesimEscopo de segurança para hubs de usuário
max_connections_per_usernão5Conexões WebSocket por usuário
queue_multipliernão100Fila de mensagens = conexões × multiplicador
user_hub_inactivity_timeoutnão7200sTempo ocioso antes da limpeza do hub
  1. O cliente WebSocket conecta com user_id nos metadados
  2. O hub central valida a conexão e verifica os limites por usuário
  3. O hub central cria ou reutiliza um hub de usuário para o usuário
  4. O hub de usuário envia uma mensagem welcome ao cliente:
{
"user_id": "alice",
"client_count": 1,
"plugins": [
{ "prefix": "session_", "process_id": "...", "status": "running" },
{ "prefix": "ai_", "process_id": "...", "status": "pending" }
]
}

O status do plugin e um dos seguintes: "not_started" (registrado, nunca iniciado), "pending" (inicializacao em andamento), "running", "failed" ou "stopped".

Clientes enviam mensagens JSON com um campo type. O hub de usuário compara o prefixo do tipo com os plugins registrados e roteia a mensagem:

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

O prefixo session_ corresponde ao plugin de sessão. O hub remove o prefixo e envia a mensagem para o processo do plugin com o tipo sem prefixo como o tópico:

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

Plugins respondem enviando mensagens de volta para conn_pid.

Plugins são entradas process.lua com 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
CampoTipoDescrição
meta.typestringDeve ser relay.plugin
meta.command_prefixstringPrefixo de tipo de mensagem que este plugin trata
meta.auto_startbooleanIniciar quando o hub de usuário inicializar
meta.default_hoststringSobrescreve o host de processos

Plugins são gerados pelo hub de usuário. Na inicialização, o plugin recebe:

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

O plugin session_ recebe mensagens de ciclo de vida:

MensagemQuando
"resume"Primeiro cliente se conecta ao hub de usuário
"shutdown"Último cliente se desconecta do hub de usuário

Plugins recebem 1 reinício automático em caso de crash. Após um segundo crash, o plugin é marcado como "failed" e não é reiniciado.

Plugins recebem mensagens em sua caixa de entrada de processo. Cada mensagem tem um tópico (o prefixo de comando removido) e um payload contendo os dados originais da mensagem junto com conn_pid para enviar respostas de volta ao 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
-- primeiro cliente conectado
elseif topic == "shutdown" then
-- último cliente desconectado
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 }

O relay envia mensagens de erro estruturadas aos clientes:

Código de ErroDescrição
max_connections_reachedUsuário no limite de conexões
missing_user_idSem user_id nos metadados da conexão
hub_creation_failedFalha ao gerar o hub de usuário
invalid_jsonErro de decodificação da mensagem
unknown_commandMensagem sem campo type
plugin_not_foundNenhum plugin corresponde ao prefixo de comando
plugin_failedPlugin indisponível ou com crash

Hubs de usuário são criados sob demanda quando o primeiro cliente para um usuário se conecta. O hub é gerado com o ator e escopo de segurança do usuário.

O hub central verifica periodicamente hubs de usuário inativos. Um hub sem clientes conectados por mais tempo que user_hub_inactivity_timeout (padrão 2 horas) é encerrado graciosamente com um timeout de cancelamento de 10 segundos.

O intervalo de verificação do GC é derivado automaticamente: inactivity_timeout / 2.5.

O hub central executa sob seu próprio grupo de segurança (wippy.relay.security:root) com acesso total. Cada hub de usuário é gerado com o user_security_scope configurado, isolando operações a nível de usuário.

TópicoDireçãoDescrição
ws.joinCliente → Central/User HubSolicitação de conexão
ws.leaveCliente → Central/User HubDesconexão
ws.messageCliente → User HubMensagem WebSocket
ws.cancelCentral → User HubEncerramento gracioso
ws.controlCentral → User HubControle de roteamento
hub.activity_updateUser Hub → CentralAtualização de contagem de clientes