Estudo de caso: atualizações em tempo real no Stream Congress

Luigi montanez
Luigi Montanez

Introdução

Por meio do WebSockets e do EventSource, o HTML5 permite que os desenvolvedores criem aplicativos da Web que se comunicam em tempo real com um servidor. O Stream Congress (disponível na Chrome Web Store) fornece atualizações em tempo real sobre o funcionamento do Congresso dos Estados Unidos. Ela transmite atualizações da Câmara e do Senado, notícias relevantes, tweets de membros do Congresso e outras atualizações de mídias sociais. O aplicativo deve ficar aberto o dia todo, já que captura os negócios do Congresso.

Começando com WebSockets

A especificação WebSockets chamou muita atenção devido ao que ela permite: um soquete TCP bidirecional e estável entre o navegador e o servidor. Não há formato de dados imposto ao 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 do navegador para WebSockets seja simples, o suporte do lado do servidor ainda está na fase de formação. O Socket.IO no Node.js fornece uma das soluções do lado do servidor mais maduras e robustas. Um servidor orientado a eventos como o Node.js é a opção ideal para WebSockets. Para implementações alternativas, os desenvolvedores Python podem usar Twisted e Tornado, enquanto os desenvolvedores Ruby têm EventMachine.

Conheça o Cramp

O Cramp é um framework da Web em Ruby assíncrono executado com base no EventMachine. Ele foi escrito por Pratik Naik, membro da equipe principal do Ruby on Rails. Com uma linguagem específica de domínio (DSL) para apps da Web em tempo real, o Cramp é a escolha ideal para desenvolvedores da Web em Ruby. Aqueles que já aprenderam a escrever controladores em Ruby on Rails reconhecerão 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 no topo do EventMachine sem bloqueio, é preciso ter em mente várias considerações:

  • É preciso usar drivers de banco de dados sem bloqueio, como MySQLPlus e em-mongo.
  • É preciso usar servidores da Web orientados por eventos. O suporte é integrado para Thin e Rainbows.
  • O app Cramp precisa ser executado separadamente do app principal do Rails que alimenta o Stream Congress, reiniciado e monitorado de maneira 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 do navegador para WebSockets. Embora não exista polyfill de JavaScript puro, há um substituto de Flash que foi amplamente adotado. No entanto, depender do Flash está longe do ideal. Embora o Chrome e o Safari continuem a oferecer suporte a WebSockets, ficou claro que, para oferecer suporte a todos os navegadores modernos sem depender do Flash, os WebSockets precisaria ser substituídos.

Como reverter para a sondagem de AJAX

Foi tomada a decisão de abandonar os WebSockets e voltar à tradicional pesquisa de AJAX. Embora seja muito menos eficiente do ponto de vista de E/S de disco e rede, a pesquisa AJAX simplificou a implementação técnica do Stream Congress. O mais significativo foi que a necessidade de um aplicativo Cramp separado foi eliminada. Em vez disso, o endpoint AJAX foi fornecido pelo aplicativo do Rails. O código do lado do cliente foi modificado para oferecer suporte à pesquisa de 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 significativo a ser lembrado com relação ao EventSource é que as conexões entre domínios não são permitidas. Isso significa que o app Cramp precisa ser disponibilizado no mesmo domínio streamcongress.com que o app Rails principal. Para isso, use o proxy no servidor da Web. Supondo que o app Cramp use a tecnologia Thin e esteja em execução na porta 8000, a configuração do Apache será semelhante a esta:

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 em 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 do Remy Sharp faz isso implementando a pesquisa longa em navegadores que não são compatíveis com EventSource de forma nativa. Assim, o EventSource funciona atualmente em todos os navegadores modernos com JavaScript ativado.

Conclusão

O HTML5 abre as portas para muitas possibilidades novas e empolgantes de desenvolvimento da Web. Com WebSockets e EventSource, os desenvolvedores da web agora têm padrões limpos e bem definidos para permitir aplicativos da web em tempo real. No entanto, 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 do lado do servidor para WebSockets e EventSource ainda estão nos estágios iniciais. É importante ter esses fatores em mente ao desenvolver aplicativos HTML5 em tempo real.