É... Eu sei que você perdeu minha palestra no Seminário sobre o Ecossistema Javascript, do CISL, mas, como o assunto é muito interessante, resolvi postar aqui o código-fonte e um artigo sobre isso.
Veja como implementar comunicação de baixa latência entre os clientes e o Servidor usando apenas o padrão (HTML 5).
Sempre foi um sonho dos desenvolvedores Web poder enviar informações para as páginas. Isso é conhecido por "push". Na verdade, existe uma forma mais simples de fazer isso, por exemplo, usando o "meta" refresh. Isso é conhecido como "push simulado".
E podemos implementar isso até mesmo usando Ajax e "setInterval", porém, ainda estaremos agindo da mesma forma:
- Usando o mesmo socket;
- Usando o mesmo HTTP;
- Sujeitos à mesma latência.
Latência???
Sim! É todo o tempo gasto entre o envio da mensagem e seu recebimento, no destinatário. O HTTP é um protocolo complexo e existe alguma latência decorrente disso.Considerando uma aplicação de Chat, temos uma página na qual o usuário digita a mensagem, e também pode receber respostas. Se a comunicação entre ambos os usuários for feita na base do HTTP, notaremos que existe um tempo, além do necessário, deste que digitamos a mensagem até que ela seja exibida na tela do segundo usuário.
Parte dessa "latência" é o próprio mecanismo que estamos usando para fazer a mensagem aparecer. Na verdade, o segundo usuário tem que perguntar ao Servidor, com determinada frequência, se há novas mensagens. Só isso implica em:
- Esperar o intervalo de tempo;
- Enviar mensagem ao Servidor;
- Receber resposta.
Se nossa aplicação apenas envia mensagens, como um Chat, essa latência pode ser tolerável. Mas e se estivermos fazendo uma tarefa na qual o tempo é mais crítico, como controlar um "drone", por exemplo? O Drone vai se chocar com a parede, antes de receber o comando para virar!
É aí que entra o Websocket!
Websocket é um padrão e uma tecnologia para comunicação bidirecional em HTML, com Baixa latência e Bidirecional. Está implementado no HTML 5 e vários navegadores dão suporte. E, se não derem, podemos recorrer a um "fallback" usando Ajax.Para saber o suporte a Websockets, podemos consultar o site "html5test.com":
Funciona inicialmente com HTTP e faz "upgrade" da conexão. Usa o mesmo Navegador e o mesmo Servidor Web, maior segurança e estabilidade. Por exemplo, podemos requisitar um Websocket utilizando o novo protocolo "ws", a partir do Navegador:
var connection =
new WebSocket('ws://server.example.com',
['soap', 'xmpp']);
Neste momento, o Navegador envia um request ao Servidor solicitando o "upgrade" para Websockets:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: ...
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
E o Servidor, caso suporte o "upgrade", responde dessa forma:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ...
Sec-WebSocket-Protocol: chat
A partir daí, o Cliente é acionado sempre que alguma mensagem chegar via o Websocket, como se fosse um Servidor, sem necessidade de ficar "cutucando" o Servidor para saber se tem nova mensagem. O Servidor simplesmente usa o Websocket e envia a mensagem ao Cliente.
Um exemplo vale mais que 1000 palavras...
Ok. Eu fiz um exemplo bem legal que vai fazer cair o seu queixo! Antes de mais nada, veja só a imagem dele, com 4 navegadores rodando:Ao digitar uma mensagem e clicar no botão, a mesma é enviada ao Servidor (usando um HTTP PUT via Ajax) e o Servidor a re-envia para todos os Websockets conectados. A resposta é quase instantânea.
Este projeto está no GitHub.
É uma aplicação Node.js / Express típica. Ao obter a página inicial, o Template Jade já armazena um UUID (identificador de sessão) enviado pelo servidor (arquivo: layout.jade):
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js') script(type='text/javascript', src='/javascripts/index.js') script(type='text/javascript') | meuid = "#{uuid}"; body block content
A página carrega o jQuery e um script meu "index.js", que intercepta o evento "onload" da página:
// Página de eventos do Cliente var porta = 8080; var websocket; $(document).ready(function(){ $("#btnsend").click(function(){ $.ajax({ url: "/msg/" + $("#textomsg").val(), type: 'PUT', fail: function(jqXHR, textStatus) { $("#mensagens") .text("Erro ao postar mensagem: " + textStatus); } }); }); websocket = new WebSocket("ws://localhost:9090/"); websocket.onopen = function() { console.log('Websocket aberto'); websocket.send(meuid); }; websocket.onerror = function(e) { console.log('Websocket erro: ' + e.data); }; websocket.onmessage = function(e) { $('#mensagens').text(e.data) console.log('mensagem: ' + e.data); }; });
Este script modifica o clique do botão, para enviar um request REST PUT usando Ajax, com a nova mensagem. E também abre um Websocket com o Servidor, enviando o UUID recebido inicialmente. Ele cria um "callback" para quando chega uma mensagem do Servidor, neste caso, ele simplesmente exibe o conteúdo em uma DIV.
Não há necessidade de "refresh" e nem de "setInterval()". Simplesmente, o Cliente se comportará como um Servidor, com um socket bi-direcional aberto. Ele poderá receber mensagens sem ter enviado coisa alguma. Legal, não?
Do lado Servidor, eu simplesmente usei o módulo "ws" que possibilita o uso de Websocket com aplicações Node.js. No meu código app.js eu abro o Websocket, depois de registrar as Rotas do Express:
...
app.get('/', routes.index); app.put('/msg/:mensagem', routes.novamsg); http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); }); // Websocket wsserver = new WebSocketServer({port: 9090}); wsserver.on('connection', function(ws) { conexoes.push(ws); ws.on('message', function(message) { ws.uuid = message; console.log('WS identificado: ' + ws.uuid); }); });
Eu tenho duas rotas: uma, para baixar a página inicial, e outra para enviar uma mensagem. Logo após fixar as rotas, eu abro um Websocket servidor, na porta 9090. Quando chega uma mensagem, eu armazeno o UUID recebido. Quando o Cliente abre uma conexão websocket, a primeira coisa que ele faz é enviar o UUID. Assim, eu tenho um vetor de Websockets, cada um com seu UUID, e posso identificar quem enviou e para quem devo enviar a mensagem (embora eu não esteja usando isso nesse exemplo).
Bem, quando o Cliente envia uma mensagem usando o Ajax, o Servidor a re-envia para todos os Clientes (inclusive o autor da mensagem) usando Websockets. Eis o arquivo de rota "index.js" que faz isso:
var WebSocket = require('ws'); var uuid = require('node-uuid'); exports.index = function(req, res){ var identificador = uuid.v1(); res.render('index', { title: 'Msg app', 'msg': mensagem.texto , 'uuid' : identificador}); }; exports.novamsg = function(req, res){ mensagem.texto = req.params.mensagem; console.log('Conexoes: ' + conexoes.length); for (var x=0;x<conexoes.length;x++) { console.log('Enviando msg para uuid: ' + conexoes[x].uuid); conexoes[x].send(mensagem.texto); } res.header("Content-Type", "application/json; charset=utf-8"); res.json({ 'msg' : mensagem.texto }); };
Funciona muito bem! E note que estou misturando comunicação normal com Websocket sem problema algum. O Cliente poderia usar o próprio websocket para enviar mensagem, ou poderia enviar o UUID do Cliente que deveria recebê-la. Mas isso, eu deixo para você fazer.
Nenhum comentário:
Postar um comentário