Skip to content
mobius1qwe edited this page Nov 11, 2026 · 1 revision

Home > WebSocket 🚧

WebSocket

Important

Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.

Vale para a 2.0 · Delphi XE em diante e Lazarus/FPC 3.2 em diante · Nível: básico

Em uma frase: uma rota WebSocket mantém a conexão aberta depois do handshake, e servidor e cliente trocam mensagens (texto ou binário) nos dois sentidos, quando quiserem; o RAL traz as rotas (TRALWebSocketModule), o hub de cada rota (todas as conexões, usuários e grupos) e o cliente (TRALWebSocketClient).

Você vai precisar: o pacote RALModuleWebSocketPackage (paleta RAL - Modules; o cliente fica na paleta RAL - Client), que pede o RALNetPackage, e um motor que entregue a conexão (tabela abaixo). Para chamadas e eventos prontos em JSON, veja Hubs.

Quando usar

  • O servidor precisa avisar o cliente sem que ele pergunte (chat, painel ao vivo, notificações, progresso de uma tarefa).
  • As mensagens vão e voltam o tempo todo e o custo de um pedido HTTP por mensagem pesa.
  • Só o servidor fala? Resposta em fluxo (SSE) é mais simples e passa por qualquer proxy.

Exemplo mínimo

uses RALIndyServer, RALWebSocket, RALWebSocketServer;

procedure TForm1.FormCreate(Sender: TObject);
var
  vRoute: TRALWebSocketRoute;
begin
  RALWebSocketModule1.Server := RALIndyServer1;   // ou crie em código
  RALWebSocketModule1.Domain := '/ws';
  vRoute := RALWebSocketModule1.CreateWebSocketRoute('/chat', OnChatMessage);
  vRoute.OnOpen := OnChatOpen;
  vRoute.Deflate := True;           // permessage-deflate, se o navegador oferecer
  RALIndyServer1.Port := 8090;
  RALIndyServer1.Active := True;
end;

procedure TForm1.OnChatOpen(AConnection: TRALWebSocketConnection);
begin
  // a sala vem da query: ws://host:8090/ws/chat?sala=geral
  AConnection.Hub.Join(AConnection, AConnection.QueryParam('sala'));
end;

procedure TForm1.OnChatMessage(AConnection: TRALWebSocketConnection;
  AMessage: TRALWebSocketMessage);
begin
  // repassa para quem está na mesma sala
  AConnection.Hub.SendToGroup(AConnection.QueryParam('sala'), AMessage.Text);
end;

No navegador:

const ws = new WebSocket("ws://localhost:8090/ws/chat?sala=geral");
ws.onmessage = e => console.log(e.data);
ws.onopen = () => ws.send("olá");

No Lazarus os eventos levam @: CreateWebSocketRoute('/chat', @OnChatMessage).

A rota (TRALWebSocketRoute)

O handshake é um GET comum e passa por todo o pipeline do servidor antes de virar conexão: plugins (limites, lista branca, CORS), autenticação, Roles. Um GET que a autenticação recusa recebe o 401 e nunca vira WebSocket. Depois a rota confere:

situação resposta
GET sem Upgrade: websocket 426 Upgrade Required
versão diferente de 13 426, com Sec-WebSocket-Version: 13
Sec-WebSocket-Key inválida 400
Origin fora de AllowedOrigins 403
OnHandshake com AAccept := False 403 (ou o status que o evento pôs)
propriedade padrão o que faz
AllowedOrigins vazio (qualquer) origens de navegador aceitas, separadas por vírgula; um cliente sem Origin (não navegador) sempre passa
Protocols vazio subprotocolos, na ordem de preferência; vale o primeiro que o cliente também oferece
Deflate False aceita permessage-deflate (RFC 7692); cada mensagem é comprimida sozinha, sem memória guardada entre mensagens
MaxMessageSize 16 MB maior mensagem aceita (também depois de descomprimir); acima fecha com 1009
KeepAliveInterval 30000 ms sem tráfego por esse tempo manda ping; sem resposta por mais um intervalo, fecha
MaxQueued 4 MB bytes que podem esperar para sair a um cliente (motores com fila: mORMot2 smAsync)
SendTimeout 10000 ms quanto um envio espera um cliente que não lê; depois fecha com 1008
FragmentSize 0 maior fragmento enviado; 0 manda cada mensagem num quadro

Eventos: OnOpen, OnMessage, OnClose(ACode, AReason) (1006 quando caiu sem quadro de fechamento), OnError (evento que levantou exceção ou cliente que quebrou o protocolo), OnHandshake.

A conexão (TRALWebSocketConnection)

  • Send(texto), SendBinary(bytes), Ping, Close(código, motivo): de qualquer thread.
  • ID (único no processo), User e Roles (da identidade que a autenticação deu ao handshake), Header(nome), QueryParam(nome), Path, RemoteIP, Protocol.
  • Data (um objeto seu; com OwnsData ele é liberado junto).
  • Os eventos da rota rodam na thread do motor daquela conexão, um de cada vez. Guardar a conexão depois do OnClose: AddRef e, no fim, Release.

O hub (TRALWebSocketHub)

Cada rota tem um (Route.Hub, ou AConnection.Hub):

método para quem
Broadcast, BroadcastBinary todos
BroadcastExcept(conexão, texto) todos menos um
SendTo(ID, texto) uma conexão
SendToUser(usuário, texto) as conexões do usuário autenticado
Join, Leave, SendToGroup, GroupCount grupos (nome sem diferença de maiúsculas)
Count, GetIDs, Find, CloseAll o resto

Um envio nunca roda sob a trava do hub: um cliente lento segura só a chamada que escreve para ele (no máximo SendTimeout).

O cliente (TRALWebSocketClient)

Client := TRALWebSocketClient.Create(nil);
Client.URL := 'wss://api.exemplo.com/ws/chat?sala=geral';
Client.OnMessage := OnMensagem;      // na thread de leitura; SyncEvents := True para a principal
Client.AutoReconnect := True;        // 1 s, 2 s, 4 s... até ReconnectMaxDelay
Client.Connect;                      // levanta se o servidor recusar (ex.: 401)
Client.Send('olá');
  • Sobre os sockets do próprio RAL, sem motor HTTP, em todas as plataformas.
  • wss:// segue a regra de certificado do TRALClient: SSL.Pins, SSL.Verify, OnValidateServerCert (o evento decide, senão o pin, senão a OpenSSL).
  • Proxy (HTTP CONNECT ou SOCKS5), Headers extras, Authentication (um TRALClientBasicAuth, ou um Bearer que um TRALClient já obteve), Protocols, Deflate, KeepAliveInterval.

Motores

motor WebSocket como
Indy sim a conexão fica na thread do TIdContext (uma thread por WebSocket)
fpHTTP sim a conexão fica na thread do pool que atendeu o handshake (o pool cresce), TLS incluído
mORMot2 smThreads sim na thread da conexão
mORMot2 smAsync sim no laço de eventos (epoll/IOCP), sem thread por conexão: o modo para muitas conexões
mORMot2 smHttpSys, Sagui, UniGUI, CGI, MsQuic, Kwik não o servidor não inicia com uma rota WebSocket (TRALServer.SupportsWebSocket)

No smAsync, um handshake recusado pelo pipeline volta com o status, mas sem cabeçalhos nem corpo (é a rejeição do mORMot2).

Erros comuns

  • "O motor X não atende rotas WebSocket" ao ativar: troque o motor ou o Mode do Synopse.
  • A conexão cai a cada 60 s atrás de um proxy: o proxy corta conexão ociosa; deixe o KeepAliveInterval abaixo do tempo dele.
  • nginx: precisa de proxy_http_version 1.1, proxy_set_header Upgrade $http_upgrade e proxy_set_header Connection "upgrade".
  • Mensagem grande fecha com 1009: aumente MaxMessageSize (dos dois lados).

Veja também

Começar

Servidor

MQTT 🚧

Cliente

Motores

Avançado

Ajuda

🚧 = em desenvolvimento, pode mudar

Clone this wiki locally