Tutoriais

Crie um app de chat em tempo real com MQTT

Crie um chat no navegador sobre MQTT: salas como topics, presença com mensagens retidas e Last Will, identidade do remetente garantida por ACLs, tudo em um HTML.

Atualizado · 9 min de leitura

Crie um app de chat em tempo real com MQTT

O MQTT já faz a parte difícil de um app de chat: ele entrega cada mensagem a todos os assinantes em tempo real. Um chat sobre MQTT se resume a três decisões: como as salas viram topics, como a presença funciona e como impedir que alguém poste com o nome de outra pessoa. Este tutorial monta um chat com várias salas funcionando em um único arquivo HTML com MQTT.js, conectado via WebSockets seguros, e depois mostra como testá-lo e reforçá-lo.

Design de topics

TopicPayloadQoSRetidaFinalidade
testmqtt/chat/<room>/messages/<user>{"text":"…","ts":…}1NãoMensagens do chat
testmqtt/chat/<room>/presence/<user>{"status":"online"} ou {"status":"offline"}1SimQuem está na sala

Duas escolhas nesse layout fazem a maior parte do trabalho:

  • O remetente faz parte do topic. Um cliente assina testmqtt/chat/<room>/messages/+ e lê o nome de usuário no último nível do topic. Com uma ACL que só deixa cada usuário escrever com o próprio nome (veja abaixo), o remetente não pode mais ser falsificado. Um campo "user" dentro do JSON poderia.
  • Presença é uma mensagem retida por usuário. Mensagens retidas são entregues no momento em que um cliente assina, então quem chega vê a lista completa de membros na hora, sem perguntar a ninguém.

As salas são só valores diferentes de <room>, então você tem quantas salas quiser sem nenhuma configuração no broker. As boas práticas de design de topics explicam por que hierarquias estáveis com o identificador no final, como esta, escalam bem.

Presença com mensagens retidas e Last Will

A presença usa dois recursos do MQTT juntos:

  1. Depois de conectar, o cliente publica {"status":"online"} retida no próprio topic de presença.
  2. No pacote CONNECT, o mesmo cliente registra um Last Will: {"status":"offline"}, também retido, no mesmo topic.

Se a conexão terminar sem um DISCONNECT MQTT limpo (aba fechada, notebook suspenso, rede caída), o broker publica o will. Como ele é retido, substitui a mensagem "online" e todos os membros veem o usuário ficar offline. Quando o usuário clica em Leave, o cliente apaga a própria presença de propósito publicando um payload retido vazio, o que remove a mensagem retida.

A página de chat completa

Salve isto como chat.html e abra duas vezes: chat.html?user=alice e chat.html?user=bob. Adicione &room=seutime para escolher uma sala. No endpoint de testes compartilhado, escolha um nome de sala incomum para não conversar com desconhecidos.

@@CHAT@@

Alguns detalhes que merecem atenção:

  • textContent, nunca innerHTML. Mensagens de chat são entrada não confiável vinda de outros usuários. Renderizá-las como HTML abre uma brecha de cross-site scripting.
  • Os nomes de sala e de usuário são higienizados para que um nome com /, + ou # não escape do seu nível de topic.
  • Suas próprias mensagens voltam pelo broker. Essa é a sua confirmação de entrega. Se preferir renderizá-las localmente, o MQTT 5 permite assinar com { qos: 1, nl: true } (No Local) para que o broker não as devolva.
  • Payloads malformados são ignorados em vez de derrubar o handler, porque qualquer um com permissão de publicação pode mandar qualquer coisa.

Se a configuração do MQTT.js é nova para você, comece por MQTT no navegador com JavaScript puro, que explica as opções de conexão e o fluxo de depuração.

Teste

Abra duas abas (alice e bob) e passe por estas verificações:

  1. As mensagens fluem nos dois sentidos. Cada mensagem aparece nas duas abas, com o remetente tirado do topic.
  2. Quem chega depois vê a presença. Abra uma terceira aba: a lista de membros é preenchida na hora pelas mensagens retidas.
  3. O will dispara. Feche a aba do bob. A alice deve ver o bob em cinza em poucos segundos.
  4. Leave limpa. Clique em Leave em uma aba: o usuário some da lista em vez de aparecer como offline.

Para ver o tráfego bruto, assine testmqtt/chat/lobby/# no cliente MQTT online, ou pelo terminal:

Acompanhar uma sala e postar como bot
mosquitto_sub -h broker.connectmq.com -t 'testmqtt/chat/lobby/#' -v

mosquitto_pub -h broker.connectmq.com -q 1 \
  -t testmqtt/chat/lobby/messages/deploy-bot \
  -m "{\"text\":\"Build 412 deployed\",\"ts\":$(date +%s)000}"

O segundo comando mostra que qualquer serviço (CI, monitoramento, um backend) pode postar em uma sala com um único publish. Para mais formas de exercitar o will, veja testar o Last Will.

Acompanhe o lobby no cliente online

Garanta a identidade com ACLs

Em um endpoint de testes aberto, qualquer pessoa pode publicar em testmqtt/chat/lobby/messages/alice. Em produção, dê a cada usuário credenciais próprias no broker e uma ACL que amarre os topics ao nome de usuário. No Mosquitto, regras pattern substituem %u pelo nome de usuário autenticado:

acl_file (Mosquitto)
# Everyone can read every room
pattern read testmqtt/chat/+/#

# ...but may only write under their own name
pattern write testmqtt/chat/+/messages/%u
pattern write testmqtt/chat/+/presence/%u

O broker verifica o will com as mesmas regras, então um cliente também não consegue registrar um will no topic de presença de outra pessoa. Depois use os testes de permissão de ACL para confirmar que um publish no topic de outro usuário é realmente rejeitado. No MQTT 3.1.1 esse publish geralmente é descartado em silêncio, então verifique se ele nunca chega em vez de esperar um erro.

O que o MQTT não oferece

NecessidadeSó MQTTSolução típica
Histórico de mensagensNão (só a última mensagem retida por topic)Um assinante no backend gravando em um banco de dados, mais um endpoint HTTP de histórico
Entrega para quem ficou fora por horasSessões persistentes enfileiram mensagens, mas dentro dos limites do brokerBanco de dados mais uma busca "desde a última visita" ao reconectar
Confirmação de leitura e indicador de digitaçãoNão incluídoTopics extras, por exemplo .../typing/<user> com QoS 0
Moderação, exclusão de mensagensNãoServiço de backend com permissão de publicação em um topic de sistema

Sessões persistentes também exigem um broker que guarde o estado da sessão. O endpoint de testes compartilhado não guarda, então teste a recuperação de mensagens na sua própria instância.

Um chat em tempo real sobre MQTT é, acima de tudo, design de topics: salas como níveis de topic, o remetente no topic, presença retida com Last Will e ACLs que mantêm cada usuário na sua faixa. Monte tudo em um arquivo HTML, verifique cada comportamento com duas abas e um assinante observando #, e adicione um banco de dados só quando os usuários precisarem de histórico.

Perguntas frequentes

MQTT é um bom protocolo para chat?

Para entrega ao vivo, sim: publish/subscribe se encaixa naturalmente em salas, as mensagens são pequenas e o Last Will dá presença de graça. Mas o MQTT não guarda histórico do chat, então combine-o com um banco de dados se os usuários precisarem rolar para trás ou se atualizar depois de ficarem offline.

Como mostrar quem está online com MQTT?

Cada cliente publica uma mensagem retida 'online' no próprio topic de presença e registra um Last Will retido 'offline' no mesmo topic. Novos clientes recebem todas as mensagens de presença retidas ao assinar, e o broker publica o will quando um cliente some.

Como impedir que usuários se passem uns pelos outros?

Coloque o nome do remetente no topic, por exemplo chat/<sala>/messages/<usuario>, e use uma ACL do broker que só deixe cada usuário publicar com o próprio nome. Assim quem recebe confia no topic, e não em um campo de nome dentro do payload.