Relay
Das Modul wippy/relay leitet WebSocket-Verbindungen über einen zentralen Hub und
benutzerspezifische Hubs. Die Benutzer-Hubs verwalten Clientverbindungen und verteilen
Nachrichten an Plugins anhand von Präfixen.
Diese Seite ist ein Teilrezept zur Integration und eine Protokollreferenz, keine
eigenständige WebSocket-Anwendung. Die Konfigurations- und Pluginblöcke setzen ein
bestehendes Wippy-Projekt, einen echten Sicherheits-Scope am konfigurierten
user_security_scope und einen mit dem Relay verbundenen HTTP-WebSocket-Endpunkt
gemäß WebSocket-Relay voraus. Protokoll-Payloads und
Lifecycle-Blöcke sind Referenzformen.
Architektur
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 Service. Wenn sich ein WebSocket-Client verbindet, sucht oder erstellt er einen Hub für den Benutzer. Dieser Benutzer-Hub verwaltet den Verbindungslebenszyklus und leitet Nachrichten anhand des Befehlspräfixes weiter.
Einrichtung
Fügen Sie das Modul zum Projekt hinzu:
wippy add wippy/relay
wippy install
Deklarieren Sie die Abhängigkeit mit den erforderlichen Parametern:
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
Konfigurationsparameter
| Parameter | Erforderlich | Standard | Beschreibung |
|---|---|---|---|
application_host |
ja | — | Process Host für Relay-Prozesse |
env_storage |
nein | intern | Speicher für Umgebungsvariablen |
user_security_scope |
ja | — | Sicherheits-Scope für User-Hubs |
max_connections_per_user |
nein | 5 |
WebSocket-Verbindungen pro Benutzer |
queue_multiplier |
nein | 100 |
Nachrichten-Queue = Verbindungen × Multiplikator |
user_hub_inactivity_timeout |
nein | 7200s |
Idle-Zeit vor Hub-Bereinigung |
Client-Verbindungsablauf
- WebSocket-Client verbindet sich mit
user_idin den Metadaten - Zentraler Hub validiert die Verbindung und prüft die Limits pro Benutzer
- Zentraler Hub erstellt oder wiederverwendet einen User-Hub für den Benutzer
- 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".
Nachrichten-Routing
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
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
Plugin-Metadaten
| Feld | Typ | Beschreibung |
|---|---|---|
meta.type |
string | Muss relay.plugin sein |
meta.command_prefix |
string | Nachrichten-Typ-Präfix, den dieses Plugin verarbeitet |
meta.auto_start |
boolean | Starten, wenn der User-Hub initialisiert wird |
meta.default_host |
string | Process Host überschreiben |
Plugin-Lebenszyklus
Der Benutzer-Hub startet jedes Plugin mit den folgenden Argumenten:
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:
| Nachricht | Wann |
|---|---|
"resume" |
Erster Client verbindet sich mit dem User-Hub |
"shutdown" |
Letzter Client trennt sich vom User-Hub |
Plugins erhalten nach einem Absturz einen automatischen Neustart. Nach dem zweiten
Absturz wird das Plugin als "failed" markiert und nicht erneut gestartet.
Plugin-Implementierung
Plugins empfangen Nachrichten über ihre Prozess-Inbox. Jede Nachricht besitzt ein aus
dem Befehlstyp abgeleitetes Topic und einen Payload mit den ursprünglichen Daten sowie
conn_pid für Antworten.
local json = require("json")
local function handle_message(topic, payload)
if topic == "get_state" then
if not payload.conn_pid then
return nil, "Relay message is missing conn_pid"
end
local encoded, encode_err = json.encode({
type = "session_state",
data = { status = "active" }
})
if encode_err then
return nil, encode_err
end
local sent, send_err = process.send(payload.conn_pid, "ws.message", encoded)
if not sent then
return nil, send_err or "Relay response was not sent"
end
end
return true
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
local ok, err = handle_message(topic, payload)
if not ok then
error("Failed to handle relay message: " .. tostring(err))
end
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 }
Fehlerbehandlung
Das Relay meldet Clientfehler mit den folgenden Codes:
| Fehlercode | Beschreibung |
|---|---|
max_connections_reached |
Benutzer am Verbindungslimit |
missing_user_id |
Keine user_id in den Verbindungsmetadaten |
hub_creation_failed |
Erzeugung des User-Hubs fehlgeschlagen |
invalid_json |
Fehler beim Dekodieren der Nachricht |
unknown_command |
Nachricht ohne Type-Feld |
plugin_not_found |
Kein Plugin passt zum Befehlspräfix |
plugin_failed |
Plugin nicht verfügbar oder abgestürzt |
Hub-Lebenszyklus
User-Hub-Erstellung
Die erste Clientverbindung eines Benutzers erstellt dessen Hub. Er läuft mit dem Sicherheitsakteur und Scope des Benutzers.
Garbage Collection
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.
Sicherheit
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.
Interne Topics
| Topic | Richtung | Beschreibung |
|---|---|---|
ws.join |
Client → Central/User Hub | Verbindungsanfrage |
ws.leave |
Client → Central/User Hub | Verbindungstrennung |
ws.message |
Client → User Hub | WebSocket-Nachricht |
ws.cancel |
Central → User Hub | Sauberes Herunterfahren |
ws.control |
Central → Client | Leitet die Ziel-PID der Clientverbindung auf den Benutzer-Hub um |
hub.activity_update |
User Hub → Central | Aktualisierung der Client-Anzahl |
Siehe auch
- WebSocket-Relay — Konfiguration des HTTP-WebSocket-Endpunkts
- Prozessmodell — Prozesslebenszyklus und Messaging
- Sicherheit — Sicherheitsakteure und Scopes
- Framework-Übersicht — Framework-Module installieren und importieren