Server Side Events com YeAPF

Manter uma tela com dados dinâmicos atualizados não é fácil. Uma solução é empregar XMLHttpRequest e ficar fazendo polling sobre uma base de tempo regular. Assim perguntaríamos ao servidor se há modificações sobre o escopo desejado.

O problema desta aproximação é que ficamos fazendo um monte de requisições ao servidor das quais a maioria é apenas para dizer “nenhuma novidade por aqui”

Uma forma contundentemente mais limpa e saudável é usar WebSocket. YeAPF inclui suporte para WebSocket desde o final de 2016. O problema com eles é que você precisa de disponibilizar um porto no seu servidor apenas para ele e utilizar um certificado para poder fazer https em lugar de http com o qual o projeto pode ficar inviabilizado. Insisto, porém, em dizer que é uma solução muitíssimo mais rica e -em certo sentido- potente do que a SSE.

Server Side Events

Os SSE surgem como uma resposta ao gargalo situacional criado pelo exposto logo acima. Pense neles como um WebSocket simplificado. Ou seja, uma tomada ao qual seu aplicativo se conecta e por meio dela o servidor avisa quando há coisas para serem puxadas.  Esse aviso gera um evento no cliente que já pode ser o dado a ser puxado ou uma indicação para utilizar outras tecnologias para puxar a informação.

Não é o escopo deste artigo ensinar SSE, mas pode dar uma olhada neste link da W3Schools e também neste outro da MDN.

O YeAPF permite a construção de aplicativos usando SSE de forma bem simples. Caso o SSE não funcione bem ou não esteja implementado (como no caso de Opera for Linux ou algumas versões de Mozilla for Windows) o YeAPF vai automaticamente cair para uma versão de polling que ao menos permitirá continuar com o uso do aplicativo de forma aceitável (é claro que não vai ser possível construir um jogo de ação online fazendo polling mas dá para o gasto em aplicativos comerciais)

Um exemplo

O exemplo a seguir mostra a utilização do SSE no Javascript. Neste caso trata apenas da ‘recepção’ dos eventos que são gerados pelo servidor ou pelo próprio SSE.

/* inicializo um SSE indicando um grupo de trabalho */
mySSE = ycommSSEBase('agenda');

/* 
    adiciono alguns gestores de eventos ao SSE 
    o evento 'ready' é disparado pelo SSE e indica que ele está pronto para funcionar
    o evento 'close' é disparado pelo SSE e indica o fechamento (correto ou não) do SSE 
*/
mySSE.addEventListener(
       "ready",
        function(e) {
          console.log("ready: "+e.gateway);
        }
);

mySSE.addEventListener(
       "close",
        function(e) {
          console.log("close");
        }
);

/* 'marcar' é um evento criado pelo programador */
mySSE.addEventListener(
        "marcar",
        function(e) {
          var dados = JSON.parse(e.data);
          console.log("dados: "+e.data);
          y$('registro').innerHTML+=e.data;
        }
   );

Com o exemplo anterior, recebemos as mensagens enviadas do servidor. Essas mensagens podem ser disparadas por outros eventos de outros clientes por exemplo ou do próprio servidor, ou na recepção de um e-mail… enfim a imaginação do programador é o limite.

Para simplificar as coisas, faremos com que o mesmo aplicativo envie certa informação ao servidor, o servidor a receba, a manipule e a envie de novo ao cliente que a receberá nesse evento ‘marcar’ que acabamos de inventar.

<script src='js/yloader.js'></script>
<script src='js/exemplo_sse.js'></script>
<button class="button" id="btnMarcar">Marcar</button>
<div id="registro"></div>

HTML com um botao e uma div para o registro da atividade.

/* js/exemplo_sse.js */  
  contador = 0;
  enviarEventoMarcar = function(e) {
    contador++;
    ycomm.invoke("meu_gestor", "xMarcar", { c: contador });
  }

  addOnLoadManager(
    function() {
      addEvent('btnMarcar', 'click', enviarEventoMarcar);
      /* inclua aqui o código de inicialização do mySSE 
       * mostrado no inicio  deste exemplo */
    }
  );

Javascript para criação do evento. Repare que estamos usando xMarcar em lugar de marcar para facilitar a leitura durante o aprendizado. Em produção deixamos os dois iguais para poder acompanhar melhor.

<?php
  /* meu_gestor.php */
  function qmeu_gestor($a)
  {
    /* retorno nulo */
    $ret='';

    /* publicamos as variáveis passadas como parâmetros no invoke 
       por exemplo, o 'c' que criamos do lado do cliente no javascript
       aparece aqui como uma variável local */
    extract(xq_extractValuesFromQuery());

    /* analisamos o evento de entrada */
    switch($a)
    {
      /* caso se trate do xMarcar ... */
      case 'xMarcar':
        /* mostrarei a data do servidor */
        $ts = date('c');
        /* mostrarei um valor aleatorio */
        $v = random(1000,9999);
        /* empacoto os dados. 
           'ts' e 'v' estou criando aqui ao passo que 
           'c' veio do javascript */
        $data = array("c" => $c, "ts"=> $ts, "v" => $v);
        /* envio para os clientes conectados (todos eles) */
        SSE::broadcastMessage('marcar', json_encode($data));
        /* indico ao cliente que o envio foi bem sucedido */
        $ret = array('envio' => 'ok');
        break;
    }

    /* retorno normal de um invoke */
    xq_produceReturnLines($ret, true, 20);

  }

?>

PHP para a recepção do evento do lado do servidor.

Bem, se tudo deu certo, seu aplicativo deve mostrar a data do servidor e um contador que vai mudando a cada vez que o botão é clicado.

Se isso está funcionando, abra um outro navegador de verdade (todos menos o IE) e veja o que acontece. Abra mais dois ou três. A cada clique do botão em um dos clientes, os outros devem ser informados. Há alguns navegadores em que o SSE está mal implementado ou com requisitos superiores. Nesse caso, o gateway não vai ser ‘SSE‘ no evento ‘ready’ e sim ‘Polling‘. Ele mesmo detectará se é melhor fazer invoke() ou crave(). Dependendo das necessidades do aplicativo você pode usar isso para impedir que o mesmo seja utilizado nesse navegador.

Se isso lhe fez feliz, coloque num servidor publico distante (se tiver um servidor na Ásia melhor….kkkk)