É... 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