Escolar Documentos
Profissional Documentos
Cultura Documentos
Desenvolvimento de Aplicacoes Com Websockets
Desenvolvimento de Aplicacoes Com Websockets
Orientador:
Frank Siqueira
Florianópolis, 2012/2
1
Rafael Michels Motta
DESENVOLVIMENTO DE APLICACÕES COM WEBSOCKETS
Banca examinadora
___________________________________
___________________________________
2
“Foco é dizer não”
Steve Jobs
3
Dedicatória
4
Resumo
A web está vivendo uma nova era, onde tudo está sendo feito em tempo real.
As expectativas de quão rápido a internet deve entregar as informações mudou
– atrasos de minutos são inaceitáveis. Grandes empresas, como Twitter,
Google e Facebook captaram essa necessidade rapidamente, e atualmente já
entregam seus dados em tempo real, seja em um simples bate-papo, ou
mesmo em notificações de ações em redes sociais. Nesse cenário, este
trabalho vista explorar os conceitos e definições das tecnologias envolvidas
para se criar aplicações Web em tempo real, com foco na API e protocolo de
WebSockets. Por fim, pretende-se ainda implementar um serviço utilizando
esse recurso.
Palavras chaves: Comunicação na web, HTML5, WebSockets.
5
Abstract
The web is living a new era, where everything is done in real time. The
expectations of how fast the Internet must deliver information has changed -
minute delays are unacceptable. Large companies, such as Twitter, Facebook
and Google have captured this need quickly, and currently deliver their data in
real time, whether a simple chat, or even action notifications in social networks.
In this scenario, this work explore concepts and definitions of the technologies
involved to create real-time Web applications, focusing on API, and
WebSockets protocol. Finally, the aim is also to implement a service using this
feature.
Key words: Web communication, HTML5, WebSockets.
6
Sumário
4. HTML5 ........................................................................................................ 35
4.1. História.................................................................................................. 35
7
4.4.2. APIs Javascript .................................................................................. 41
5. WebSockets................................................................................................ 43
6. Projeto ........................................................................................................ 53
7. Conclusões ................................................................................................. 79
8. Referências bibliográficas........................................................................... 81
8
Lista de figuras
9
Figura 31 – Tela da aplicação fictícia
Figura 32 – Tickets
Figura 33 – Compartilhamento
10
Lista de tabelas
11
Lista de siglas
12
1. Introdução
13
modelo HTTP, que permitiu que, de fato, dados fossem enviados sem uma
requisição explicita do servidor.
Plug-ins de terceiros também foram criados, como Flash Sockets. Esses
permitiram que, de fato, fosse utilizada a técnica PUSH, isso é, do servidor web
enviar dados ao cliente. A ressalva era que esses plug-ins tinham que ser
instalados por cada usuário em sua máquina, o que não era garantido,
principalmente para usuários leigos e em grandes corporações.
Tudo mudou em 2008, quando a W3C anunciou um novo padrão da
web, o HTML5. As novidades dessa nova especificação iam desde novas tags
– que traziam melhorias na semântica e acessibilidade – até um conjuntos de
API’s especificadas em Javascript, mudando totalmente o HTML que
estávamos acostumados a lidar. Uma das seções mais interessantes desse
novo padrão foi a parte de comunicação, beneficiando principalmente as
aplicações em tempo real. A API de WebSockets permite agora a comunicação
bidirecional por canais full-duplex através da porta 80 do protocolo TCP,
significando que agora pode ser enviados dados no sentido servidor – cliente e
ainda sem os problemas ligados a performance, que tanto afetavam as
técnicas que usadas em aplicações em tempo real.
Nesse cenário, serão explorados os conceitos e definições das
tecnologias envolvidas para se criar aplicações Web em tempo real, com foco
na API e protocolo de WebSockets.
14
2. Apresentar as principais técnicas para se criar aplicações em tempo
real;
3. Analisar a fundamentação teórica sobre a especificação HTML5, com
foco na API de WebSockets;
4. Levantar os requisitos e ferramentas necessárias para se criar um uma
aplicação de compartilhamento de navegação;
5. Implementar a aplicação a partir da análise feita;
6. Apresentar uma análise dos resultados obtidos com a aplicação.
1.2. Motivação
1.3. Metodologia
15
últimos anos, bem como nas novas tecnologias que estão sendo desenvolvidas
no presente momento. Com o levantamento bibliográfico, é feito um estudo
teórico sobre o tema sobre aplicações em tempo real e todas tecnologias
envolvidas no processo para se criar o serviço proposto:
1. Comunicação na web;
2. HTML5;
3. WebSockets.
Por fim, é feito uma análise sobre o serviço que será desenvolvido utilizando a
API de WebSockets, apresentando posteriormente o desenvolvimento e os
resultados obtidos.
16
2. Desenvolvimento de Aplicações Web
A World Wide Web começou com um programador que teve uma nova ideia
de software no grupo de controle e aquisição de dados da Organização
Européia para pesquisa nuclear – CERN, localizado em Genebra. Segundo
Berners-Lee (1989), o objetivo original do desenvolvimento da web foi tornar
mais fácil o compartilhamento de documentos de pesquisas. Tim Berners-Lee
propôs que a gerência da CERN adotasse um sistema de informação
distribuído baseado em hipertexto, a fim de facilitar o compartilhamento de
conhecimento dentro da instituição. O projeto, que inicialmente foi chamado de
Mesh, convenceu rapidamente todos os gerentes devido a grande perda de
informações que acontecia diariamente no CERN. Após um ano de trabalho
incessante neste projeto, Berners-Lee finalizou seu projeto e o batizou com um
novo nome: World Wide Web. Nesse projeto, ele implementou uma série de
ferramentas que são usadas ainda hoje:
17
“A WorldWideWeb é uma grande hipermídia que tem por objetivo dar acesso
universal a um grande universo de documentos”.
Daquele momento em diante, a web cresceu em um nível exponencial.
Com cinco anos o número de usuários na web já ultrapassava a marca de 40
milhões de usuários (Infonetics Research, 2005). Em um certo momento, o
número de usuários dobrava a cada dois meses. O universo de documentos de
Berners-Lee era realidade, e estava cada dia se expandido mais e mais.
Cada vez a web evoluiu mais, e as páginas web deixaram de serem
apenas páginas estáticas para se comportarem como aplicações. Passou
então a ser uma enorme plataforma, com o desenvolvimento de novas
linguagens e tecnologias. A demanda pelo desenvolvimento de aplicações web
hoje só cresce, e por consequência disso gera uma alta demanda no
desenvolvimento e maturação de tecnologias para apoiarem este processo.
18
Figura 1: Modelo cliente-servidor (Carvalhana, 2004)
2.1.1 URLs
19
• Caminho: Localização do recurso dentro do servidor web
• Recurso: Nome do recurso que se deseja obter dentro do servidor web
A URL deve possuir todos esses componentes, já que ela contém apenas o
caminho absoluto dentro do servidor.
Para exemplificar, a URL http://www.ufsc.br possui os seguintes componentes:
• Protocolo: HTTP
• Servidor: www
• Domínio: ufsc.br
• Porta: Padrão(80)
• Caminho: /
• Recurso: index.php
20
Tabela 1: Tipos MIME
2.1.3 Comunicação
21
Figura 2: Modelo assíncrono (Paiva, 2006)
2.3.1 HTML
2.3.2 XML
2.3.3 CSS
22
2.4 Programação lado cliente
2.5.1 Funcionamento
23
Figura 3: Funcionamento do protocolo HTTP (Pitanga, 2006)
2.5.2 Mensagem
2.5.3 Cabeçalho
24
2.5.4 Corpo da mensagem
2.5.5 Métodos
• GET
• HEAD
• POST
• PUT
• DELETE
• TRACE
• OPTIONS
• CONNECT
Método Descrição
GET O método GET tem a finalidade de solicitar algum recurso do
servidor. Esses recursos podem ser um documento HTML, um
25
script ou uma imagem, por exemplo.
HEAD O método HEAD é uma variação do GET. Ele é usado para se
obter metadados por meio do cabeçalho da resposta do servidor
web, sem a necessidade de recuperar algum recurso.
POST Envia dados para serem processador no servidor, como dados
de um formulário HTML ou mesmo um arquivo a ser enviado
para o servidor.
PUT A entidade enviada na requisição deve ser tratada como uma
versão mais recente do recurso a ser atualizado. Um exemplo
claro é a atualização de um arquivo mantido no servidor.
DELETE O servidor deve apagar o recurso identificado pela URI. Por
exemplo, queremos apagar um arquivo contido no servidor,
basta então realizarmos uma requisição HTTP na URI específica
referenciando o método DELETE.
TRACE Ecoa o pedido ao servidor, de maneira que o cliente possa saber
o que os servidores intermediários estão mudando em seu
pedido.
OPTIONS Recupera os métodos HTTP que o servidor aceita.
CONNECT Serve para uso com um proxy que possa se tornar uma conexão
segura.
26
3. Aplicações em tempo real na Web
27
daquele momento as aplicações web começaram a compartilhar uma série de
características com as aplicações desenvolvidas no desktop. Todo o
processamento da interface passa agora a ser tratado no próprio navegador,
permitindo um número muito menor de requisições ao servidor (Fratelli, 2010).
28
pelo Twitter para notificar quando um novo tweet era efetuado por algum
usuário da rede, esse tipo de notificação ficou muito comum na maioria das
redes sociais. Dessa forma, as aplicações em tempo real começaram a se
popularizar, principalmente devido a esse tipo de aplicação.
29
servidor web, ao invés do tradicional modelo cliente-servidor, que era baseado
na requisição de um cliente.
Durante o passar dos anos uma série de tecnologias foram criadas para
prover esse tipo de aplicações, que envolveu a técnica polling e outras
tecnologias do tipo push, sendo a mais notável o modelo Comet(Paul, 2007).
3.1. Polling
30
Figura 7: Exemplo do uso de Polling
3.2. Comet
3.2.1 Streaming
31
proxy. Logo, mantendo-se uma conexão aberta para ouvir eventos do servidor
poderia acabar por bloquear o navegador de enviar novas requisições
enquanto se esperava pelo resultado de uma requisição feita anteriormente.
Para se contornar isso foi necessário criar um hostname diferente para lidar
com informações em tempo real, que é um alias para o mesmo servidor web.
O grande problema dessa técnica é que por ela ser encapsulada dentro
do protocolo HTTP, firewall e servidores proxy podem intervir e armazenar a
resposta, aumentando o tempo de entrega da resposta. Por isso, muitas
aplicações Comet acabam usando a técnica Long-Polling quando é detectado
que a resposta está sendo armazenada.
3.2.1 Long-Polling
32
Figura 9: Diagrama da técnica Long-Polling (Vital, 2012)
33
Essa técnica acabou reduzindo consideravelmente a latência e o tráfego
de rede, mas ao gerar um volume de dados alto ela acaba por não oferecer
uma melhora de desempenho em relação ao método Polling.
Em todas as técnicas citadas, é usado o protocolo HTTP para se realizar
a comunicação. Entretanto, esse protocolo não foi projetado para lidar com
esse tipo de aplicação. Um dos fatores que contribuem para isso são os
cabeçalhos de resposta, que podem conter uma série de dados que não são
necessários, fazendo com que a latência aumente. Outra coisa que merece
destaque é que para termos uma conexão full-duplex, é necessário mais do
que apenas uma conexão do servidor ao cliente. Para simularmos uma
conexão full-duplex sobre o protocolo HTTP, são usadas duas conexões, o que
representa um aumento significativo em termos de complexidade e sobrecarga
do servidor.
A dificuldade e os vários problemas envolvidos na criação de aplicações
em tempo real estimularam o desenvolvimento de uma API que resolve todos
esses problemas. Em 2008 a W3C anunciou a especificação de um novo
padrão para web, o HTML5. E um dos itens mais esperados era justamente a
API de WebSockets, que vinha com o objetivo de se criar aplicações em tempo
real de forma fácil, rápido e muito mais escalável se comparado com as
técnicas criadas até então.
34
4. HTML5
4.1. História
35
ano de 1997, a W3C publicou a nova especificação do HTML 4.0, onde trouxe
uma série de melhorias, mudando consideravelmente em relação à
especificação anterior. Após dois meses da publicação do HTML, a W3C
publicou o XML, uma linguagem de marcação para criação de documentos em
geral. A W3C via possibilidades muito maiores com o XML, e no mesmo ano
publicou o XHTML, uma reformulação da linguagem HTML, que combinava as
marcações do HTML com as regras de XML. A W3C acreditava que a médio
prazo o XHTML substituiria o HTML, dessa forma a W3C começou a considerar
o HTML uma linguagem morta, alterando seu foco de trabalho para os padrões
XML e XHTML, e deixando o HTML de lado. Mesmo com o órgão que regulava
os padrões da web considerando o HTML como uma linguagem
descontinuada, a maioria da comunidade continuou a servir seus documentos
na web nesse formato, e não em XHTML.
O grande impasse foi formalizado em 2004, em um workshop
organizado pela própria W3C. Nesse workshop, grandes instituições comerciais
estavam presentes, como Opera, Mozilla, Nokia e Apple. Com o objetivo de
apresentar uma visão do futuro da web, o W3C deixou claro que o HTML seria
descontinuado, cedendo lugar ao XHTML. Assim como a maioria da
comunidade, essas empresas ainda acreditavam no HTML, e foi aí que foi
necessário apoiar ou se desligar da W3C, e começar a desenvolver uma nova
especificação do HTML.
Alguns meses depois surgiu o WHATWG, um pequeno grupo de
desenvolvedores de grandes empresas, como Opera e Mozilla, que passaram
a trabalhar no desenvolvimento tecnológico de aplicações de hipertexto para
web. Mesmo trabalhando apenas por uma lista de email, esse grupo atingiu um
nível de maturidade muito grande após 2 anos, e começou a desenvolver uma
nova especificação do HTML. Em paralelo a isso, a W3C continuava a
trabalhar na nova especificação do XHTML, mas sem muito apoio da maioria
das empresas que desenvolviam os navegadores. Com um momento muito
melhor, a WHATWG começou a ganhar atenção, e em outubro de 2006, Tim
Berners-Lee, o fundador do W3C, se viu obrigado a anunciar a volta do
36
envolvimento da W3C na especificação do HTML, trabalhando em conjunto
com o WHATWG.
Dois anos depois, a primeira especificação do HTML5 surgiu, e um ano
após o desenvolvimento do XHTML 2.0 foi parado. Anos após a primeira
especificação do HTML5, ela ainda não está totalmente finalizada, e muitos
acreditam que ela será revista constantemente, sempre decorrente das
necessidades da comunidade.
37
código de cada página web (Clary, 2003). Esse motor é conhecido como
agente de usuário, e é nele que é feita a implementação de recursos propostos
pela W3C.
Motor Browser
Webkit Safari e Chrome
Gecko Firefox e Camino
Trident IE4 – IE9
Presto Opera
Mesmo que alguns recursos sequer possuam uma interface, alguns agentes de
usuários fazem uma implementação por sua conta, e por isso é cada vez mais
comum vermos alguns recursos sendo utilizados com o prefixo do agente de
usuário no inicio. Um exemplo claro é a recente API de full screen, que permite
estender para tela cheia elementos da página web ou a própria página web:
Funcionalidade
Habilitar full screen Desabilitar full screen
Candidato a padrão .requestFullScreen() .cancelFullScreen()
Webkit .webkitRequestFullScren() .webkitCancelFullScreen()
Gecko .mozRequestFullScreen .mozCancelFullScreen()
Esse tipo de recurso ainda é pouco recomendável para se usar, mas mesmo
estando pouco maduro, já é adotado em ambientes de produção. O Facebook,
por exemplo, já adota esse recurso, exibindo as fotos em tela cheia quando o
usuário deseja. Mas por ser um recurso muito novo, ainda é necessário utilizar
uma técnica proprietária (Flash) para usuários que estiverem utilizando o
38
Internet Explorer, Opera ou mesmo uma versão mais antiga do Chrome ou do
Firefox.
1. Compatibilidade;
2. Utilidade e prioridade;
3. Interoperabilidade;
4. Acesso universal;
5. Paradigma sem plug-ins.
4.3.1. Compatibilidade
4.3.2. Utilidade
39
4.3.3. Interoperabilidade
40
• São difíceis de se integrar com o resto do documento HTML, já
que todo código está em uma camada diferente.
1. Core;
2. API’s Javascript.
4.4.1. Core
41
• Áudio e Vídeo: Permite a representação de elementos multimídia, como
áudio e vídeo, da mesma forma que representamos uma imagem;
• Formulários: Cria formulários dos mais variados tipos, principalmente
para melhorar a acessibilidade para dispositivos móveis e pessoas
cegas. Além disso é possível realizar de forma fácil a validação dos
valores no lado do cliente, que antes só era possível através do
Javascript.
• SVG: Permite representar arquivos no formato SVG direto nas páginas
web.
• Workers: Permite criar threads no lado do cliente, isso é, no código
Javascript.
• Storage: Armazena dados no próprio navegador do cliente.
• Eventos server-side: Permite ouvir eventos vindos do servidor web.
• Sockets: Cria um canal de comunicação bidirecional entre cliente e
servidor
42
5. WebSockets
43
Futuramente isso pode mudar, sendo esse hanshake feito através de uma
porta, sem precisar depender mais do tradicional protocolo HTTP.
O uso desse recurso hoje é grande, e podemos ver em uma série de
aplicações que usamos no dia-a-dia. Desde o chat no GMail ou Facebook,
notificações em sua rede social preferida, edição em grupo do Google Docs, e
até jogos online já fazem o uso da API de WebSockets.
5.1. Compatibilidade
Desktop
IE Firefox Chrome Safari Opera
Celular
IOS Opera Mini Opera mobile Android Chrome
Mobile
44
Suporte Sim Não Sim Não Sim
1ª versão a 4.2 11.0 1.0
suportar
Versão atual 4.3 12.0 1.0
5.2.1. URIs
45
Além deste padrão de URI’s, é ainda oferecido um método seguro, semelhante
ao HTTPS. Para fazer o uso, basta alterar o inicio da URI para WSS.
5.2.2. Handshake
46
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: 7cxQRnWs91xJW9TOQLSuvQ==
Origin: http://example.com
5.2.2. Framing
47
Figura 13: Framing (Grego, 2009)
O servidor deve fechar a conexão caso algum dado não esteja mascarado.
Nesse caso, o servidor deve enviar um quadro de fechamento, com código de
status 1002 – que significa um erro no protocolo. Pra não ter problemas de
codificação, todas mensagens devem estar no formato UTF-8.
5.3. Interface
48
Figura 14: Interface da Api WebSockets (Hickson, 2009)
5.3.1. Construtor
49
Quando se conecta em um WebSocket há ainda a possibilidade de enviar um
segundo parâmetro, que pode ser um String ou um array, que contém os
nomes dos sub-protocolos que a aplicação entende e deseja usar para se
comunicar.
5.3.2. Métodos
websocket.send(mensagem);
websocket.close();
50
razão pela qual o
servidor fechou a
conexão
Fechado por erro Boolean Indica se a conexão foi
fechado por um erro ou
não
5.3.3. Eventos
websocket.onOpen = function() {
console.log(‘conexão estabelecida’);
}
websocket.onMessage = function() {
console.log(‘Mensagem’);
}
51
websocket.onClose = function() {
console.log(‘conexão encerrada’);
}
websocket.onError = function() {
console.log(‘mensagem de erro recebida’);
}
52
6. Projeto
6.1.1. Descrição
53
como de ter um administrador online no sistema Observador. Além disso, este
deverá responder positivamente para que a navegação compartilhada seja
iniciada. Quando iniciada, será oferecido um chat para que os usuários se
comuniquem. O administrador ainda terá acesso a informações detalhadas a
respeito do sistema utilizado pelo cliente, tais como: plataforma, navegador,
agente de usuário, IP e geolocalização, a fim de facilitar, por exemplo, um
serviço de suporte. Por questões de segurança e privacidade, o
compartilhamento poderá ser cancelado por qualquer um dos usuários a
qualquer momento.
Requisitos funcionais:
54
• RF05: Geração de um JavaScript dinâmico para cada subdomínio
cadastrado no sistema administrador;
• RF06: Receber chamadas para visualizar a navegação de um
cliente;
• RF07: Criação dinâmica de um botão para o cliente realizar o
compartilhamento de tela;
• RF08: Compartilhamento da navegação;
• RF09: Troca de mensagens no chat criado durante o
compartilhamento;
• RF10: Especificação de informações detalhadas acerca do
sistema, browser e localização do cliente.
Requisitos não-funcionais:
55
6.1.3 Diagrama de casos de uso
1. Registro no sistema:
Tudo começa por este caso de uso. Um administrador de uma página ou
sistema publicado na web deve efetuar um registro no sistema
56
Observador a fim de adicionar a funcionalidade de compartilhamento de
navegação nas páginas que deseja.
2. Login no sistema
Após feito o registro, o administrador deverá efetuar um login junto ao
sistema, informando email e senha. Caso as credenciais estiverem
corretas, ele terá acesso ao sistema e poderá realizar as operações que
deseja.
3. Alterar dados pessoais
Altera dados pessoais da conta, como nome, email e senha.
4. Criação e edição de um domínio e suas configurações
Para adicionar a funcionalidade de compartilhamento de navegação, o
usuário deverá indicar primeiramente qual o domínio desta página. Com
isso, será necessário efetuar mais alguns procedimentos de
configurações para que então seja gerado um código JavaScript, para
ser inserido na página com o domínio que foi indicado. Com isso feito, a
página já estará apta a oferecer a funcionalidade.
5. Compartilhar navegação
Com as configurações feitas pelo administrador, o visitante ao acessar a
página web poderá realizar o compartilhamento de sua navegação a
partir de um botão criado na lateral da página.
6. Visualizar navegação
Quando um usuário realizar a ação identificada pelo caso de uso
número 5(Compartilhar navegação), será exibido ao administrador um
pedido de compartilhamento de navegação. Se o administrador optar por
aceitar o pedido, o compartilhamento da navegação será iniciado.
7. Visualizar informações do visitante
É importante que o administrador tenha acesso a informações
detalhadas sobre o cliente, como plataforma, browser, IP e
geolocalização, de forma precisa e fácil. Estas deverão estar dispostas
na lateral da tela do administrador, abaixo do chat.
8. Troca de mensagens
57
A comunicação entre os usuários também deverá acontecer através de
um chat, localizado na lateral da tela dos usuários.
1. Registro no sistema:
2. Login
58
Figura 17: Diagrama Login
59
4. Criação e edição de um domínio e suas configurações
5. Compartilhar navegação
6. Visualizar navegação
60
Figura 21: Visualizar navegação
8. Troca de mensagens
61
Figura 23: Troca de mensagens
- User:
{ "activated" : { type: Boolean, default: true },
"domains" : [
type: mongoose.SchemaTypes.ObjectId
ref: Domain
],
"createAt" : { type: Date, default: Date.now },
"email" : { type: String, required: true },
"firstName" : { type: String, required: true }
"lastName" : { type: String },
"password" : { type: String, required: true },
"role" : { type: Number, default: 1 , required: true }
}
62
- Domain:
{ "name" : { type: String, required: true },
"url" : { type: String, required: true },
"activated" : { type: Boolean, default: true },
"buttonPosition" : { type: String, default: ‘left’},
"sidebarPosition" : { type: String, default: ‘left’},
"chat" : { type: Boolean, default: true },
"config" : { type: Boolean, default: true }
}
6.2. Desenvolvimento
6.2.1.Ambiente
6.2.2 A Aplicação
63
Para tornar o compartilhamento da navegação, não se pode esquecer
dos requisitos não-funcionais, principalmente os listados abaixo:
• Desempenho;
• Funcionalidade Cross-Browser;
• Livre de plug-ins.
64
encontrado, e ao mesmo tempo bastante fácil de ser implementado. A
maior dificuldade seria realizar o print-screen, mas que foi resolvido
facilmente utilizando-se a biblioteca HTML2Canvas, que basicamente
realiza uma cópia de toda árvore HTML, e a partir desta cria uma
imagem utilizando a API HTML5 Canvas. Com a imagem codificada em
base64, foi necessário realizar o envio ao cliente. Mesmo sendo um
método livre de plug-ins e funcionando em todos os navegadores, o
desempenho deixou muito a desejar, já que o trafego dos de dados
gerado na rede é muito alto. Além disso, todas as operações utilizados
na biblioteca HTML2Canvas consomem muitos recursos
computacionais, ocasionando desta forma problemas de desempenho
na máquina do cliente. Em testes realizados localmente, foi alcançado a
marca de 1 quadro por segundo, número que tende a diminuir mais
ainda se inserido em um ambiente de produção. Desta forma, este
método foi totalmente desconsiderado visando os requisitos não-
funcionais destacados anteriormente.
65
implementada em nenhum browser no momento, o que torna inviável o
desenvolvimento da aplicação usando esta técnica. É importante
destacar que já existe uma extensão do Google Chrome que torna
possível fazer o uso dessa API, entretanto, como foi frisado
anteriormente, queremos que essa aplicação seja cross-browsers e sem
a instalação de qualquer plug-in ou extensão.
66
o Environments
o Routes.js
o Environment.js
• Log
• Node_modules
• Public
o Images
o Javascripts
o Stylesheets
• Server.js
• Package.json
67
§ 404.jade: Renderiza requisicões que não possuem um
mapeamento no sistema
§ 500.jade: Renderiza requisições que o sistema não pode
compreender com sucesso.
o Observer
§ Index.jade: Representa a tela onde o administrador irá
visualizar a navegação de um visitante.
o Tickets
§ Index.jade: Exibe os pedidos de compartilhamento de
navegação feitos pelos visitantes.
o Settings
§ Index.jade: Permite realizar configurações específicas de
compartilhamento de um site.
o Account
§ Index.jade: Permite alterar configurações acerca da conta
do usuário
o Signin
§ Index.jade: Tela para o usuário entrar no sistema.
o Signup
§ Index.jade: Tela para os usuários se registrarem no
sistema.
• Controller
o Os controladores são responsáveis por receber requisições e
intermediar as relações entre o banco de dados e a interface do
usuário.
o Application.js: Provê um conjunto básico de funcionalidades,
principalmente para controle de sessão dos usuários.
o Observer.js: É o principal controlador do sistema. Ele lida com
toda a comunicação entre um visitante e um administrador.
o Tickets.js: Trata os pedidos de compartilhamento da navegação
feita pelos visitantes e exibe seus pedidos na view.
68
o Settings.js: Exibe a tela para o usuário cadastrar / editar algum
domínio.
o Account.js: Serve a tela para o usuário realizar alterações em
sua conta.
o Signin.js: Esse controlador lida com as operações de login e
logout no sistema.
o Signup.js: Trata requisições para o usuário se registrar no
sistema.
6.3. Resultados
69
Figura 24: Criação de uma página fictícia
70
Figura 25: Registro
Após o preenchimento dos dados é enviada uma requisição HTTP via método
POST ao controlador Signup. Este vai verificar se os dados estão corretos e
então criar um novo registro no banco representado pelo schema User. Com o
registro efetuado com sucesso, o usuário será redirecionado a página de login
para realizar a autenticação no sistema.
71
Figura 26: Entrar
A página de login apresenta dois campos(email e senha) para que o usuário
faça autenticação através do controlador Signin.
Com os dados fornecidos corretamente, uma sessão será criada através
do Redis server, um mini-banco avançado para armazenamento de dados no
formato chave-valor. Foi optado fazer o uso do Redis server ao invés do
controle de sessão em memória para otimizar ao máximo o desempenho da
aplicação, consumindo o mínimo de recursos computacionais.
Com uma sessão criada, o administrador terá acesso ao painel
administrativo. O layout básico da aplicação consiste em uma barra superior
com o logo da aplicação e 2 ações: Conta e o botão de sair. Na barra lateral
estão as principais ações do sistema: Tickets e configurações.
72
Figura 27: Layout básico do sistema
73
Figura 28: Conta
74
não o chat e exibir as configurações do cliente. Com o domínio criado, será
exibido no fim da página um bloco de código. É este bloco de código que o
administrador deverá adicionar em sua página para concluir todo o processo.
75
Figura 31: Tela da aplicação fictícia
Para conectar basta indicar o endereço do socket criado. Neste caso, toda
nossa aplicação está rodando localmente a partir da porta 3000. Após ser
estabelecido a conexão é enviado uma mensagem a aplicação Observador.
Uma das vantagens de se utilizar a biblioteca Socket.io é que ela permite a
criação de eventos próprios, tornando a troca de dados entre cliente e servidor
muito mais fácil. Caso estivesse sendo usado a implementação padrão de
WebSockets, a única forma de comunicação entre as aplicações(cliente e
servidor) seria a partir do evento onMessage. Nesse trecho de código acima,
foi criado o evento newTicket, enviando como parâmetros detalhes internos de
configuração da aplicação Observador(como ID), a fim de notificar o pedido de
compartilhamento de navegação para o usuário correto. Com o pedido feito, o
76
administrador deverá ser notificado. A tela tickets exibe todos os pedidos de
compartilhamento de navegação feito pelos visitantes.
77
comunicar. Embaixo, são exibidos detalhes do sistema do cliente. Na direita é
exibida a tela que o visitante está compartilhando.
78
7. Conclusões
79
ponto de se utiliza-la em ambiente de produção, embora, como já dito acima, a
W3C ainda não recomende fazer seu uso, pois a API ainda encontra-se em
estágio de desenvolvimento, podendo ter eventuais alterações em breve.
80
8. Referências bibliográficas
Clairy, Bob. Browser detection and cross browser support. Disponível em <
https://developer.mozilla.org/en-
US/docs/Browser_Detection_and_Cross_Browser_Support>. Acesso em 10 de
outubro de 2012.
81
SYNODINOS, Dio. HTML5 Web Sockets vs. Comet and AJAX. Disponível em
<http://www.infoq.com/news/2008/12/websockets-vs-comet-ajax>. Acesso em
23 de junho de 2012.
82
Apêndice 1– Artigo
rafaelmotta021@inf.ufsc.br
Abstract. The web is living a new era, where everything is done in real time. The
expectations of how fast the Internet must deliver information has changed - minute
delays are unacceptable. Large companies, such as Twitter, Facebook and Google
have captured this need quickly, and currently deliver their data in real time,
whether a simple chat, or even action notifications in social networks. In this
scenario, this work explore concepts and definitions of the technologies involved to
create real-time Web applications, focusing on API, and WebSockets protocol.
Finally, the aim is also to implement a service using this feature.
Key words: Web communication, HTML5, WebSockets.
Resumo. A web está vivendo uma nova era, onde tudo está sendo feito em tempo
real. As expectativas de quão rápido a internet deve entregar as informações
mudou – atrasos de minutos são inaceitáveis. Grandes empresas, como Twitter,
Google e Facebook captaram essa necessidade rapidamente, e atualmente já
entregam seus dados em tempo real, seja em um simples bate-papo, ou mesmo em
notificações de ações em redes sociais. Nesse cenário, este trabalho vista explorar
os conceitos e definições das tecnologias envolvidas para se criar aplicações Web
em tempo real, com foco na API e protocolo de WebSockets. Por fim, pretende-se
ainda implementar um serviço utilizando esse recurso.
Palavras chaves: Comunicação na web, HTML5, WebSockets.
1. Introdução
Por que a Web em tempo real é importante? Vivemos em um mundo em que
tudo é feito em tempo real, então, é natural que a web esteja se movendo nesta direção.
As expectativas de quão rápido a internet deve entregar as informações mudou – atrasos
de minutos são inaceitáveis. Grandes empresas, como Twitter, Google e Facebook
captaram essa necessidade rapidamente, e atualmente já entregam seus dados em tempo
real, seja em um simples bate-papo, ou mesmo em notificações de ações em redes
sociais.
De fato, a web está vivendo uma nova era. Criada originalmente para exibição,
organização e compartilhamento de documentos hipertexto, ela passou por muitas fases
até chegar no estágio que se encontra hoje. Do simples fornecimento de páginas
estáticas, escritas em HTML, surgiu espaço para a criação de linguagens e outros
83
mecanismos que puderam oferecer esse conteúdo de forma dinâmica. Esse modelo, que
hoje é tradicionalmente conhecido como HTTP, é baseado em requisições e respostas:
um cliente faz um pedido de uma página web, o servidor entrega o conteúdo, e nada
mais acontece até que o cliente faça outra requisição. Surge depois o AJAX, técnica que
permitiu que a web torna-se muito mais dinâmica, permitindo que a requisição fosse
feita de forma assíncrona, sem a necessidade de recarregar da página atual por
completo. No entanto, ainda estávamos presos naquele antigo modelo HTTP, não
possuindo um padrão para enviar dados no sentido servidor - cliente.
Uma gama de soluções surgiu para resolver esse problema. A mais básica foi a
técnica chamada Polling, que consistia em fazer requisições em tempos regulares no
servidor web utilizando AJAX, a fim de obter alguma nova informação. De fato ela deu
a percepção aos usuários de que a aplicação era em tempo real, mas outros problemas
acabaram surgindo, geralmente ligados a performance e a sobrecarga dos servidores
web, já que utilizavam o tradicional modelo HTTP, que não tinha sido projetado para
esse tipo de aplicação. A mais notável foi a Comet, um modelo de aplicação baseado no
modelo HTTP, que permitiu que, de fato, dados fossem enviados sem uma requisição
explicita do servidor.
Plug-ins de terceiros também foram criados, como Flash Sockets. Esses
permitiram que, de fato, fosse utilizada a técnica PUSH, isso é, do servidor web enviar
dados ao cliente. A ressalva era que esses plug-ins tinham que ser instalados por cada
usuário em sua máquina, o que não era garantido, principalmente para usuários leigos e
em grandes corporações.
Tudo mudou em 2008, quando a W3C anunciou um novo padrão da web, o
HTML5. As novidades dessa nova especificação iam desde novas tags – que traziam
melhorias na semântica e acessibilidade – até um conjuntos de API’s especificadas em
Javascript, mudando totalmente o HTML que estávamos acostumados a lidar. Uma das
seções mais interessantes desse novo padrão foi a parte de comunicação, beneficiando
principalmente as aplicações em tempo real. A API de WebSockets permite agora a
comunicação bidirecional por canais full-duplex através da porta 80 do protocolo TCP,
significando que agora pode ser enviados dados no sentido servidor – cliente e ainda
sem os problemas ligados a performance, que tanto afetavam as técnicas que usadas em
aplicações em tempo real.
Nesse cenário, serão explorados os conceitos e definições das tecnologias
envolvidas para se criar aplicações Web em tempo real, com foco na API e protocolo de
WebSockets.
84
nome: World Wide Web. Nesse projeto, ele implementou uma série de ferramentas que
são usadas ainda hoje:
“A WorldWideWeb é uma grande hipermídia que tem por objetivo dar acesso universal
a um grande universo de documentos”.
Daquele momento em diante, a web cresceu em um nível exponencial. Com
cinco anos o número de usuários na web já ultrapassava a marca de 40 milhões de
usuários (Infonetics Research, 2005). Em um certo momento, o número de usuários
dobrava a cada dois meses. O universo de documentos de Berners-Lee era realidade, e
estava cada dia se expandido mais e mais.
Cada vez a web evoluiu mais, e as páginas web deixaram de serem apenas
páginas estáticas para se comportarem como aplicações. Passou então a ser uma enorme
plataforma, com o desenvolvimento de novas linguagens e tecnologias. A demanda pelo
desenvolvimento de aplicações web hoje só cresce, e por consequência disso gera uma
alta demanda no desenvolvimento e maturação de tecnologias para apoiarem este
processo.
85
Mesmo com estas soluções bastante rudimentares, a web começou a ficar cada
vez mais interativa. Com o surgimento do AJAX - técnica que tornou possível realizar
um pedido ao servidor de forma assíncrona, utilizando-se Javascript - a web sofreu uma
guinada e passou a ser extremamente dinâmica. A sigla RIA – Rich Internet
Applications – começou a se popularizar, e a partir daquele momento as aplicações web
começaram a compartilhar uma série de características com as aplicações desenvolvidas
no desktop. Todo o processamento da interface passa agora a ser tratado no próprio
navegador, permitindo um número muito menor de requisições ao servidor (Fratelli,
2010).
Em 2004 uma segunda geração de serviços e comunidades na web começou a
surgir, tendo como conceito a web como uma plataforma. Essa geração de aplicações
foi apelidada de Web 2.0, pela empresa norte-americana O’Reilly. Serviços como redes
sociais, páginas de distribuição de vídeos, wikis e blogs começaram a se popularizar, e
possuíam como característica comum a participação efetiva dos usuários no trafego de
informações(O’Reilly, 2005).
Com uma massa de dados cada vez maior e as aplicações web cada vez mais
complexas, devido ao surgimento e aprimoramento de tecnologias, uma demanda pela
entrega de informações de forma instantânea começou a surgir, principalmente em
aplicações da web 2.0. Implementado inicialmente pelo Twitter para notificar quando
um novo tweet era efetuado por algum usuário da rede, esse tipo de notificação ficou
muito comum na maioria das redes sociais. Dessa forma, as aplicações em tempo real
começaram a se popularizar, principalmente devido a esse tipo de aplicação.
As aplicações web em tempo real se referem a uma série de tecnologias e
práticas que permitem aos usuários receberem notificações assim que os dados são
publicados por seus autores, ao invés de realizar requisições em tempos periódicos ao
servidor para verificar se há algum dado novo. Esse classe de aplicação também é
conhecida como aplicações de tecnologia push, já que se baseiam em uma comunicação
onde a requisição é iniciada pelo servidor web, ao invés do tradicional modelo cliente-
servidor, que era baseado na requisição de um cliente.
Durante o passar dos anos uma série de tecnologias foram criadas para prover
esse tipo de aplicações, que envolveu a técnica polling e outras tecnologias do tipo
push, sendo a mais notável o modelo Comet(Paul, 2007).
4. HTML5
No início de 2008, a W3C anunciou a primeira especificação do HTML5. Essa
nova especificação inclui grandes alterações, como:
• Novas tags para melhoria da semântica;
• Controle embutido de conteúdo multimídia;
• Melhoria na depuração de erros;
• API’s Javascript.
Esse último, em especial, contém realmente as grandes novidades da nova
especificação, e dentro dela há uma seção de comunicação que envolve a API de
WebSockets, criada para lidar com aplicações em tempo real.
86
5. WebSockets
Historicamente, desenvolver aplicações web que precisem de comunicação
bidirecional entre cliente e servidor, era significado de um abuso do protocolo HTTP,
devido às incessantes requisições ao servidor web para verificar se havia algum dado
novo, além da criação de duas conexões TCP, uma para enviar mensagens ao cliente e
outra para receber mensagens(Greco, 2012).
Uma simples solução para esse problema foi usar uma única conexão TCP para
trafegar entre ambas direções. É isso basicamente que a especificação WebSockets
define. Ela não é uma simples melhora na comunicação do tradicional protocolo HTTP.
Ele representa uma revolução na comunicação da web, especialmente para aplicações
em tempo real e aplicações baseadas em eventos server-side, também conhecidas como
aplicações de classe push.
A especificação dessa API descreve um novo protocolo para trafegar os dados
de maneira bidirecional, entre cliente e servidor, operando em um socket único através
da porta 80 (no caso do protocolo HTTP) ou da porta 443 (protocolo HTTPS). Além
disso, foi especificada uma interface JavaScript para de fato usar esse protocolo e ouvir
os eventos disparados pelo servidor. Com esse novo protocolo, problemas ocorridos nas
técnicas de Comet e Polling, como o grande trafego de rede e a alta latência foram
totalmente resolvidos. E com a interface JavaScript, agora é possível criar aplicações
desse tipo muito mais facilmente, reconhecendo também uma série de eventos
padronizados pelo servidor web.
O protocolo WebSocket foi projetado para substituir todas as outras tecnologias
existentes de comunicação bidirecional na web que usam o protocolo HTTP como
transporte, principalmente para beneficiar a infraestrutura existente. Hoje o protocolo
WebSocket ainda é dependente do HTTP, já que para usar o protocolo WebSocket
deve-se obrigatoriamente realizar um handshake entre cliente e servidor utilizando o
protocolo HTTP. Futuramente isso pode mudar, sendo esse hanshake feito através de
uma porta, sem precisar depender mais do tradicional protocolo HTTP.
O uso desse recurso hoje é grande, e podemos ver em uma série de aplicações
que usamos no dia-a-dia. Desde o chat no GMail ou Facebook, notificações em sua rede
social preferida, edição em grupo do Google Docs, e até jogos online já fazem o uso da
API de WebSockets.
6. Projeto
Este trabalho vista desenvolver um sistema de navegação compartilhada, onde
um usuário irá compartilhar o conteúdo de uma janela de seu browser com outro
usuário. A solução será livre de plug-ins e registros por parte do visitante, sendo útil
principalmente para os administradores de sistema auxiliarem os visitantes no uso do
sistema, bem como para reportar bugs de forma fácil e rápida.
O grande diferencial do sistema é que ele poderá ser integrado a qualquer
aplicação publicada na web, independentemente da linguagem utilizada. Um exemplo
claro desse tipo de aplicação é o Google Analytics – onde basta inserir um código
JavaScript em sua página e você terá acesso a informações relevantes sobre os acessos
em sua página.
Seguindo estes moldes, será desenvolvida uma aplicação à parte, chamada
Observador. Nesta aplicação, administradores de páginas ou sistemas publicados na
web, com o interesse de observar a navegação de seus visitantes, irão efetuar um
87
registro e seguir alguns procedimentos simples para adicionar esta funcionalidade. Sem
a necessidade de realizar qualquer alteração na página ou sistema atual, o Observador
não irá trazer qualquer impactos quanto a performance, já que ele estará rodando
separadamente em uma infraestrutura dedicada.
Com os procedimentos realizados na página, quando um visitante acessar a
página em questão, será criado de forma dinâmica um botão oferecendo ao usuário a
capacidade de compartilhar sua navegação com o administrador.
Para que a navegação seja compartilhada será necessário que o visitante clique
por vontade própria no botão “Compartilhar navegação”, bem como de ter um
administrador online no sistema Observador. Além disso, este deverá responder
positivamente para que a navegação compartilhada seja iniciada. Quando iniciada, será
oferecido um chat para que os usuários se comuniquem. O administrador ainda terá
acesso a informações detalhadas a respeito do sistema utilizado pelo cliente, tais como:
plataforma, navegador, agente de usuário, IP e geolocalização, a fim de facilitar, por
exemplo, um serviço de suporte. Por questões de segurança e privacidade, o
compartilhamento poderá ser cancelado por qualquer um dos usuários a qualquer
momento.
7. Conclusões
A finalidade desse trabalho foi realizar um estudo sobre a tecnologia de
comunicação em tempo real WebSockets, além de desenvolver uma aplicação para
observar a navegação de usuários utilizando essa tecnologia. O que mais dificultou a
execução da aplicação foi o desempenho, já que o volume de dados trafegado era
bastante grande, e não foi possível realizar grandes otimizações com os recursos
oferecidos atualmente sem realizar o uso de extensões ou plug-ins. Entretanto, de uma
forma geral, todos os objetivos foram concluídos com sucesso.
8. Referências
LUBBERS , Peter. Pro HTML5 Programming. Segunda edição. Cidade desconhecida.
Apress, 2011.
88
Clairy, Bob. Browser detection and cross browser support. Disponível em <
https://developer.mozilla.org/en-
US/docs/Browser_Detection_and_Cross_Browser_Support>. Acesso em 10 de outubro
de 2012.
SYNODINOS, Dio. HTML5 Web Sockets vs. Comet and AJAX. Disponível em
<http://www.infoq.com/news/2008/12/websockets-vs-comet-ajax>. Acesso em 23 de
junho de 2012.
LUBBERS, Petters. How HTML5 WebSockets Interact With Proxy Servers. Disponível
em <http://www.infoq.com/articles/Web-Sockets-Proxy-Servers>. Acesso em 23 de
junho de 2012.
GRECO, Frank. HTML5 WebSockets: A quantum Leap in Scalability for the Web.
<Disponível em http://www.websocket.org/quantum.html>. Acesso em 23 de junho de
2012.
89
Apêndice 2 – Código fonte
Controllers
Account_Controller.coffee
load 'application'
before use 'isUserLogged'
User.findByIdAndUpdate(request.session.user._id, { firstName:
request.body.firstName, lastName: request.body.lastName, email:
request.body.email }, (err, user) ->
if not err
request.session.user = user
flash 'info', 'Usuário atualizado com sucesso'
return redirect path_to.account()
)
if request.body.currentPassword
if request.body.currentPassword isnt request.session.user.password
flash 'error', 'Senha atual não corresponde a digitada'
return redirect path_to.account()
else
if request.body.newPassword isnt request.body.password
flash 'error', 'Senhas não conferem'
return redirect path_to.account()
else
if request.body.password.length < 2
flash 'error', 'Senha deve ter pelo menos 2 carácteres'
90
return redirect path_to.account()
Application_controller.coffee
before 'protect from forgery', ->
protectFromForgery '09fcdda644a6978e42d5698679188aa9d67c76d5'
observer_controller.coffee
load 'application'
before use 'isUserLogged'
layout(false)
render title: t('observer.title')
91
signin_controller.coffee
load 'application'
if request.session.user
return redirect path_to.dashboard()
layout false
render title: t('signin.title')
92
<%- flash %>
</div>
<% }; %>
<% info = request.flash('info').pop(); if (info) { %>
<div class="alert alert-info">
<a class="close" data-dismiss="alert">×</a>
<%- info %>
</div>
<% }; %>
<fieldset>
<legend>
<%= t('account.personalData') %>
</legend>
<div class="control-group">
<label class="control-label"> <%= t('account.firstName')
%></label>
<div class="controls">
<input type="text" class="input-large" name="firstName"
placeholder=" <%= t('account.firstName') %>" value="<%=
request.session.user.firstName %>">
</div>
</div>
<div class="control-group">
<label class="control-label"><%= t('account.lastName') %></label>
<div class="controls">
<input type="text" class="input-large" name="lastName"
placeholder="<%= t('account.lastName') %>" value="<%=
request.session.user.lastName %>">
</div>
</div>
</fieldset>
<fieldset id="email">
<legend>
<%= t('account.email') %>
93
</legend>
<div class="control-group">
<label class="control-label"><%= t('account.emailAddress')
%></label>
<div class="controls">
<input type="email" class="input-large" name="email"
placeholder="<%= t('account.emailAddress') %>" value="<%=
request.session.user.email %>">
</div>
</div>
</fieldset>
<fieldset>
<legend>
<%= t('account.resetPassword') %>
</legend>
<div class="control-group password-check">
<label for="password_old_1" class="control-label"><%=
t('account.currentPassword') %></label>
<div class="controls">
<input class="input-large" name="currentPassword" size="16"
type="password" id="current-password" placeholder="<%=
t('account.currentPassword') %>">
</div>
</div>
<div class="control-group">
<label for="password_new_1" class="control-label"><%=
t('account.newPassword') %></label>
<div class="controls">
<input class="input-large" name="newPassword" size="16"
type="password" id="new-password-1" placeholder="<%=
t('account.newPassword') %>">
</div>
</div>
<div class="control-group">
94
<label for="password_new_2" class="control-label"><%=
t('account.confirmPassword') %></label>
<div class="controls">
<input class="input-large" name="password" size="16"
type="password" id="new-password-2" placeholder="<%=
t('account.confirmPassword') %>">
</div>
</div>
</fieldset>
<fieldset id="support">
<div class="form-actions">
<button class="btn btn-primary" type="submit">
<%= t('account.save') %>
</button>
</div>
</fieldset>
<% });%>
</div>
</div>
</article>
Dashboard/index.ejs
<div id="dashboard-page">
<div class="row-fluid">
<div class="span12 center" style="text-align: center;">
<ul class="stat-boxes">
<li>
<div class="right">
<strong>36094</strong>
Visitas
</div>
</li>
<li>
<div class="left peity_bar_neutral">
95
<span>20,15,18,14,10,9,9,9</span>0%
</div>
<div class="right">
<strong>1433</strong>
Usuários ativos
</div>
</li>
<li>
<div class="left peity_bar_bad">
<span>3,5,9,7,12,20,10</span>-50%
</div>
<div class="right">
<strong>8650</strong>
Orders
</div>
</li>
<li>
<div class="left peity_line_good">
<span>12,6,9,23,14,10,17</span>+70%
</div>
<div class="right">
<strong>8650</strong>
Orders
</div>
</li>
</ul>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<div class="widget-box">
<div class="widget-title">
<span class="icon"><i class="icon-signal"></i></span><h5>Site
Statistics</h5>
96
<div class="buttons">
<a href="#" class="btn btn-mini"><i class="icon-refresh"></i>
Update stats</a>
</div>
</div>
<div class="widget-content">
<div class="row-fluid">
<div class="span4">
<ul class="site-stats">
<li>
<i class="icon-
user"></i><strong>1433</strong><small>Total Users</small>
</li>
<li>
<i class="icon-arrow-
right"></i><strong>16</strong><small>New Users (last week)</small>
</li>
<li class="divider"></li>
<li>
<i class="icon-shopping-
cart"></i><strong>259</strong><small>Total Shop Items</small>
</li>
<li>
<i class="icon-
tag"></i><strong>8650</strong><small>Total Orders</small>
</li>
<li>
<i class="icon-
repeat"></i><strong>29</strong><small>Pending Orders</small>
</li>
</ul>
</div>
<div class="span8">
<div class="chart"></div>
97
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Application/index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<title><%= title %></title>
<%- stylesheet_link_tag('bootstrap', 'bootstrap-responsive', 'unicorn',
'unicorn.grey', 'style') %>
<%- csrf_meta_tag() %>
</head>
<body>
<div id="header">
<h1><a href="./dashboard.html">Unicorn Admin</a></h1>
</div>
<div id="user-nav" class="navbar navbar-inverse">
<ul class="nav btn-group">
<li class="btn btn-inverse" >
<a title="" href="/account"><i class="icon icon-user"></i> <span
class="text"><%= request.session.user.firstName %></span></a>
</li>
<li class="btn btn-inverse">
<a title="" href="/logout"><i class="icon icon-share-alt"></i> <span
class="text"><%= t('logout') %></span></a>
</li>
</ul>
</div>
<div id="sidebar">
<ul>
98
<li>
<a href="/dashboard"><i class="icon icon-home"></i><span><%=
t('dashboard.title') %></span></a>
</li>
<li>
<a href="/tickets"><i class="icon icon-inbox"></i> <span><%=
t('tickets.title') %></span></a>
</li>
<li>
<a href="/settings"><i class="icon icon-inbox"></i> <span><%=
t('settings.title') %></span></a>
</li>
</ul>
</div>
<div id="content">
<div id="content-header">
<h1><%= t(request.res.info.controller + ".title") %></h1>
</div>
<div class="container-fluid">
<%- body %>
</div>
</div>
</body>
</html>
Observer/index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<title><%= title %></title>
<%- stylesheet_link_tag('bootstrap', 'bootstrap-responsive', 'style') %>
<script src="http://localhost:8888/socket.io/socket.io.js"></script>
99
<%-
javascript_include_tag('http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.
min.js', 'bootstrap', 'rails', 'application') %>
<%- csrf_meta_tag() %>
</head>
<body>
<article id="observer-page">
<div></div>
<aside>
<dl class="dl-horizontal">
<dt>Description lists</dt>
<dd>A description list is perfect for defining terms.</dd>
<dt>Euismod</dt>
<dd>Vestibulum id ligula porta felis euismod semper eget lacinia
odio sem nec elit.</dd>
<dd>Donec id elit non mi porta gravida at eget metus.</dd>
<dt>Malesuada porta</dt>
<dd>Etiam porta sem malesuada magna mollis euismod.</dd>
<dt>Felis euismod semper eget lacinia</dt>
<dd>Fusce dapibus, tellus ac cursus commodo, tortor mauris
condimentum nibh, ut fermentum massa justo sit amet risus.</dd>
</dl>
</aside>
</article>
</body>
</html>
Settings/index.ejs
<article id="settings-page">
<div class="row-fluid">
<div class="span12">
<div class="widget-box">
<div class="widget-title">
<span class="icon"> <i class="icon-th"></i> </span>
<h5>Configurações </h5>
100
</div>
<div class="widget-content nopadding">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Nome</th>
<th>URL</th>
<th>Ativo</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
<tr>
<td> Teste TCC </td>
<td> localhost:8000 </td>
<td><i class="icon-ok"></i></td>
<td><a href="javascript:void(0);" class="icon-pencil"
rel="tooltip" data-original-title="Aceitar"></a><a href="javascript:void(0);"
class="icon-remove" rel="tooltip" data-original-title="Aceitar"></a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</article>
Settings/show.ejs
<article id="settings-page">
<div class="row-fluid">
<div class="span9">
<% form_for(account, {action: path_to.account(), method: 'POST',
class:'form-horizontal'}, function (form) { %>
101
<% flash = request.flash('error').pop(); if (flash) { %>
<div class="alert alert-error">
<a class="close" data-dismiss="alert">×</a>
<%- flash %>
</div>
<% }; %>
<% info = request.flash('info').pop(); if (info) { %>
<div class="alert alert-info">
<a class="close" data-dismiss="alert">×</a>
<%- info %>
</div>
<% }; %>
<fieldset>
<legend>
Dados básicos
</legend>
<div class="control-group">
<label class="control-label">Nome</label>
<div class="controls">
<input type="text" class="input-large" name="firstName"
value="Teste TCC">
</div>
</div>
<div class="control-group">
<label class="control-label">Dominio</label>
<div class="controls">
<input type="text" class="input-large" name="firstName"
value="localhost:8000">
</div>
</div>
<div class="control-group">
<label class="control-label">Ativo</label>
<div class="controls">
102
<label class="checkbox"><input type="checkbox"
checked=""></label>
</div>
</div>
<br />
<div class="control-group">
<label class="control-label">Exibir</label>
<div class="controls">
<label class="checkbox"><input type="checkbox"
value="">Exibir chat </label>
<label class="checkbox"><input type="checkbox"
value="">Exibir configurações do cliente </label>
</div>
</div>
<div class="control-group">
<label class="control-label">Posicão sidebar</label>
<div class="controls">
<select>
<option>Lateral esquerda</option>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label">Posicão do botão de
compartilhamento</label>
<div class="controls">
<select>
<option>Lateral direita</option>
</select>
</div>
</div>
<br />
<legend>Código</legend>
<div class="control-group" style="border-bottom: none;">
103
<pre>
<script type="text/javascript">
(function() {
var po = document.createElement("script"); po.type = "text/javascript";
po.async = true;
po.src = "https://localhost:3000/observer.js";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(po, s);
})();
</script>
</pre>
Cole esse código abaixo em seu site para adicionar a
funcionalidade
</div>
<Br />
</fieldset>
<fieldset id="support">
<div class="form-actions">
<button class="btn btn-primary" type="submit">
<%= t('account.save') %>
</button>
</div>
</fieldset>
<% });%>
</div>
</div>
</article>
Signin/index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<title><%= title %></title>
<%- stylesheet_link_tag('bootstrap', 'bootstrap-responsive', 'style') %>
104
<%- csrf_meta_tag() %>
</head>
<body>
<article id="login-page">
<h1><%= t('company.name') %></h1>
<section id="login-box">
<h1><%= t('signin.title') %></h1>
<% form_for(signin, {action: path_to.signin(), method: 'POST'},
function (form) { %>
<% flash = request.flash('error').pop(); if (flash) { %>
<div class="alert alert-error">
<a class="close" data-dismiss="alert">×</a>
<%- flash %>
</div>
<% }; %>
<ul class="unstyled">
<li class="field row-fluid">
<input name="email" class="span12"
value="rafaelmotta021@gmail.com" placeholder="<%= t('signin.email') %>"
type="email" required="required">
</li>
<li class="field row-fluid">
<input name="password" class="span12" value="123456"
placeholder="<%= t('signin.password') %>" type="password"
required="required">
</li>
<li class="actions">
<button class="btn btn-primary btn-block btn-large"
type="submit">
<%= t('signin.title') %>
</button>
</li>
</ul>
<% });%>
105
</section>
<div id="login-link">
<%= t('signin.forgotPass') %>?
<a href="/forgot_password?method=login" class="utility"><%=
t('signin.resetClickHere') %></a>
</div>
</article>
</body>
</html>
Tickets/index.ejs
<article id="tickets-page">
<div class="row-fluid">
<div class="span12">
<div class="widget-box">
<div class="widget-title">
<span class="icon"> <i class="icon-th"></i> </span>
<h5>Static table</h5>
</div>
<div class="widget-content nopadding">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>IP</th>
<th>Inicio</th>
<th>Duração</th>
<th>Localização</th>
<th>Browser</th>
<th>Dispositivos</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
<tr>
106
<td><%= ip %></td>
<td><%= begin %></td>
<td><%= duration %></td>
<td><%= location %></td>
<td><%= browser %></td>
<td><%= device %></td>
<td>
<a href="javascript:void(0);" class="icon-ok" rel="tooltip"
data-original-title="Aceitar"></a>
<a href="javascript:void(0);" class="icon-remove"
rel="tooltip" data-original-title="Aceitar"></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</article>
Config
Development.coffee
app.configure 'development', ->
app.enable 'log actions'
app.enable 'env info'
app.disable 'view cache'
app.disable 'model cache'
app.disable 'eval cache'
app.use require('express').errorHandler dumpExceptions: true, showStack:
true
production.coffee
app.configure 'production', ->
107
app.enable 'view cache'
app.enable 'model cache'
app.enable 'eval cache'
app.enable 'merge javascripts'
app.enable 'merge stylesheets'
app.disable 'assets timestamps'
app.use require('express').errorHandler()
app.enable 'quiet'
test.coffee
app.configure 'test', ->
app.use require('express').errorHandler dumpExceptions: true, showStack:
true
app.settings.quiet = true
app.enable 'view cache'
app.enable 'model cache'
app.enable 'eval cache'
en.yml
en:
logout: "Sair"
account:
title: "Conta"
personalData: "Dados pessoais"
firstName: "Primero nome"
lastName: "Último nome"
email: "Email"
emailAddress: "Endereço de email"
resetPassword: "Reseta sua senha"
currentPassword: "Senha atual"
newPassword: "Nova senha"
confirmPassword: "Confirme a nova senha"
108
save: "Salvar"
company:
name: "Observador"
signin:
title: "Entrar"
email: "Email"
password: "Senha"
forgotPass: "Esqueceu sua senha"
resetClickHere: "Clique aqui para resetar"
dashboard:
title: "Dashboard"
tickets:
title: "Tickets"
settings:
title: "Configurações"
observer:
title: "Observar"
database.yml
development:
driver: "mysql"
host: "localhost"
port: "3306"
driver: "mysql"
database: "observer"
username: "root"
password: ""
109
test:
driver: "memory"
environment.coffee
express = require 'express'
app.configure ->
cwd = process.cwd()
routes.coffee
exports.routes = (map)->
map.resources 'observer'
map.resources 'account'
map.resources 'settings'
map.resources 'tickets'
map.resources 'dashboard'
map.resources 'signin'
map.get '/logout', 'signin#logout'
DB
Schema.coffee
110
customSchema =>
111
Company = db.model 'Company', companySchema
module.exports['User'] = User
module.exports['Company'] = Company
if not module.parent
port = process.env.PORT or 3000
app.listen port
console.log "Node server listening on port %d within %s environment", port,
app.settings.env
client.js
#!/usr/bin/env node
112
};
wsServer.on('connection', function(ws) {
ws.on('close', function() {
113
console.log((new Date()) + " Peer " + this.id + " disconnected.");
delete connections[this.id];
});
});
screencapture.js
(function(exports) {
function urlsToAbsolute(nodeList) {
if (!nodeList.length) {
return [];
}
114
if (!attr) {
return;
}
115
//urlsToAbsolute(document.scripts);
// 4.5. When the screenshot loads (e.g. as ablob URL, as iframe.src, etc.),
// scroll it to the same location of this page. Do this by appending a
// window.onDOMContentLoaded listener which pulls out the saved scrollX/Y
// state from the DOM.
var script = document.createElement('script');
script.textContent = '(' + addOnPageLoad_.toString() + ')();'; // self calling.
screenshot.querySelector('body').appendChild(script);
116
return blob;
}
// NOTE: Not to be invoked directly. When the screenshot loads, it should scroll
// to the same x,y location of this page.
function addOnPageLoad_() {
window.addEventListener('DOMContentLoaded', function(e) {
var scrollX = document.documentElement.dataset.scrollX || 0;
var scrollY = document.documentElement.dataset.scrollY || 0;
window.scrollTo(scrollX, scrollY);
});
}
exports.screenshotPage = screenshotPage;
})(window);
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
117
<script
src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script>
window.jQuery || document.write('<script
src="js/jquery.min.js"><\/script>')
</script>
<script src="/public/javascripts/bootstrap.min.js"></script>
</head>
<body>
<div id="content">
<section>
<div id="frames-container"></div>
</section>
<aside>
<ul class="unstyled">
<li>
<b>Admin</b>
<p>Olá</p>
</li>
<textarea placeholder="Escreva alguma mensagem"></textarea>
</ul>
<dl class="dl-horizontal">
<dt>IP:</dt>
<dd>192.168.1.100</dd>
<hr />
<dt>Localização:</dt>
<dd>Florianópolis - BR</dd>
<hr />
<dt>Dispositivo:</dt>
<dd>IOS - iPad</dd>
<hr />
<dt>Browser:</dt>
<dd>Safari 5</dd>
118
<hr />
<dt>Agente:</dt>
<dd>WebKit</dd>
</dl>
</aside>
</div>
ws.onmessage = function(e) {
// Shouldn't have to create a new blob, but e.data isn't preserving type.
var blob = new Blob([e.data], {type: 'text/html'});
function renderFrame() {
if (framesContainer.children.length) {
var frame = framesContainer.children[currentFrameIdx];
119
if(!frame) {
return;
}
if (currentFrameIdx > 0) {
var prevFrame = frame.previousElementSibling;
prevFrame.hidden = true;
window.URL.revokeObjectURL(prevFrame.src);
}
frame.hidden = false;
currentFrameIdx++;
}
}
function playback(interval) {
clearInterval(playbackIntervalId);
if (!framesContainer.children.length) {
return;
}
var i = 0;
playbackIntervalId = setInterval(function() {
var iframe = framesContainer.children[i];
if (i > 0) {
framesContainer.children[i - 1].hidden = true;
} else if (i == 0) {
framesContainer.children[framesContainer.children.length - 1].hidden =
true;
}
iframe.hidden = false;
120
i++;
i %= framesContainer.children.length;
}, interval);
}
//setInterval(renderFrame, 1000);
</script>
</body>
</html>
app.js
window.URL = window.URL || window.webkitURL;
function connect() {
ws = new WebSocket('ws://' + WS_HOST, ['dumby-protocol']);
ws.binaryType = 'blob';
ws.onopen = function(e) {
console.log('WebSocket connection OPEN');
};
ws.onclose = function(e) {
console.log('WebSocket connection CLOSED');
};
ws.onerror = function(e) {
console.log('WebSocket connection ERROR', e);
};
return ws;
121
}
function send() {
setInterval(function() {
if (ws.bufferedAmount == 0) {
ws.send(screenshotPage());
}
}, REFRESH_EVERY);
}
cliente_app.js
var framesContainer = document.querySelector('#frames-container');
var currentFrameIdx = 0;
var playbackIntervalId = null;
var ws = connect();
ws.onmessage = function(e) {
// Shouldn't have to create a new blob, but e.data isn't preserving type.
var blob = new Blob([e.data], {type: 'text/html'});
function renderFrame() {
if (framesContainer.children.length) {
var frame = framesContainer.children[currentFrameIdx];
if(!frame) {
122
return;
}
if (currentFrameIdx > 0) {
var prevFrame = frame.previousElementSibling;
prevFrame.hidden = true;
window.URL.revokeObjectURL(prevFrame.src);
}
frame.hidden = false;
currentFrameIdx++;
}
}
function playback(interval) {
clearInterval(playbackIntervalId);
if (!framesContainer.children.length) {
return;
}
var i = 0;
playbackIntervalId = setInterval(function() {
var iframe = framesContainer.children[i];
if (i > 0) {
framesContainer.children[i - 1].hidden = true;
} else if (i == 0) {
framesContainer.children[framesContainer.children.length - 1].hidden =
true;
}
iframe.hidden = false;
i++;
123
i %= framesContainer.children.length;
}, interval);
}
124