Repository navigation
websocket
Home > 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.
- 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.
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).
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.
-
Send(texto),SendBinary(bytes),Ping,Close(código, motivo): de qualquer thread. -
ID(único no processo),UsereRoles(da identidade que a autenticação deu ao handshake),Header(nome),QueryParam(nome),Path,RemoteIP,Protocol. -
Data(um objeto seu; comOwnsDataele é 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:AddRefe, no fim,Release.
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).
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 doTRALClient:SSL.Pins,SSL.Verify,OnValidateServerCert(o evento decide, senão o pin, senão a OpenSSL). -
Proxy(HTTP CONNECT ou SOCKS5),Headersextras,Authentication(umTRALClientBasicAuth, ou um Bearer que umTRALClientjá obteve),Protocols,Deflate,KeepAliveInterval.
| 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).
-
"O motor X não atende rotas WebSocket" ao ativar: troque o motor ou o
Modedo Synopse. -
A conexão cai a cada 60 s atrás de um proxy: o proxy corta conexão ociosa; deixe o
KeepAliveIntervalabaixo do tempo dele. -
nginx: precisa de
proxy_http_version 1.1,proxy_set_header Upgrade $http_upgradeeproxy_set_header Connection "upgrade". -
Mensagem grande fecha com 1009: aumente
MaxMessageSize(dos dois lados).
-
Hubs: chamadas e eventos em JSON,
ralhub.js, vários servidores. - Autenticação, Resposta em fluxo, Internos dos motores.
- Exemplo:
PascalRAL-Samples,Delphi/Console/WebSocketExampleeLazarus/Console/WebSocketExample.
Começar
Servidor
- Rotas e parâmetros
- Rotas avançadas: tipos, grupos, versões 🚧
- Ler o pedido e responder
- Objetos no corpo 🚧
- Resposta em fluxo 🚧
- Plugins 🚧
- Autenticação
- Banco de dados (DBWare)
- Servir site e arquivos
- JSON-RPC 2.0 🚧
- Servidor MCP (IA) 🚧
- WebSocket 🚧
- Hubs (WebSocket) 🚧
- Swagger e Postman
- Servidor SOAP 🚧
- WS-Security 🚧
- HTTPS
- Criptografia ponto a ponto
- Segurança para produção
- Colocar no ar
- Desempenho
- Testes em memória 🚧
MQTT 🚧
- O que é e quando usar 🚧
- Cliente MQTT 🚧
- Broker MQTT 🚧
- MQTT-SN 🚧
- Ponte REST 🚧
- Segurança 🚧
- Rede ruim 🚧
Cliente
Motores
Avançado
Ajuda
- Problemas comuns
- Atualizar o Indy
- Várias versões do Delphi
- Vídeos e guias
- Módulos de terceiros
- Licenças
- Contribuir
- Roadmap e changelog
🚧 = em desenvolvimento, pode mudar