Introdução
Com WebSockets e EventSource, o HTML5 permite que os desenvolvedores criem apps da Web que se comuniquem em tempo real com um servidor. O Stream Congress (disponível na Chrome Web Store) oferece atualizações ao vivo sobre o funcionamento do Congresso dos Estados Unidos. Ele transmite atualizações do plenário da Câmara e do Senado, notícias relevantes, tweets de membros do Congresso e outras atualizações de mídias sociais. O app precisa ficar aberto o dia todo, já que registra as atividades do Congresso.
Começar com WebSockets
A especificação WebSockets recebeu bastante atenção pelo que ela possibilita: um soquete TCP estável e bidirecional entre o navegador e o servidor. Não há um formato de dados imposto no soquete TCP. O desenvolvedor é livre para definir um protocolo de mensagens. Na prática, transmitir objetos JSON como strings é mais conveniente. O código JavaScript do lado do cliente para detectar atualizações em tempo real é limpo e simples:
var liveSocket = new WebSocket("ws://streamcongress.com:8080/live");
liveSocket.onmessage = function (payload) {
addToStream(JSON.parse(payload.data).reverse());
};
Embora o suporte a WebSockets no navegador seja simples, o suporte do servidor ainda está em fase de formação. O Socket.IO no Node.js oferece uma das soluções mais maduras e robustas do lado do servidor. Um servidor orientado a eventos, como o Node.js, é a opção certa para WebSockets. Para implementações alternativas, os desenvolvedores do Python podem usar Twisted e Tornado, enquanto os desenvolvedores do Ruby têm EventMachine.
Introdução ao Cramp
Cramp é um framework da Web do Ruby assíncrono que é executado sobre o EventMachine. Ele foi escrito por Pratik Naik, um membro da equipe principal do Ruby on Rails. Fornecendo uma linguagem específica de domínio (DSL) para aplicativos da web em tempo real, o Cramp é a escolha ideal para os desenvolvedores web Ruby. Quem já escreveu controladores no Ruby on Rails vai reconhecer o estilo do Cramp:
require "rubygems"
require "bundler"
Bundler.require
require 'cramp'
require 'http_router'
require 'active_support/json'
require 'thin'
Cramp::Websocket.backend = :thin
class LiveSocket < Cramp::Websocket
periodic_timer :check_activities, :every => 15
def check_activities
@latest_activity ||= nil
new_activities = find_activities_since(@latest_activity)
@latest_activity = new_activities.first unless new_activities.empty?
render new_activities.to_json
end
end
routes = HttpRouter.new do
add('/live').to(LiveSocket)
end
run routes
Como o Cramp fica acima do EventMachine não bloqueado, há várias considerações a serem consideradas:
- É preciso usar drivers de banco de dados sem bloqueio, como MySQLPlus e em-mongo.
- É preciso usar servidores da Web orientados por eventos. Há suporte integrado para Thin e Rainbows.
- O app Cramp precisa ser executado separadamente do app Rails principal que alimenta o Stream Congress, reiniciado e monitorado de forma independente.
Limitações atuais
Os WebSockets sofreram um revés em 8 de dezembro de 2010, quando uma vulnerabilidade de segurança foi divulgada. O Firefox e o Opera removeram o suporte a WebSockets do navegador. Embora não exista um polyfill JavaScript puro, há um fallback do Flash que foi amplamente adotado. No entanto, confiar no Flash está longe de ser ideal. Embora o Chrome e o Safari continuem oferecendo suporte a WebSockets, ficou claro que, para oferecer suporte a todos os navegadores modernos sem depender do Flash, os WebSockets precisariam ser substituídos.
Como reverter para a pesquisa AJAX
Foi tomada a decisão de deixar de usar WebSockets e voltar para a pesquisa AJAX "antiga". Embora muito menos eficiente do ponto de vista de E/S de disco e de rede, a pesquisa AJAX simplificou a implementação técnica do Stream Congress. O mais significativo é que a necessidade de um aplicativo Cramp separado foi eliminada. O endpoint AJAX foi fornecido pelo app Rails. O código do lado do cliente foi modificado para oferecer suporte à pesquisa AJAX do jQuery:
var fillStream = function(mostRecentActivity) {
$.getJSON(requestURL, function(data) {
addToStream(data.reverse());
setTimeout(function() {
fillStream(recentActivities.last());
}, 15000);
});
};
AJAX polling, though, is not without its downsides. Relying on the HTTP request/response cycle means that the server sees constant load even when there aren't any new updates. And of course, AJAX polling doesn't take advantage of what HTML5 has to offer.
## EventSource: The right tool for the job
Up to this point, a key factor was ignored about the nature of Stream Congress: the app only needs to stream updates one way, from server to client - downstream. It didn't need to be real-time, upstream client-to-server communication.
In this sense, WebSockets is overkill for Stream Congress. Server-to-client communication is so common that it's been given a general term: push. In fact, many existing solutions for WebSockets, from the hosted [PusherApp](http://pusherapp.com) to the Rails library [Socky](https://github.com/socky), optimize for push and don't support client-to-server communication at all.
Enter EventSource, also called Server-Sent Events. The specification compares favorably to WebSockets in the context to server to client push:
- A similar, simple JavaScript API on the browser side.
- The open connection is HTTP-based, not dropping to the low level of TCP.
- Automatic reconnection when the connection is closed.
### Going Back to Cramp
In recent months, Cramp has added support for EventSource. The code is very similar to the WebSockets implementation:
```ruby
class LiveEvents < Cramp::Action
self.transport = :sse
periodic_timer :latest, :every => 15
def latest
@latest_activity ||= nil
new_activities = find_activities_since(@latest_activity)
@latest_activity = new_activities.first unless new_activities.empty?
render new_activities.to_json
end
end
routes = HttpRouter.new do
add('/').to(LiveEvents)
end
run routes
Um problema importante a ser considerado com o EventSource é que as conexões entre domínios não são permitidas. Isso significa que o app Cramp precisa ser veiculado no mesmo domínio streamcongress.com que o app Rails principal. Isso pode ser feito com proxy no servidor da Web. Supondo que o app Cramp seja executado pelo Thin e na porta 8000, a configuração do Apache será assim:
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule proxy_balancer_module /usr/lib/apache2/modules/mod_proxy_balancer.so
<VirtualHost *:80>
ServerName streamcongress.com
DocumentRoot /projects/streamcongress/www/current/public
RailsEnv production
RackEnv production
<Directory /projects/streamcongress/www/current/public>
Order allow,deny
Allow from all
Options -MultiViews
</Directory>
<Proxy balancer://thin>
BalancerMember http://localhost:8000
</Proxy>
ProxyPass /live balancer://thin/
ProxyPassReverse /live balancer://thin/
ProxyPreserveHost on
</VirtualHost>
Essa configuração define um endpoint EventSource como streamcongress.com/live
.
Polyfill estável
Uma das vantagens mais significativas do EventSource em relação aos WebSockets é que o substituto é completamente baseado em JavaScript, sem dependência de Flash. O polyfill de Remy Sharp faz isso implementando long-polling em navegadores que não oferecem suporte a EventSource de forma nativa. Assim, o EventSource funciona hoje em todos os navegadores modernos com JavaScript ativado.
Conclusão
O HTML5 abre as portas para muitas possibilidades novas e interessantes de desenvolvimento da Web. Com WebSockets e EventSource, os desenvolvedores da Web agora têm padrões claros e bem definidos para ativar apps da Web em tempo real. Mas nem todos os usuários usam navegadores modernos. A degradação graciosa precisa ser considerada ao escolher implementar essas tecnologias. Além disso, as ferramentas no lado do servidor para WebSockets e EventSource ainda estão nos estágios iniciais. É importante considerar esses fatores ao desenvolver apps HTML5 em tempo real.