Introdução
O AdonisJs oferece um WebSocket Provider robusto para servir aplicativos em tempo real.
O servidor funciona em conexões WebSocket puras (com suporte de todos os principais navegadores) e escala naturalmente dentro de um cluster de processos Node.js.
Configuração
Como o WebSocket Provider não é instalado por padrão, extraia-o de npm
:
adonis install @adonisjs/websocket
A instalação do provedor adiciona os seguintes arquivos ao seu projeto:
config/socket.js
contém a configuração do servidor WebSocket.start/socket.js
inicializa o servidor WebSocket e registra canais.start/wsKernel.js
registra o middleware para executar na assinatura do canal.
Em seguida, registre o provedor dentro do arquivo start/app.js
:
// .start/app.js
const providers = [
'@adonisjs/websocket/providers/WsProvider'
]
Finalmente, instrua o Ignitor a inicializar o servidor WebSocket no arquivo raiz server.js
:
// .server.js
const { Ignitor } = require('@adonisjs/ignitor')
new Ignitor(require('@adonisjs/fold'))
.appRoot(__dirname)
.wsServer() // boot the WebSocket server
.fireHttpServer()
.catch(console.error)
Suporte a cluster
Ao executar um cluster Node.js, o nó mestre precisa conectar a comunicação pub/sub entre os nós de trabalho.
Para fazer isso, adicione o seguinte código ao topo do arquivo raiz server.js
:
// .server.js
const cluster = require('cluster')
if (cluster.isMaster) {
for (let i=0; i < 4; i ++) {
cluster.fork()
}
require('@adonisjs/websocket/clusterPubSub')()
return
}
// ...
Exemplo básico
Vamos construir um servidor de bate-papo de sala única para mensagens do usuário.
Para manter as coisas simples, não armazenaremos mensagens do usuário, apenas as entregaremos.
Abra o arquivo start/socket.js
e cole o seguinte código:
// .start/socket.js
const Ws = use('Ws')
Ws.channel('chat', 'ChatController')
NOTA
Também podemos vincular um fechamento ao método Ws.channel
, mas ter um controlador dedicado é a prática recomendada.
Em seguida, crie o ChatController
usando o comando make:controller
:
adonis make:controller Chat --type=ws
# .Output
✔ create app/Controllers/Ws/ChatController.js
// .app/Controllers/Ws/ChatController.js
'use strict'
class ChatController {
constructor ({ socket, request }) {
this.socket = socket
this.request = request
}
}
module.exports = ChatController
Código do cliente
Vamos alternar do servidor para o cliente e assinar o canal chat
.
Primeiro, copie o modelo CSS e HTML deste gist para os seguintes locais:
- CSS →
public/style.css
- Modelo HTML →
resources/views/chat.edge
OBSERVAÇÃO
Certifique-se de definir uma rota para servir o modelo HTML.
Em seguida, crie um arquivo public/chat.js
e cole o código abaixo para conectar o cliente ao servidor (para manter as coisas simples, estamos usando jQuery):
// .public/chat.js
let ws = null
$(function () {
// Only connect when username is available
if (window.username) {
startChat()
}
})
function startChat () {
ws = adonis.Ws().connect()
ws.on('open', () => {
$('.connection-status').addClass('connected')
subscribeToChannel()
})
ws.on('error', () => {
$('.connection-status').removeClass('connected')
})
}
Em seguida, adicione o método de assinatura do canal, vinculando ouvintes para manipular mensagens:
// .public/chat.js
// ...
function subscribeToChannel () {
const chat = ws.subscribe('chat')
chat.on('error', () => {
$('.connection-status').removeClass('connected')
})
chat.on('message', (message) => {
$('.messages').append(`
<div class="message"><h3> ${message.username} </h3> <p> ${message.body} </p> </div>
`)
})
}
Finalmente, adicione o manipulador de eventos para enviar uma mensagem quando a tecla [Enter] for liberada:
// .public/chat.js
// ...
$('#message').keyup(function (e) {
if (e.which === 13) {
e.preventDefault()
const message = $(this).val()
$(this).val('')
ws.getSubscription('chat').emit('message', {
username: window.username,
body: message
})
return
}
})
Código do servidor
Agora que terminamos com o cliente, vamos voltar para o servidor.
Adicione o método onMessage
event ao arquivo ChatController
:
// .app/Controllers/Ws/ChatController.js
class ChatController {
constructor ({ socket, request }) {
this.socket = socket
this.request = request
}
onMessage (message) {
this.socket.broadcastToAll('message', message)
}
}
No exemplo acima, o método onMessage
envia a mesma mensagem para todos os clientes conectados por meio do método broadcastToAll
do soquete.
Controladores
Os controladores mantêm seu código organizado definindo classes separadas por canal.
Os controladores WebSocket são armazenados no diretório app/Controllers/Ws
.
Uma nova instância do controlador é criada por assinatura com um objeto context
passado para seu construtor, permitindo que a instância socket
seja descompactada assim:
class ChatController {
constructor ({ socket }) {
this.socket = socket
}
}
Métodos de evento
Vincule a eventos WebSocket criando métodos de controlador com o mesmo nome:
class ChatController {
onMessage () {
// same as: socket.on('message')
}
onClose () {
// same as: socket.on('close')
}
onError () {
// same as: socket.on('error')
}
}
OBSERVAÇÃO
Os métodos de evento devem ser prefixados com a palavra-chave on
.