Neste artigo vamos trabalhar com o conceito de websockets, onde este é uma nova adição às especificações do HTML5, este, por sua vez, permite que um servidor web consiga estabelecer uma conexão com o navegador e se comunicar diretamente com ele, sem qualquer atraso. Normalmente, a comunicação regular consiste em uma série de pedidos e respostas entre o navegador e o servidor web, onde, para aplicações web que atuam em tempo real, esta técnica não é bem adequada. Dessa forma, com a utilização de websockets, podemos estabelecer uma conexão uma única vez e, em seguida, a comunicação entre o servidor e o navegador poderão seguir sem problemas de atrasos ou lags. Esse é o tipo de tecnologia que podemos precisar utilizar para aplicações que requerem atualizações regulares e rápidas a partir de um webserver, como por exemplo, podemos citar aplicações de jogos multiplayer e aplicações de chat, dentre outros.

A ideia inicial para a construção da web foi de que um cliente requisita dados e o servidor cumpre essas requisições passando os dados solicitados. Esse foi um paradigma incontestável por anos, até o surgimento do AJAX em torno do ano de 2005, onde muitos exploradores procuraram por novas possibilidades de fazer conexões entre um cliente e servidor de forma bidirecional. Com o crescimento das aplicações web, muitos dados estavam sendo consumidos de forma exponencial e a forma mais importante de manter essas aplicações era com a utilização do modelo de transações HTTP tradicional iniciado pelo cliente. Para que pudesse ser superado essa barreira, um grande número de estratégias fora desenvolvido de forma a permitir que os servidores enviassem dados para os clientes, dentre os quais se destacou a estratégia de Log-Polling, onde esta tinha por objetivo manter a conexão HTTP aberta até que o servidor tivesse dados disponíveis para passar para o cliente. O problema é que esse tipo de solução levava o HTTP a uma sobrecarga com o aumento da latência. Então o que poderia ser feito para que houvesse uma conexão persistente para suportar as transações iniciadas entre servidor e cliente e que além disso, possuísse uma baixa latência? É disso que trataremos neste artigo, que é a utilização dos websockets.

Como trabalhar com os websockets?

Os WebSockets têm por finalidade proporcionar uma ligação persistente entre um cliente e um servidor onde ambas as partes podem ser utilizadas para iniciar o envio de dados a qualquer momento. Desta forma, o cliente estabelece uma conexão WebSocket através de um processo, no qual o cliente envia um pedido via HTTP para o servidor de forma regular, com isso, um cabeçalho de atualização vai incluso neste pedido que informa ao servidor que o cliente pretende estabelecer uma conexão via WebSocket. Um exemplo de cabeçalho que pode ser enviado é apresentado da seguinte forma:

GET ws://exemplo.websocket.com.br/ HTTP/1.1
  Origin: http://exemplo.com
  Connection: Upgrade
  Host: exemplo.websocket.com
  Upgrade: websockets

Como podemos observar, a URL do WebSocket usa o esquema ws, onde além deste, ainda podemos utilizar o WSS que é para conexões WebSocket seguras, além de que esta conexão é equivalente de HTTPS. No caso do servidor ter suporte a utilização de protocolos WebSockets, ele concorda com a atualização e retorna um novo cabeçalho de atualização na sua resposta, como podemos apresentar a seguir:


  HTTP 101 WebSocket Protocol Handshake
  Date: Wed, 16 Feb 2015 22:07:34 GMT
  Connection: Upgrade
  Upgrade: WebSocket

Com a comunicação tendo sido concluída, o HTTP inicial é substituído por uma ligação Websocket que utiliza a mesma ligação TCP/IP subjacente, a partir deste momento, qualquer uma das partes poderá iniciar o envio de dados. Com os Websockets, a transferência de dados pode ser feita sem a ocorrência de sobrecargas associadas as solicitações HTTP, com isso, os dados são transferidos através de um Websocket como uma mensagem, que consiste em um ou mais frames (quadros) que possui os dados que estarão sendo enviados. A fim de que a mensagem seja reconstruída de forma adequada, ao chegar no cliente, cada um dos quadros é pré-fixado com um tamanho de 4 a 12 bytes de dados sobre a carga útil. Com a utilização deste sistema de mensagens baseados em frames há uma redução significativa na latência e na quantidade de dados que seria enviado em um único pacote. É importante notarmos aqui que o cliente só será notificado sobre a nova mensagem uma vez que todos os quadros tiverem sido recebidos e a carga da mensagem original tiver sido reconstruída.

Para que possamos entender melhor com relação ao conceito de Websockets, nada melhor do que aprendermos realizando o desenvolvimento de um exemplo simples, para isso, apresentaremos a partir deste momento alguns dos itens mais importantes e comuns na utilização do Websocket no HTML5.

Criando um exemplo simples de WebSockets

Para começarmos com o nosso exemplo, primeiramente escolheremos a Ide que mais nos adequarmos para a construção do nosso código, neste momento, utilizaremos o Netbeans, mas fiquem a vontade para desenvolver da forma que acharem melhor. Dito isso, criaremos primeiro a nossa página index.html, de acordo com a apresentada pela Listagem 1.

Listagem 1. Criando a página index.html.

<!DOCTYPE html>
  <html>
      <head>
          <title>WebSockets com HTML5</title>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <link rel="stylesheet" href="style.css">
      </head>
      <body>
          <div id="page-wrapper">
              <h1>Exemplo de utilização de WebSockets</h1>
   
              <div id="status">Conectando a aplicação...</div>
   
              <ul id="mensagens"></ul>
   
              <form id="mensagem-formulario" action="#" method="post">
                  <textarea id="mensagem" placeholder="Escreva a sua mensagem aqui!" required></textarea>
                  <button type="submit">Enviar mensagem</button>
                  <button type="button" id="close">Fechar conexão</button>
              </form>
          </div>
          <script src="aplicacao.js"></script>
      </body>
  </html>

Neste momento, temos alguns elementos-chave criados que serão utilizados pela nossa aplicação, dentre os quais, temos aqui uma <div> responsável pela exibição das mensagens sobre o status da conexão; além dela, temos uma lista que vai ser utilizada para apresentarmos as mensagens enviadas e recebidas a partir do servidor; e também, teremos um formulário para inserirmos as mensagens que serão enviadas. Agora que temos a nossa página index.html criada, percebam que teremos também a criação de um arquivo CSS, que chamamos de style e um arquivo js, que chamamos de aplicacao.js, os quais estão sendo apresentados de acordo com as Listagens 2 e 3.

Listagem 2. Criação do arquivo de estilo Style.css.

*, *:before, *:after {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
  }
   
  html {
    font-family: Helvetica, Arial, sans-serif;
    font-size: 100%;
    background: #333;
  }
   
  #page-wrapper {
    width: 650px;
    background: #FFF;
    padding: 1em;
    margin: 1em auto;
    border-top: 5px solid #69c773;
    box-shadow: 0 2px 10px rgba(0,0,0,0.8);
  }
   
  h1 {
                  margin-top: 0;
  }
   
  #status {
    font-size: 0.9rem;
    margin-bottom: 1rem;
  }
   
  .open {
    color: green;
  }
   
  .closed {
    color: red;
  }
   
   
  ul {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 0.95rem;
  }
   
  ul li {
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid #EEE;
  }
   
  ul li:first-child {
    border-top: 1px solid #EEE;
  }
   
  ul li span {
    display: inline-block;
    width: 90px;
    font-weight: bold;
    color: #999;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 1px;
  }
   
  .envia {
    background-color: #F7F7F7;
  }
   
  .recebida {}
   
  #mensagem-formulario {
    margin-top: 1.5rem;
  }
   
  textarea {
    width: 100%;
    padding: 0.5rem;
    font-size: 1rem;
    border: 1px solid #D9D9D9;
    border-radius: 3px;
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
    min-height: 100px;
    margin-bottom: 1rem;
  }
   
  button {
    display: inline-block;
    border-radius: 3px;
    border: none;
    font-size: 0.9rem;
    padding: 0.6rem 1em;
    color: white;
    margin: 0 0.25rem;
    text-align: center;
    background: #BABABA;
    border-bottom: 1px solid #999;
  }
   
  button[type="submit"] {
    background: #86b32d;
    border-bottom: 1px solid #5d7d1f;
  }
   
  button:hover {
    opacity: 0.75;
    cursor: pointer;
  }

Agora que temos a nossa classe style criada, onde definimos apenas alguns itens para uma melhor visualização do nosso projeto exemplo, vamos partir para a criação do nosso arquivo JavaScript, como apresentado pela Listagem 3.

Listagem 3. Criação do arquivo aplicacao.js.


  window.onload = function() {
   
    //  pega as referencias dos elementos da página.
    var form = document.getElementById('mensagem-formulario');
    var mensagemTexto = document.getElementById('mensagem');
    var listaMensagem = document.getElementById('mensagens');
    var socketStatus = document.getElementById('status');
    var btnFechar = document.getElementById('close');
  };

Como apresentado, criamos aqui um número de variáveis e as inicializamos para a busca dos elementos-chave na página. Com isso, temos o início da nossa aplicação, onde temos uma aplicação demonstrativa configurada e portanto, podemos começar a aprender sobre a API do WebSocket. De início, veremos aqui como podemos realizar uma conexão através do WebSocket. Para criarmos uma conexão não será nenhuma dificuldade, onde tudo que precisamos fazer é chamar o construtor WebSocket e passar a URL do servidor que será utilizado, como podemos ver através da seguinte passagem que incluiremos no nosso arquivo aplicacao.js, como apresentado pela Listagem 4.

Listagem 4. Abrindo uma conexão com webSockets.

// Criando uma nova conexão WebSocket.
  var socket = new WebSocket('ws://echo.websocket.org');

Para realizarmos os testes com websockets podemos ver através algumas informações relevantes através do seu site. Uma vez que tivermos a conexão estabelecida, o evento open() será disparado para a nossa instância WebSocket. Na nossa aplicação, o que estamos fazendo é a adição de um listener do evento que irá atualizar o status da <div> com uma mensagem, apresentando que a conexão foi estabelecida. Agora que temos a nossa conexão, vamos adicionar mais este trecho de código no nosso arquivo aplicacao.js, como mostra a Listagem 5.

Listagem 5. Apresentando uma mensagem de abertura do Websocket.

socket.onopen = function(event) {
    socketStatus.innerHTML = 'Conectado com: ' + event.currentTarget.URL;
    socketStatus.className = 'open';
  };

Com os websockets também podemos recuperar erros que possam ocorrer no decorrer de um Listener (escuta) que parta de um evento error. No nosso caso, por simplificação, estamos apenas apresentando esses erros num console através do código apresentado de acordo com a Listagem 6.

Listagem 6. Código referente a ocorrência de erros.

socket.onerror = function(error) {
    console.log('Erro do WebSocket: ' + error);
  };

Agora que vimos, como realizarmos uma conexão e capturarmos erros, o que mais poderíamos ter com os websockets? O que nos falta no momento é o envio das mensagens, as quais, para serem enviadas através da conexão WebSocket, devemos chamar o método send() na nossa instância WebSocket, onde passaremos os dados que serão transferidos, o que apresentamos da seguinte forma:

socket.send(data); 

Com os Websockets, podemos realizar o envio de tanto de textos como dados binários, no nosso exemplo, enviaremos o conteúdo do campo para o servidor quando o formulário for enviado. Para que possamos fazer isso, precisaremos inicialmente ter um evento de Listener configurado no formulário, o que faremos de acordo com a Listagem 7.

Listagem 7. Enviando os dados do formulário.

form.onsubmit = function(e) {
    e.preventDefault();
   
    // Recuperando a mensagem do textarea.
    var mensagem = mensagemTexto.value;
   
    // Enviando a mensagem através do WebSocket.
    socket.send(mensagem);
   
    // Adicionando a mensagem numa lista de mensagens enviadas.
    listaMensagem.innerHTML += '<li class="envia"><span>Enviado:</span>' + mensagem +
                              '</li>';
   
    // Limpando o campo da mensagem após o envio.
    mensagemTexto.value = '';
   
    return false;
  };

Quando o formulário for enviado, o código apresentado pela Listagem 7 tem por finalidade recuperar a mensagem do campo mensagemTexto e em seguida, enviá-la através do WebSocket. A mensagem é então adicionada à lista de mensagens chamada de listaMensagem e apresentada na página. Para concluirmos nosso exemplo, precisamos obter o valor de mensagemTexto como reposta pronta para que o usuário digite uma nova mensagem.

Recebendo uma mensagem

Quando uma mensagem é recebida o evento mensagem é disparado. Este evento inclui uma propriedade chamada de data que poderá ser usada para acessar o conteúdo da mensagem. Para o nosso exemplo, precisaremos criar um evento Listener que será disparado quando uma nova mensagem for recebida, com isso, o código devera recuperar a mensagem do evento e exibi-lo no listaMensagem. Este método está sendo apresentado de acordo com a Listagem 8.

Listagem 8. Copiando a mensagem que é enviada para o servidor.

socket.onmessage = function(event) {
    var mensagem = event.data;
    listaMensagem.innerHTML += '<li class="recebida"><span>Recebida:</span>' +
                               mensagem + '</li>';
  };

Após termos finalizado a utilização do nosso WebSocket, o que precisamos fazer é encerrar a nossa conexão, onde utilizaremos o método close():

socket.close();

Após a nossa conexão ter sido fechada, o navegador irá disparar um evento close. Associando um evento de Listener ao evento close, o que nos permite a execução de qualquer limpeza que precisarmos fazer. Para demonstrarmos que a nossa conexão foi fechada, iremos apenas atualizar o status da conexão, de acordo com o código apresentado pela Listagem 9, que será adicionado também ao nosso arquivo aplicacao.js.

Listagem 9. Fechamento da conexão WebSocket.

socket.onclose = function(event) {
    socketStatus.innerHTML = 'Disconectado do WebSocket.';
    socketStatus.className = 'closed';
  };

Para finalizarmos, vamos adicionar um evento que será disparado quando o botão de “Fechar conexão” for clicado, o que irá chamar o método close() do WebSocket, como podemos apresentar de acordo com a Listagem 10.

Listagem 10. Fechando a conexão WebSocket com o botão.

btnFechar.onclick = function(e) {
    e.preventDefault();
   
    // Fechando o WebSocket.
    socket.close();
   
    return false;
  };

Agora que apresentamos cada trecho do nosso aplicacao.js, explicando cada ponto dele. Vejamos o arquivo completo através da Listagem 11.

Listagem 11. Arquivo aplicacao.js completo.

window.onload = function() {
   
    // Pegando as referências para os elementos da página.
    var form = document.getElementById('mensagem-formulario');
    var mensagemTexto = document.getElementById('mensagem');
    var listaMensagem = document.getElementById('mensagens');
    var socketStatus = document.getElementById('status');
    var btnFechar = document.getElementById('close');
   
   
    // Criando uma nova WebSocket.
    var socket = new WebSocket('ws://echo.websocket.org');
   
   
    // segurando os erros que ocorrerem.
    socket.onerror = function(error) {
      console.log('erros do WebSocket: ' + error);
    };
   
   
    // Mostrando a mensagem de Conectado quando o WebSocket for aberto.
    socket.onopen = function(event) {
      socketStatus.innerHTML = 'Conectado com: ' + event.currentTarget.URL;
      socketStatus.className = 'open';
    };
   
    // Pegando as mensagens enviadas pelo servidor.
    socket.onmessage = function(event) {
      var mensagem = event.data;
      listaMensagem.innerHTML += '<li class="recebida"><span>Recebida:</span>' +
                                 mensagem + '</li>';
    };
   
    //Mostrando a mensagem de desconectado quando o websocket for fechado.
    socket.onclose = function(event) {
      socketStatus.innerHTML = 'Disconectando o WebSocket.';
      socketStatus.className = 'closed';
    };
   
    //Enviando uma mensagem quando o formulário for submetido.
    form.onsubmit = function(e) {
      e.preventDefault();
   
      // Recuperando a mensagem do textarea.
      var mensagem = mensagemTexto.value;
   
      // Enviando a mensagem através do WebSocket.
      socket.send(mensagem);
   
      //Adicionando a mensagem para a lista de mensagens.
      listaMensagem.innerHTML += '<li class="envia"><span>Enviada:</span>' + mensagem +
                                '</li>';
   
      // Limpando o campo de mensagem.
      mensagemTexto.value = '';
      return false;
    };
    //Fechando a conexão WebSocket quando o botão for clicado.
    btnFechar.onclick = function(e) {
      e.preventDefault();
   
      // Fechando o WebSocket.
      socket.close();
      return false;
    };
  };

Ao contrário do que o nome indica, o protocolo WebSocket não é apenas sobre a web! A especificação foi implementada a partir dos sistemas operacionais móveis e tablets, incluindo iOS, Android e Windows. Isso significa que podemos usar o poder e a velocidade do protocolo WebSocket em um aplicativo de smartphone nativamente. Os princípios fundamentais permanecem, independentemente de usarmos JavaScript, ou linguagens de programação, como Objective-C ou mesmo o C#.

O protocolo WebSocket como parte do HTML5, possui uma estrutura robusta para o desenvolvimento e a concepção de aplicações web. Não é apenas uma nova marcação ou alguns novos seletores de estilo, nem é uma nova linguagem de programação. O HTML5 significa uma coleção de tecnologias, linguagens de programação e ferramentas, cada uma das quais possui um papel discreto e todos juntos realizam uma tarefa específica que é a construção de ricas aplicações web para qualquer tipo de dispositivo. Os principais pilares do HTML5 incluem Markup, CSS3 e Api’s JavaScript.

Com isso finalizamos o nosso artigo sobre a utilização de WebSockets, onde vimos que ele tem por finalidade definir uma comunicação persistente bidirecional entre servidores e clientes web, o que significa que ambas as partes podem trocar dados de mensagens ao mesmo tempo. Eles são otimizados para obter alto desempenho e resultados em aplicações web muito mais ágeis e ricas. Com a utilização dos WebSockets, vimos que podemos substituir os long-pollings, onde este é na verdade, um conceito bastante interessante, no qual, um cliente envia uma solicitação para o servidor, e como este ainda não possui os dados de resposta, ele essencialmente mantém a conexão aberta até ocorrer o refresh, onde os dados estarão atualizados e prontos para serem enviados para o cliente que recebe a informação e envia um novo pedido. Dentre as vantagens que podemos ter com a utilização de websockets, podemos citar aqui a redução na latência, onde, como já temos uma conexão que foi aberta, não será requerida uma nova conexão.

Links

https://www.websocket.org/

http://www.codeproject.com/Articles/531698/Introduction-to-HTML-WebSocket

https://developer.mozilla.org/en-US/docs/WebSockets

http://www.tutorialspark.com/html5/HTML5_WebSockets_Apps.php