Concrete Logo
Hamburger button

Comet para aplicação web em tempo real

  • Blog
  • 27 de Setembro de 2012
Share

Ou Comet, o desktop na web

 
No início, a web era chata. Muito chata.

Seu modelo de uma página inteira por requisição não permitia muita flexibilidade para interações dos usuários com os sistemas web. Quem estava acostumado com aplicações ricas no desktop se desanimava com estado da web até o início dos anos 2000.

Hoje a web não é chata (pelo menos para mim e se você está lendo isto, provavelmente concorde comigo).

Por quê?

Bom, muitas técnicas surgiram para fazer uma aplicação web receber dados do servidor SEM EFETUAR uma requisição. Com isso, temos um feeling mais “desktopish” como por exemplo Facebook, Google Docs e afins…

Como tudo em tecnologia da informação, foram criados trocentos nomes para trocentas formas de se atingir esse resultado. Hoje há até um termo que agrupa as várias técnicas: Comet.

Sem ter a pretensão de esgotar o assunto, veremos um exemplo de UMA destas técnicas de Comet, o long polling.

 

Introdução

O termo “Comet” foi criado por Alex Russell do projeto Dojo para descrever trocas entre um cliente e um servidor nas quais o servidor inicia os contatos ao invés do cliente.

Joe Walker do projeto Direct Web Remoting (DWR) chamava isto de AJAX Reverso.

Como dissemos, o termo Comet se refere a um tipo de solução que também é chamada por trocentos outros nomes e que pode ser implementada usando trocentas técnicas diferentes. Mesmo com efeitos colaterais algumas delas fingem direitinho que é o servidor que está no comando.

Comet pode ser usado em:

    – chats,
    – jogos,
    – aplicações de monitoramento,
    – dashboards de processos em geral,
    – aplicações que recebem atualizações de atividades server side de longa duração,
    – leitor de emails em que o cliente recebe automaticamente novos emails,
    – o que mais sair da sua imaginação.

Este artigo mostra uma aplicação bem mais simples do que parece e cujo modelo pode ser facilmente replicado em outras circunstâncias em que Comet é indicado.

 

Aplicação exemplo – placar atualizado em tempo real

Fazer uma aplicação que atualize placar em tempo real já foi preocupação de muita gente e como dissemos, pode ser feito de várias modos.

O Emerson Macedo (@emerleite) da Globo.com, apresentou no Brazil.JS de 2011 em Fortaleza, uma palestra que vale a pena ver os slides em Node.JS Realtime Web Applications. Ele cita vários meios que poderiam também ser usados.

 
O cenário é o seguinte:

    Uma aplicação web que mostre o placar dos jogos da NBA em tempo real.

    O caso típico de uso será:

      quem quer acompanhar o jogo de basquete abrirá a página da aplicação, provavelmente de um tablet ou celular e receberá o novo placar, SEM ter que atualizar a página (ou sem a página ficar pedindo atualização a cada 5 segundos por debaixo dos panos como ocorre em muitos portais por aí).

    No servidor, um administrador da aplicação pode atualizar o placar visto pelos usuários clientes.

    Melhor ainda se o servidor for atualizado automaticamente usando algum coletor de dados eletrônico e neste caso o uso do Arduino poderia ser muito bem indicado. Veja sobre o Arduino no nosso blog em https://blog.concretesolutions.com.br/?s=arduino.

    Diálogo entre devs:

      [sourcecode language=”text” light=”true”]
      Eu: Simples!
      Outro dev: Hmmm… posso usar Flash?
      Eu: Não… há trocentos dispositivos sem suporte ao Flash…
      Outro dev: Silverlight??? Quicktime??? Applet??? Help?
      Eu: Erhm…. não. Comet to the rescue![/sourcecode]

     
    Long polling é a técnica de receber uma requisição mas não commitar a resposta. Ou seja, manter a comunicação com o cliente aberta para trafegar dados até que estes estejam disponíveis ou um tempo limite seja atingido (ou um desastre mate sua infraestrutura… nunca se sabe…).

    Essa técnica não é muito bem quista por manter muitas conexões simultâneas abertas, onerando o servidor. Mas no nosso caso será mais simples do que parece.

    Usaremos duas coisas: Erlang e ChicagoBoss.

    Ops, não pare de ler, você verá que o bicho não é tão feio assim.

    Escolhemos essa linguagem e esse framework porque Erlang não se importa com dezenas de milhares de conexões abertas. Na verdade, a linguagem foi FEITA para isso.

    E o ChicagoBoss é um framework que está ganhando muitos adeptos no mundo de Erlang (ainda um mundo pequeno mas que promete… vai por mim…).

    O ChicagoBoss facilita ABSURDAMENTE para qualquer um usar Erlang (mesmo que não entenda nada de OTP, BEAM, APPUPs, RELUPs, etc). De novo, vai por mim…

    Como diria Monthy Python: GET ON WITH IT!

     

    Mão na massa, bora botar para rodar

    Requisitos

    Testados em Linux e OSX. Para Windows há alguns problemas, mas não é impossível \o/ ):

      1 – Erlang (R14B01 ou mais recente)

        – no OSX instale com o homebrew: [sourcecode language=”bash” gutter=”true”]brew install erlang[/sourcecode]

      2 – Git

    Passos para rodar a aplicação exemplo na sua máquina:

      1 – Clone o projeto e passe para o diretório dele:

        [sourcecode language=”bash”]git clone https://bitbucket.org/cs_victor_nascimento/erlang_comet.git
        cd erlang_comet[/sourcecode]

      2 – Clone o projeto ChicagoBoss DENTRO da pasta do projeto e chame o comando make dentro da pasta ChicagoBoss:

        [sourcecode language=”bash”]git clone https://github.com/evanmiller/ChicagoBoss.git
        cd ChicagoBoss
        make[/sourcecode]

      3 – Entre no diretório da aplicação (‘erlang_comet/bascore’) e compile o projeto com o comando:

        [sourcecode language=”bash”]./rebar boss c=compile[/sourcecode]

      4 – Ainda no diretório da aplicação, assegure-se que o servidor não está no ar e o inicialize em modo developer (para ter na mão o terminal do erlang):

        [sourcecode language=”bash”]./init.sh stop
        ./init-dev.sh[/sourcecode]

      Neste momento, depois de muitas linhas de log passando na tela sobre as dependências e sistemas do Erlang, a aplicação já deve estar de pé!

     

    Preparar o Comet para a ação

      Criar um jogo no servidor:

        Abra um browser (até o Internet Explorer funciona… juro!) e acesse:

        Se tudo estiver certo você verá a página mais tosca que já viu na vida. Para não complicar nosso exemplo, deixei o HTML mais cru já visto por qualquer um!!!

        A página create deve estar bem intuitiva: digite o nome dos times que disputarão uma partida e clique em ‘Criar placar’. Você será redirecionado para uma página

        A última parte da url é o ID do placar criado.

      Acessar o placar das máquinas ou dispositivos clientes:

        Verifique qual o endereço IP da máquina que está rodando o servidor.

        Abra outros browsers, de preferência em outras máquinas e outros dispositivos e digite o endereço

        Tem que colocar o ID do placar no final da URL. Se forem criados mais de um placar, serão gerados IDs diferentes. Assim, caso queira acompanhar outros placares, basta usar a mesma URL só que com o id diferente, por exemplo,

     

    Tcharam….

    Está tudo certo! Veremos agora o Comet em ação, especificamente o long-polling.

    Altere o placar na página administrativa no servidor para, digamos, 2 pontos para o time A e 2 pontos para o time B. Clique em atualizar o placar.

    Todos os browsers clientes serão atualizados sem fazer nenhuma requisição manual!

    Gostou? É show de bola, concorda? E tem muitas aplicações que podem se beneficiar deste modelo.

     

    Você pode parar de ler por aqui. A menos que queira saber o que está por debaixo dos panos.

     

    Código fonte

    Imagina-se que para atingir este resultado há centenas de linhas de código, alto consumo de CPU, baixa estabilidade, impossível escalar e etc.

    Na verdade, são apenas três dezenas de linhas de código (sem comentários ou logs) no controlador e 5 linhas para o modelo.

    Tudo bem que não há validações de campos entre outros detalhes, mas ainda assim prova o poder das linguagens funcionais 😉

    Todo o código Erlang está comentado e pode ser visto on line em https://bitbucket.org/cs_victor_nascimento/erlang_comet

    No fim deste artigo há uma seção de links sobre a linguagem e o framework. Caso fique alguma dúvida, por favor postem comentários!

    Para ressaltar o que está acontecendo, vou me ater apenas às partes do código que julgo importantes.

    Primeiro o create:

    [sourcecode language=”erlang”]% Salva o score
    {ok, Score} = DummyScore:save(),

    % Publica uma mensagem na nossa fila
    boss_mq:push(Score:id() ++ ?QUEUE_SUFFIX, Score),

    % Redireciona para ação admin com o id do placar
    {redirect, "admin/" ++ Score:id()}.[/sourcecode]

    Magia negra nesse trecho! Ao salvar nosso placar, colocamos o resultado numa fila criada com o framework boss_mq (parte do ChicagoBoss). Se a fila não existir (nosso caso aqui) será criada automaticamente. O nome da fila é o nosso id mais um sufixo definido como “-watch“.

    Feito isso, redirecionamos para a ação de admin passando o parâmetro do id criado. De lá o administrador poderá atualizar o placar quando quiser.

    Até aqui tudo bem! Mas e o consumo da fila?

    Como o cliente é atualizado?

    Simples. Vejamos um trecho da função live/2 (live/2 porque é como o Erlang entende que é uma função com 2 parâmetros).

    [sourcecode language=”erlang”]Ts = boss_mq:now(ScoreId ++ ?QUEUE_SUFFIX),

    % Renderiza o template com as variáveis score e timestamp
    {ok, [{score, boss_db:find(ScoreId)}, {timestamp, Ts}]}.[/sourcecode]

    Reparou que usamos um timestamp?

    O motivo é simples: queremos pegar mensagens na fila que sejam mais recentes do que a última vez que recebemos atualizações do jogo. Para isso usamos ‘boss_mq:now/1‘ que retorna um timestamp do servidor em que se encontra a fila (importante caso a aplicação esteja clusterizada!).

    JavaScript que renderiza o template live.html com os parâmetros do placar e com o timestamp.

    A página live.html possui um JavaScript malandro…

    [sourcecode language=”javascript”]
    function listen_for_events(ts) {
    $.ajax(‘/score/pull/’ + ts + ‘/{{ score.id }}’, {
    complete:

    function(data, code, xhr) {

    if(code == ‘error’) {
    alert(‘Um erro ocorreu ao atualizar o placar. Por favor recarregue a página.’);
    return;
    }

    var sResult = JSON.parse(data.responseText);

    $("#team_a_score").val(sResult.score.team_a_score);
    $("#team_b_score").val(sResult.score.team_b_score);

    listen_for_events(sResult.timestamp);
    }
    });
    }

    $(document).ready(
    function() {
    listen_for_events({{ timestamp }});
    }
    );[/sourcecode]

    Não sou nenhum ninja de JS (mas garanto que está na lista das próximas linguagens a estudar a fundo). Para nosso caso não precisa de magia negra…

    A função listen_for_events acima envia recursivamente uma requisição por AJAX. Com resultado da chamada, a mesma função atualiza os campos da página com a resposta de cada chamada a nossa ação ‘pull/2‘ e volta a recursão chamando de novo listen_for_events.

    Segredo!
    Quando chamamos pull/2, a chamada bloqueia!

    Lembra que criamos uma fila? Bom, aqui tentamos pegar uma mensagem da fila e se não houver nenhuma mensagem nova desde ‘LastTimestamp‘, a chamada bloqueia e a requisição do cliente fica aberta até que uma mensagem nova seja colocada na fila.

    Vejamos o código (reparem como é DIFÍCIL retornar um JSON com o ChicagoBoss):

    [sourcecode language=”erlang”]% Prepara o timestamp
    Ts = list_to_integer(LastTimestamp),

    % !!! Esta chamada bloqueia até haver um evento na fila.
    % Por isso chamamos essa técnica de long polling !!!

    % Esta função é chamada RECURSIVAMENTE pelo javascript na página live.
    {ok, Timestamp, [Score]} = boss_mq:pull(ScoreId ++ ?QUEUE_SUFFIX, Ts),

    % Retorna os parâmetros em um objeto JSON.
    {json, [{timestamp, Timestamp}, {score, Score}]}.[/sourcecode]

    Isto não é um problema para a VM do Erlang pois ela não é baseada em Threads, mas sim em processos leves (processos da própria VM). Ela foi pensada para justamente ESTE tipo de caso (na verdade, o cenário eram switches de telefones… mas estou usando licença poética aqui…).

     

    That’s all folks!

    Esta solução, por mais que pareça ruim em outras plataformas como a JVM ou o runtime da Microsoft, é perfeita para o caso Erlang.

    O ChicagoBoss é um ótimo framework web com muito mais recursos do que os mostrados aqui. Fica a dica para o leitor.

    Espero que o exemplo tenha demonstrado a técnica de long polling para Comet (ou AJAX reverso ou http-push ou seja lá o nome que você quiser dar…).

    De bônus temos um pouco do mundo Erlang que é excepcional para sistemas distribuídos.

    Com a ferramenta certa para o problema certo fica muito simples acompanhar em tempo real os jogos da NBA…

     

    Links: