WebSocket
Mensagens instantâneas entre o servidor e o cliente.
Introdução
Com WebSocket é possível estabelecer uma conexão permanente entre o servidor e o navegador do cliente.
Isto quer dizer que a qualquer momento o navegador poderá utilizar a conexão estabelecida via WebSocket para enviar dados para o servidor, sem ser obrigatório obter uma resposta. O mesmo acontece para o servidor que poderá contactar o navegador do cliente enviando dados de forma imediata.
A comunicação é realizada instantaneamente e a qualquer momento é possível transitar dados de um lado para o outro de forma independente e imediata.
É muito útil para realizar operações de realtime com comunicações imediatas, é como se fosse um chat, um bate-papo entre o navegador e o servidor, a qualquer momento qualquer um pode enviar uma mensagem e o outro recebe.
Por exemplo, é muito utilizado desde soluções de chats no geral até jogos, utilizamos também para apresentar alteração de dados ao vivo, ou seja, quando existe a necessidade de realizar comunicação o mais imediata possível.
De forma simplificada a diferença entre a comunicação clássica HTTP e o WebSocket:

Do lado do servidor é criado um endpoint, endereço que permite estabelecer conexões com WebSocket.
O servidor tem a capacidade de enviar dados apenas para uma conexão específica, ou seja, é possível enviar novas mensagens para um participante de um chat em específico.
Também o servidor pode fazer broadcast que é o envio de dados para todas as conexões ativas num determinado
endereço, ou seja, quando é enviado uma mensagem para todos os participantes do chat que estão em uma sala ou em um
grupo.
Ativação e Configuração
Para ativar e configurar o WebSocket na sua aplicação Netuno é necessário editar o arquivo de configuração da aplicação referente ao ambiente que está sendo utilizado, como:
📂 config/_development.json📂 config/_production.json
Insira e ajuste os seguintes parâmetros:
...
"ws": {
"hosts": [...], "<-- OPCIONAL": "mais sobre na documentação abaixo.",
"endpoints": [
{
"name": "pool",
"enabled": true,
"sendTimeout": 10000,
"idleTimeout": 0,
"maxText": 15000,
"public": "/ws/pool",
"path": "/",
"service": "/services/ws/pool"
},
{
"name": "room",
"enabled": true,
"sendTimeout": 10000,
"idleTimeout": 0,
"maxText": 15000,
"public": "/ws/room",
"path": "/{uid}",
"service": "/services/ws/room"
}
]
},
...
endpoints
É um array que permite definir que o servidor vai suportar WebSocket através dos múltiplos endereços públicos específicados (endpoints), então cada endpoint é um endereço público que suporta receber conexões via WebSockets a partir dos navegadores.
name
Nome de identificação do endpoint.
enabled
Permite ativar e desativar o endpoint, o padrão é true (ativo).
sendTimeout
Limite de tempo máximo para enviar uma mensagem para os clientes, o padrão é 60000 milissegundos, ou seja, equivale
a 1 minuto.
idleTimeout
Limite de tempo máximo para a inatividade na conexão, o padrão é 300000 milissegundos, ou seja, equivale a 5 minutos.
maxText
Limite em bytes para o tamanho máximo do comprimento da mensagem, o padrão é 1048576 equivalente a 1 megabyte.
public
Define a base do endereço público que permitirá receber as conexões via WebSocket vinda dos navegadores.
O endereço final é constituído pelo valor da configuração public seguido pelo que está definido no path.
path
Define a parte final do caminho do endereço público, pode ser dinâmico, o que permite criar por exemplos múltiplos canais separados, por exemplo é útil para criar múltiplas salas.
Atenção: O
pathdeve sempre iniciar com/, e na configuração de conexão no front-end é muito importante conter a barra exatamente como definido nopath.
No front-end o endereço final será o public + path, veja o exemplo:
...
"public": "/ws/pool",
"path": "/",
...
Então o endereço final no front-end será /ws/pool/, com a barra no fim.
service
Endereço da base de serviço de back-end da aplicação que processa as conexões e a comunicação via WebSocket.
hosts
Opcionalmente podemos definir os hosts que os nossos endpoints estão disponíveis, ou seja, na conexão vem o endereço do Host no header HTTP, exatamente este host sendo considerado apenas a informação do domínio ou IP e descartado a indicação da porta, é o que é utilizado para associar e encontrar o endpoint correspondente.
Portanto, o endpoint para funcionar corretamente precisa combinar
hostepublic+path, da seguinte forma:
ws://[host]:9000[public][path]Em produção com NGINX, proxy reverso e SSL deve ficar assim:
wss://[host][public][path]
O Netuno define alguns domínios locais com o prefixo do nome da app, que associa automaticamente como valor
padrão para os hosts dos endpoints, além que na app em desenvolvimento adiciona também o localhost e o
127.0.0.1 aos hosts padrão, por isso a definição dos hosts é opcional, porque há esta configuração automática.
Os seguintes hosts são definidos automaticamente para os endpoints, caso o nome da app seja minhapp:
- Este direciona diretamente para o IP local 127.0.0.1:
minhaapp.local.netu.no
- Estes para funcionar é necessário registrar no arquivo de hosts do sistema operacional:
minhaapp.localhostminhaapp.local
Caso o nome da app seja minha_app então passa a ser estes os hosts automáticos:
- Este direciona diretamente para o IP local 127.0.0.1:
minha-app.local.netu.no
- Estes para funcionar é necessário registrar no arquivo de hosts do sistema operacional:
minha-app.localhostminha-app.local
No domínio não é permitido o
_(subtraço) e no nome das apps não é permitido o-(traço), por isso externamente utilizamos o-(traço) no endereço, mas o Netuno transforma automaticamente o-(traço) em_(subtraço) para encontrar a aplicação certa.
A aplicação padrão ou a aplicação definida na inicialização do servidor do Netuno tem adicionalmente os seguintes hosts:
localhost127.0.0.1
Para definir hosts customizados utilizamos as seguintes configurações:
...
"ws": {
"endpoints": [
{
"hosts": ["meu-dominio1.com", "www.meu-dominio1.com"],
...
},
{
"hosts": ["meu-dominio2.com", "www.meu-dominio2.com"],
...
}
]
},
...
Caso todos os endpoints funcionem para os mesmos hosts então podemos definir globalmente desta forma:
...
"ws": {
"hosts": ["meu-dominio.com", "www.meu-dominio1.com"],
"endpoints": [
...
]
},
...
Certifique que no endereço final do WebSocket utiliza sempre um domínio ou IP que seja um host válido, caso contrário não funcionará porque o endpoint não poderá ser encontrado internamente.
Serviço para os Endpoints
POST = nova conexão
É executado o serviço configurado com o método POST quando uma nova conexão é estabelecida, por exemplo:
- JavaScript
- Python
- Ruby
- Kotlin
- Groovy
const uidRoom = _ws.path().getString("uid")
const dbRoom = _db.get("room", uidRoom)
if (dbRoom == null) {
_log.warn(`Invalid room ${uidRoom}.`)
_ws.close()
} else {
_log.info(`New WebSocket Session: ${_ws.sessionId()}`)
}
uidRoom = _ws.path().getString("uid")
dbRoom = _db.get("room", uidRoom)
if dbRoom == null:
_log.warn(f"Invalid room {uidRoom}.")
_ws.close()
else:
_log.info(f"New WebSocket Session: {_ws.sessionId()}")
uidRoom = _ws.path().getString("uid")
dbRoom = _db.get("room", uidRoom)
if dbRoom == null
_log.warn("Invalid room #{uidRoom}.")
_ws.close()
else
_log.info("New WebSocket Session: #{_ws.sessionId()}")
end
val uidRoom = _ws.path().getString("uid")
val dbRoom = _db.get("room", uidRoom)
if (dbRoom == null) {
_log.warn("Invalid room ${uidRoom}.")
_ws.close()
} else {
_log.info("New WebSocket Session: ${_ws.sessionId()}")
}
def uidRoom = _ws.path().getString("uid")
def dbRoom = _db.get("room", uidRoom)
if (dbRoom == null) {
_log.warn("Invalid room ${uidRoom}.")
_ws.close()
} else {
_log.info("New WebSocket Session: ${_ws.sessionId()}")
}
Normalmente o
_ws.sessionId()é guardado em base de dados para ser utilizado em futuras comunicações.
PUT = nova mensagem
Se a mensagem recebida não for em formato JSON, ou se for um JSON que não está no formato suportado para execução de
um serviço específico (service), então será executado este serviço genérico:
- JavaScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(`The session ${_ws.sessionId()} sent this message: ${_ws.message()}`)
_log.info(f"The session {_ws.sessionId()} sent this message: {_ws.message()}")
_log.info("The session #{_ws.sessionId()} sent this message: #{_ws.message()}")
_log.info("The session ${_ws.sessionId()} sent this message: ${_ws.message()}")
_log.info("The session ${_ws.sessionId()} sent this message: ${_ws.message()}")
DELETE = fechou a conexão
É executado quando acontece uma desconexão do navegador do cliente.
- JavaScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(`Session ${_ws.sessionId()} has disconnected.`)
_log.info(f"Session {_ws.sessionId()} has disconnected.")
_log.info("Session #{_ws.sessionId()} has disconnected.")
_log.info("Session ${_ws.sessionId()} has disconnected.")
_log.info("Session ${_ws.sessionId()} has disconnected.")
GET = fluxo contínuo de mensagens
Inicia a execução quando acontece uma nova conexão do navegador do cliente, e a execução é mantida até finalizar a conexão.
Ou seja, o serviço para o método HTTP GET fica recebendo as mensagens que estão vindo do cliente continuamente
em stream, ou seja, é um fluxo aberto e contínuo de mensagens que tando pode receber como enviar.
- JavaScript
- Python
- Ruby
- Kotlin
- Groovy
if (_ws.isStream()) {
while (_ws.awaitStream()) {
if (_ws.isBinaryStreamed()) {
_log.info("Nova mensagem binária recebida: "+ _convert.textFromBytes(_ws.binaryStreamed()))
} else if (_ws.isTextStreamed()) {
_log.info("Nova mensagem de texto recebida: "+ _ws.textStreamed())
}
_ws.sendText("Ok! A mensagem foi recebida com sucesso.")
}
}
if _ws.isStream():
while _ws.awaitStream():
if _ws.isBinaryStreamed():
_log.info("Nova mensagem binária recebida: "+ _convert.textFromBytes(_ws.binaryStreamed()))
elif _ws.isTextStreamed():
_log.info("Nova mensagem de texto recebida: "+ _ws.textStreamed())
_ws.sendText("Ok! A mensagem foi recebida com sucesso.")
if _ws.isStream()
while _ws.awaitStream()
if _ws.isBinaryStreamed()
_log.info("Nova mensagem binária recebida: "+ _convert.textFromBytes(_ws.binaryStreamed()))
elsif _ws.isTextStreamed()
_log.info("Nova mensagem de texto recebida: "+ _ws.textStreamed())
end
_ws.sendText("Ok! A mensagem foi recebida com sucesso.")
end
end
if (_ws.isStream()) {
while (_ws.awaitStream()) {
if (_ws.isBinaryStreamed()) {
_log.info("Nova mensagem binária recebida: "+ _convert.textFromBytes(_ws.binaryStreamed()))
} else if (_ws.isTextStreamed()) {
_log.info("Nova mensagem de texto recebida: "+ _ws.textStreamed())
}
_ws.sendText("Ok! A mensagem foi recebida com sucesso.")
}
}
if (_ws.isStream()) {
while (_ws.awaitStream()) {
if (_ws.isBinaryStreamed()) {
_log.info("Nova mensagem binária recebida: "+ _convert.textFromBytes(_ws.binaryStreamed()))
} else if (_ws.isTextStreamed()) {
_log.info("Nova mensagem de texto recebida: "+ _ws.textStreamed())
}
_ws.sendText("Ok! A mensagem foi recebida com sucesso.")
}
}
O if com o _ws.isStream() faz com que verifique se realmente é um pedido interno gerido pelo WebSocket para
realizar o stream.
No while com o _ws.awaitStream() fica aguardando que um novo fluxo de mensagem chegue, ou seja, aguarda a
chegada de uma nova mensagem. Assim que houver uma nova mensagem é executado o código interno do while.
O
_ws.awaitStream()funciona como um loop infinito aguardando uma nova mensagem.
Então nos ifs internos do while é verificado o tipo de mensagem, se a mensagem é binária ou de texto, em ambos
os casos é apresentado nos logs o conteúdo destas mensagens. Repare que qualquer formato de mensagem pode ser
utilizado, podendo ser processadas de qualquer maneira que seja conveniente.
Finalmente no _ws.sendText é enviada uma mensagem de resposta ao cliente, confirma a recepção.
Cada execução do serviço
GETserá uma conexão de ativa.
Quando a conexão com o endpoint do WebSocket for finalizada então a execução deste serviço é finalizada internamente.
Comunicação
sendService
Executa o serviço e envia o output gerado para um cliente específico conectado através do WebSocket, utiliza o ID de
conexão do cliente (sessionId).
Exemplo da Chamada de Serviço
O código abaixo pode ser utilizado no serviço de método PUT do endpoint (service), repare que está sendo
utilizado o _ws.sessionId() que obtém o ID da sessão de conexão do cliente, se o identificador de sessão
estiver guardado em base de dados o sendService poderia ser executado em qualquer outro serviço a qualquer
momento.
- JavaScript
- Python
- Ruby
- Kotlin
- Groovy
const uid = _ws.path().getString("uid")
_ws.sendService(
_ws.sessionId(), // Identificação da conexão atual do WebSocket.
_val.map() // Parametrização do serviço que será executado.
.set("method", "POST")
.set("service", "/services/room/participant/new")
.set(
"data", // Parâmetros que serão passados para o servi ço e recebidos através do _req.
_val.map()
.set("uid", uid)
)
)
uid = _ws.path().getString("uid")
_ws.sendService(
_ws.sessionId(), # Identificação da conexão atual do WebSocket.
_val.map() # Parametrização do serviço que será executado.
.set("method", "POST")
.set("service", "/services/room/participant/new")
.set(
"data", # Parâmetros que serão passados para o serviço e recebidos através do _req.
_val.map()
.set("uid", uid)
)
)
uid = _ws.path().getString("uid")
_ws.sendService(
_ws.sessionId(), # Identificação da conexão atual do WebSocket.
_val.map() # Parametrização do serviço que será executado.
.set("method", "POST")
.set("service", "/services/room/participant/new")
.set(
"data", # Parâmetros que serão passados para o serviço e recebidos através do _req.
_val.map()
.set("uid", uid)
)
)
val uid = _ws.path().getString("uid")
_ws.sendService(
_ws.sessionId(), // Identificaç ão da conexão atual do WebSocket.
_val.map() // Parametrização do serviço que será executado.
.set("method", "POST")
.set("service", "/services/room/participant/new")
.set(
"data", // Parâmetros que serão passados para o serviço e recebidos através do _req.
_val.map()
.set("uid", uid)
)
)
def uid = _ws.path().getString("uid")
_ws.sendService(
_ws.sessionId(), // Identificação da conexão atual do WebSocket.
_val.map() // Parametrização do serviço que será executado.
.set("method", "POST")
.set("service", "/services/room/participant/new")
.set(
"data", // Parâmetros que serão passados para o serviço e recebidos através do _req.
_val.map()
.set("uid", uid)
)
)
Ou seja, na configuração o path que está configurado no endpoint tem o valor /{uid}, assim permite o cliente
conectar através do endereço público:
/ws/room/d641f095-30eb-4025-a39c-b2e3e497eab7
Então o valor do uid será d641f095-30eb-4025-a39c-b2e3e497eab7.
Após será executado o serviço que fica na URL /services/room/participant/new e o valor do uid é passado.
O uid pode ser obtido no código do serviço que é executado, como um parâmetro do pedido HTTP, veja um exemplo:
- JavaScript
- Python
- Ruby
- Kotlin
- Groovy
// O UID enviado é obtido diretamente no request.
const uid = _req.getString('uid')
# O UID enviado é obtido diretamente no request.
uid = _req.getString('uid')
# O UID enviado é obtido diretamente no request.
uid = _req.getString('uid')
// O UID enviado é obtido diretamente no request.
val uid = _req.getString('uid')
// O UID enviado é obtido diretamente no request.
def uid = _req.getString('uid')
broadcastService
Para realizar o broadcast é preciso o nome do endpoint do WebSocket, definir o caminho (path) se aplicável, e o
serviço que será executado.
O output do serviço será enviado para todas as conexões ativas no
endpointespecificado.
Exemplo de Broadcast
- JavaScript
- Python
- Ruby
- Kotlin
- Groovy
_ws.broadcastService(
"admin", // Nome do endpoint do WebSocket.
"/", // Caminho configurado no endpoint (path).
_val.map() // Parametrização do serviço que será executado.
.set("service", "/services/room/participant/list")
)
_ws.broadcastService(
"admin", # Nome do endpoint do WebSocket.
"/", # Caminho configurado no endpoint (path).
_val.map() # Parametrização do serviço que será executado.
.set("service", "/services/room/participant/list")
)
_ws.broadcastService(
"admin", # Nome do endpoint do WebSocket.
"/", # Caminho configurado no endpoint (path).
_val.map() # Parametrização do serviço que será executado.
.set("service", "/services/room/participant/list")
)
_ws.broadcastService(
"admin", // Nome do endpoint do WebSocket.
"/", // Caminho configurado no endpoint (path).
_val.map() // Parametrização do serviço que será executado.
.set("service", "/services/room/participant/list")
)
_ws.broadcastService(
"admin", // Nome do endpoint do WebSocket.
"/", // Caminho configurado no endpoint (path).
_val.map() // Parametrização do serviço que será executado.
.set("service", "/services/room/participant/list")
)
Neste exemplo o conteúdo de output gerado pelo serviço room/participant/list será enviado para todos que estiverem
conectados no endpoint admin.
WS Client - NPM
Para realizar facilmente a integração com o frontend, é disponibilizado o módulo do NPM:
Comando de instalação:
pnpm install @netuno/ws-client
_ws.config({
url: 'ws://localhost:9000/ws/example/',
servicesPrefix: '/services',
method: 'GET',
autoReconnect: true,
connect: (event) => {
console.info('WebSocket Connected', event);
},
close: (event) => {
console.warn('WebSocket Closed', event);
},
error: (error) => {
console.error('WebSocket Error', error);
},
message: (data, event) => {
console.debug('WebSocket Message', data);
}
});
Certifique que a URL combina corretamente os valores configurados no endpoint para
hostepublic+path, da seguinte forma:
ws://[host]:9000[public][path]Já a URL de produção com a integração do NGINX, proxy reverso e SSL, provavelmente será assim:
wss://[host][public][path]
A seguir pode iniciar a conexão:
_ws.connect();
Executar Serviço via WebSocket
Podemos executar serviços da API REST através da conexão WebSocket, isso quer dizer que não será realizado nenhum pedido HTTP, então os dados do pedido são enviados como mensagem, e a resposta do serviço é obtida como uma mensagem recebida de volta.
Antes de iniciar devemos criar um observador da resposta do serviço, por exemplo:
const listenerRef = _ws.addListener({
method: 'GET', // Opcional, por padrão é GET.
service: 'caminho/do/meu/servico/aqui',
success: (data) => {
console.log('Resposta do serviço executado via WebSocket:', data);
},
fail: (error)=> {
console.log('O serviço executado via WebSocket falhou:', error);
}
});
Agora podemos enviar um pedido de execução do serviço, sendo que a resposta do serviço será obtida no observador criado acima, executamos o serviço assim:
_ws.sendService({
method: 'GET', // Opcional, outro método HTTP pode ser utilizado.
service: 'caminho/do/meu/servico/aqui',
data: {
message: 'Olá...'
}
});
Para remover o observador adicionado, utilize:
_ws.removeListener(listenerRef);
E para fechar a conexão WebSocket:
_ws.close();
NGINX Proxy Reverso
No NGINX o proxy reverso pode ser configurado desta forma:
location /ws {
proxy_pass http://localhost:9000;
proxy_set_header Host minha-app.local.netu.no;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_http_version 1.1;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
add_header X-Frame-Options "";
}
Na linha 3 onde define o
Hostdeve ser ajustado a parte daminha-apppara o nome correspondente da aplicação Netuno. Se houver_no nome da aplicação então no endereço doHostserá substituído por-, por exemplo, a aplicaçãominha_apppassa a ter o valor doHostassimminha-app.local.netu.no.
Reinicie o NGINX para que a configuração faça efeito.
Os domínios configurados no NGINX, ou seja, a parametrização do server_name não precisa estar nos hosts do
endpoint na aplicação do Netuno, porque na configuração de proxy acima é alterado o Host no header do HTTP
para um endereço padrão que já é configurado automaticamente pelo Netuno sempre, des que o prefixo com o nome da
aplicação esteja correta.
Caso o servidor do NGINX utilize a porta 80, então a URL final será:
ws://[host][public][path]
E com SSL/HTTPS, caso o servidor do NGINX utilize a porta 443, então a URL final será:
wss://[host][public][path]