Zum Inhalt springen

Relay

Das Modul wippy/relay bietet eine WebSocket-Relay-Infrastruktur mit zweistufiger Hub-Architektur. Ein zentraler Hub verwaltet benutzerspezifische Hubs, die wiederum WebSocket-Client-Verbindungen verwalten und Nachrichten an Plugins routen.

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

Der zentrale Hub läuft als Dienst. Wenn ein WebSocket-Client eine Verbindung herstellt, sucht oder erstellt der zentrale Hub einen User-Hub für diesen Benutzer. Der User-Hub verwaltet die Lebensdauer des Clients und routet Nachrichten basierend auf Befehls-Präfixen an Plugins.

Modul zum Projekt hinzufügen:

Terminal-Fenster
wippy add wippy/relay
wippy install

Abhängigkeit mit erforderlichen Parametern deklarieren:

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
ParameterErforderlichStandardBeschreibung
application_hostjaProcess Host für Relay-Prozesse
env_storageneininternSpeicher für Umgebungsvariablen
user_security_scopejaSicherheits-Scope für User-Hubs
max_connections_per_usernein5WebSocket-Verbindungen pro Benutzer
queue_multipliernein100Nachrichten-Queue = Verbindungen × Multiplikator
user_hub_inactivity_timeoutnein7200sIdle-Zeit vor Hub-Bereinigung
  1. WebSocket-Client verbindet sich mit user_id in den Metadaten
  2. Zentraler Hub validiert die Verbindung und prüft die Limits pro Benutzer
  3. Zentraler Hub erstellt oder wiederverwendet einen User-Hub für den Benutzer
  4. User-Hub sendet eine welcome-Nachricht an den Client:
{
"user_id": "alice",
"client_count": 1,
"plugins": [
{ "prefix": "session_", "process_id": "...", "status": "running" },
{ "prefix": "ai_", "process_id": "...", "status": "pending" }
]
}

Plugin-status ist einer von "not_started" (registriert, nie gestartet), "pending" (Start in Arbeit), "running", "failed" oder "stopped".

Clients senden JSON-Nachrichten mit einem type-Feld. Der User-Hub vergleicht den Typ-Präfix mit registrierten Plugins und routet die Nachricht:

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

Der Präfix session_ passt zum Session-Plugin. Der Hub entfernt den Präfix und sendet die Nachricht mit dem reduzierten Typ als Topic an den Plugin-Prozess:

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

Plugins antworten, indem sie Nachrichten zurück an conn_pid senden.

Plugins sind process.lua-Einträge mit 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
FeldTypBeschreibung
meta.typestringMuss relay.plugin sein
meta.command_prefixstringNachrichten-Typ-Präfix, den dieses Plugin verarbeitet
meta.auto_startbooleanStarten, wenn der User-Hub initialisiert wird
meta.default_hoststringProcess Host überschreiben

Plugins werden vom User-Hub erzeugt. Beim Start empfängt das Plugin:

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

Das session_-Plugin empfängt Lebenszyklus-Nachrichten:

NachrichtWann
"resume"Erster Client verbindet sich mit dem User-Hub
"shutdown"Letzter Client trennt sich vom User-Hub

Plugins erhalten 1 automatischen Neustart bei einem Absturz. Nach einem zweiten Absturz wird das Plugin als "failed" markiert und nicht neu gestartet.

Plugins empfangen Nachrichten in ihrer Prozess-Inbox. Jede Nachricht hat ein Topic (der entfernte Befehlspräfix) und ein Payload, das die ursprünglichen Nachrichtendaten zusammen mit conn_pid enthält, um Antworten an den Client zurückzusenden.

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
-- erster Client verbunden
elseif topic == "shutdown" then
-- letzter Client getrennt
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 }

Der Relay sendet strukturierte Fehlermeldungen an Clients:

FehlercodeBeschreibung
max_connections_reachedBenutzer am Verbindungslimit
missing_user_idKeine user_id in den Verbindungsmetadaten
hub_creation_failedErzeugung des User-Hubs fehlgeschlagen
invalid_jsonFehler beim Dekodieren der Nachricht
unknown_commandNachricht ohne Type-Feld
plugin_not_foundKein Plugin passt zum Befehlspräfix
plugin_failedPlugin nicht verfügbar oder abgestürzt

User-Hubs werden bei Bedarf erstellt, sobald sich der erste Client für einen Benutzer verbindet. Der Hub wird mit dem Sicherheitsaktor und Scope des Benutzers erzeugt.

Der zentrale Hub prüft regelmäßig auf inaktive User-Hubs. Ein Hub ohne verbundene Clients für länger als user_hub_inactivity_timeout (Standard: 2 Stunden) wird mit einem 10-Sekunden-Cancel-Timeout sauber beendet.

Das GC-Prüfintervall wird automatisch abgeleitet: inactivity_timeout / 2.5.

Der zentrale Hub läuft unter seiner eigenen Sicherheitsgruppe (wippy.relay.security:root) mit vollem Zugriff. Jeder User-Hub wird mit dem konfigurierten user_security_scope erzeugt, wodurch Operationen auf Benutzerebene isoliert werden.

TopicRichtungBeschreibung
ws.joinClient → Central/User HubVerbindungsanfrage
ws.leaveClient → Central/User HubVerbindungstrennung
ws.messageClient → User HubWebSocket-Nachricht
ws.cancelCentral → User HubSauberes Herunterfahren
ws.controlCentral → User HubRouting-Steuerung
hub.activity_updateUser Hub → CentralAktualisierung der Client-Anzahl