Pular para o conteúdo

Relay WebSocket

O middleware de relay WebSocket atualiza conexões HTTP para WebSocket e retransmite mensagens para um processo alvo.

  1. Handler HTTP define header X-WS-Relay com PID do processo alvo
  2. Middleware atualiza conexão para WebSocket
  3. Relay anexa ao processo alvo e o monitora
  4. Mensagens fluem bidirecionalmente entre cliente e processo
A conexão WebSocket está vinculada ao processo alvo. Se o processo sair, a conexão fecha automaticamente.

Conexões WebSocket são processos completos com seu próprio PID. Elas se integram com o sistema de processos:

  • Endereçável - Qualquer processo pode enviar mensagens para um PID WebSocket
  • Monitorável - Processos podem monitorar conexões WebSocket para eventos de saída
  • Linkável - Conexões WebSocket podem ser vinculadas a outros processos
  • Eventos EXIT - Quando a conexão fecha, monitores recebem notificações de saída
-- Monitora uma conexão WebSocket de outro processo
process.monitor(websocket_pid)
-- Envia mensagem para cliente WebSocket de qualquer processo.
-- O relay encapsula como JSON {topic, data}; o nome do tópico é arbitrário.
process.send(websocket_pid, "update", "hello")
O relay monitora o processo alvo. Se o alvo sair, a conexão WebSocket fecha automaticamente e o cliente recebe um frame de fechamento.

Conexões podem ser transferidas para um processo diferente enviando uma mensagem de controle:

process.send(websocket_pid, "ws.control", {
target_pid = new_process_pid,
message_topic = "ws.message"
})

Adicione como middleware pós-match em um roteador:

- name: ws_router
kind: http.router
meta:
server: gateway
prefix: /ws
post_middleware:
- websocket_relay
post_options:
wsrelay.allowed.origins: "https://app.example.com"
OpçãoDescrição
wsrelay.allowed.originsOrigens permitidas separadas por vírgula
Se nenhuma origem configurada, apenas requisições same-origin são permitidas.

O handler HTTP cria um processo e configura o relay:

local http = require("http")
local json = require("json")
local function handler()
local req = http.request()
local res = http.response()
-- Cria processo handler
local pid = process.spawn("app.ws:handler", "app:processes")
-- Configura relay
res:header("X-WS-Relay", json.encode({
target_pid = tostring(pid),
message_topic = "ws.message",
heartbeat_interval = "30s",
metadata = {
user_id = req:query("user_id")
}
}))
end
CampoTipoPadrãoDescrição
target_pidstringobrigatórioPID do processo para receber mensagens
message_topicstringws.messageTópico para mensagens do cliente
heartbeat_intervalduration-Frequência de heartbeat (ex: 30s)
metadataobject-Anexado a todas as mensagens

O relay envia estas mensagens para o processo alvo:

TópicoQuandoPayload
ws.joinCliente conectaJSON {client_pid, metadata}
ws.message (ou seu message_topic)Cliente envia mensagemPayload bruto do cliente (frame de texto -> string, frame binário -> bytes); o PID de origem do pacote do relay é o PID do cliente
ws.heartbeatPeriódico (se configurado)JSON {client_pid, uptime, message_count, metadata}
ws.leaveCliente desconectaJSON {client_pid, metadata}
local json = require("json")
local function handler()
local inbox = process.inbox()
while true do
local msg, ok = inbox:receive()
if not ok then break end
local topic = msg:topic()
local from = msg:from() -- PID da conexão do cliente
if topic == "ws.join" then
-- Cliente conectou -- payload é {client_pid, metadata}
local data = msg:payload():data()
local client_pid = data.client_pid
elseif topic == "ws.message" then
-- Mensagem bruta do cliente; from() é o PID do cliente
local body = msg:payload():data() -- string ou bytes
handle_message(from, json.decode(body))
elseif topic == "ws.leave" then
-- Cliente desconectou -- payload é {client_pid, metadata}
cleanup(from)
end
end
end

Envia mensagens de volta usando o PID do cliente. Qualquer tópico que você escolher é encapsulado como JSON {topic, data} e encaminhado para o WebSocket. O tipo de frame é decidido pelo formato do payload: strings se tornam frames de texto, bytes se tornam frames binários (codificados em base64 dentro do envoltório JSON).

-- Envia uma mensagem estruturada (qualquer nome de tópico)
process.send(client_pid, "update", json.encode({event = "update", value = 42}))
-- Envia binário
process.send(client_pid, "data", binary_content)
-- Fecha conexão (payload é a string do motivo de fechamento)
process.send(client_pid, "ws.close", "Sessão encerrada")

Os tópicos reservados de servidor -> cliente são ws.control (reconfiguração do relay) e ws.close (fechar a conexão).

Rastreie PIDs de clientes para broadcast para múltiplos clientes:

local clients = {}
-- No join
clients[client_pid] = true
-- No leave
clients[client_pid] = nil
-- Broadcast
local function broadcast(message)
local data = json.encode(message)
for pid, _ in pairs(clients) do
process.send(pid, "broadcast", data)
end
end
Para cenários complexos de múltiplas salas, crie um processo handler separado por sala ou use um processo gerenciador central que rastreia membros de salas.