Apresentando WebSockets - Trazendo soquetes para a web

O problema: conexões cliente-servidor e servidor-cliente de baixa latência

A Web tem sido construída em torno do chamado paradigma de solicitação/resposta de HTTP. Um cliente carrega uma página da Web e nada acontece até que o usuário clique na próxima página. Por volta de 2005, o AJAX começou a deixar a web mais dinâmica. Ainda assim, toda a comunicação HTTP era direcionada pelo cliente, o que exigia interação do usuário ou sondagem periódica para carregar novos dados do servidor.

As tecnologias que permitem ao servidor enviar dados ao cliente no mesmo momento em que ele sabe que novos dados estão disponíveis já existem há algum tempo. Elas são conhecidas por nomes como "Push" ou "Comet". Uma das invasões mais comuns para criar a ilusão de uma conexão iniciada pelo servidor é chamada de sondagem longa. Com a sondagem longa, o cliente abre uma conexão HTTP com o servidor, que o mantém aberta até que a resposta seja enviada. Sempre que o servidor realmente tiver dados novos, ele enviará a resposta (outras técnicas envolvem Flash, solicitações XHR multipart e os chamados htmlfiles). A sondagem longa e as outras técnicas funcionam muito bem. Você as usa todos os dias em aplicativos como o bate-papo do Gmail.

No entanto, todas essas soluções compartilham um problema: elas carregam a sobrecarga do HTTP, o que não as torna adequadas para aplicativos de baixa latência. Pense nos jogos multiplayer de tiro em primeira pessoa no navegador ou em qualquer outro jogo on-line com um componente em tempo real.

Apresentação do WebSocket: trazendo soquetes para a Web

A especificação WebSocket define uma API que estabelece conexões de "soquete" entre um navegador da Web e um servidor. Em poucas palavras: há uma conexão persistente entre o cliente e o servidor, e ambas as partes podem começar a enviar dados a qualquer momento.

Vamos começar

Para abrir uma conexão WebSocket, basta chamar o construtor WebSocket:

var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);

Observe o ws:. Este é o novo esquema de URL para conexões WebSocket. Há também wss: para conexão WebSocket segura, da mesma forma que https: é usado para conexões HTTP seguras.

Anexar alguns manipuladores de eventos imediatamente à conexão permite saber quando a conexão foi aberta, se recebeu mensagens ou se houve um erro.

O segundo argumento aceita subprotocolos opcionais. Pode ser uma string ou uma matriz de strings. Cada string precisa representar um nome de subprotocolo, e o servidor aceita somente um dos subprotocolos transmitidos na matriz. O subprotocolo aceito pode ser determinado acessando a propriedade protocol do objeto WebSocket.

Os nomes de subprotocolos precisam ser um dos nomes registrados no registro IANA. No momento, há apenas um nome de subprotocolo (soap) registrado em fevereiro de 2012.

// When the connection is open, send some data to the server
connection.onopen = function () {
connection.send('Ping'); // Send the message 'Ping' to the server
};

// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};

// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};

Comunicação com o servidor

Assim que houver uma conexão com o servidor (quando o evento open é disparado), poderemos começar a enviar dados ao servidor usando o método send('your message') no objeto de conexão. Ele costumava ser compatível apenas com strings, mas, na especificação mais recente, agora também pode enviar mensagens binárias. Para enviar dados binários, use o objeto Blob ou ArrayBuffer.

// Sending String
connection.send('your message');

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
connection.send(binary.buffer);

// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);

Da mesma forma, o servidor pode nos enviar mensagens a qualquer momento. Sempre que isso acontece, o callback onmessage é acionado. O callback recebe um objeto de evento, e a mensagem real pode ser acessada pela propriedade data.

WebSocket também pode receber mensagens binárias na especificação mais recente. Frames binários podem ser recebidos no formato Blob ou ArrayBuffer. Para especificar o formato do binário recebido, defina a propriedade binaryType do objeto WebSocket como "blob" ou "arraybuffer". O formato padrão é "blob". Não é necessário alinhar o parâmetro binaryType ao enviar.

// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
console.log(e.data.byteLength); // ArrayBuffer object if binary
};

As extensões são outro recurso recém-adicionado do WebSocket. Usando extensões, será possível enviar frames compactados, multiplexados etc. Você pode encontrar extensões aceitas pelo servidor examinando a propriedade de extensões do objeto WebSocket após o evento aberto. Ainda não há especificações de extensões publicadas oficialmente a partir de fevereiro de 2012.

// Determining accepted extensions
console.log(connection.extensions);

Comunicação de origem cruzada

Sendo um protocolo moderno, a comunicação de origem cruzada está incorporada diretamente no WebSocket. Embora você ainda deva se comunicar apenas com clientes e servidores confiáveis, o WebSocket permite a comunicação entre partes em qualquer domínio. O servidor decide se o serviço ficará disponível para todos os clientes ou apenas para aqueles que residem em um conjunto de domínios bem definidos.

Servidores proxy

Cada tecnologia nova tem um novo conjunto de problemas. No caso do WebSocket, é a compatibilidade com os servidores proxy que faz a mediação das conexões HTTP na maioria das redes corporativas. O protocolo WebSocket usa o sistema de upgrade HTTP (que normalmente é usado para HTTP/SSL) para fazer "upgrade" de uma conexão HTTP para uma conexão WebSocket. Alguns servidores proxy não gostam disso e abandonarão a conexão. Assim, mesmo se um determinado cliente usar o protocolo WebSocket, talvez não seja possível estabelecer uma conexão. Isso torna a próxima seção ainda mais importante :)

Use WebSockets hoje

O WebSocket ainda é uma tecnologia jovem e não foi completamente implementada em todos os navegadores. No entanto, você pode usar o WebSocket hoje com bibliotecas que usam um dos substitutos mencionados acima sempre que o WebSocket não estiver disponível. Uma biblioteca que se tornou muito popular nesse domínio é a socket.io, que acompanha o protocolo como um cliente e um servidor e inclui substitutos. Desde fevereiro de 2012, o socket.io ainda não é compatível com mensagens binárias. Há também soluções comerciais, como PusherApp, que podem ser facilmente integradas a qualquer ambiente da Web fornecendo uma API HTTP para enviar mensagens do WebSocket aos clientes. Devido à solicitação HTTP extra, sempre haverá sobrecarga extra em comparação com o WebSocket puro.

O lado do servidor

O uso de WebSocket cria um padrão de uso totalmente novo para aplicativos do lado do servidor. Embora pilhas de servidor tradicionais como LAMP sejam projetadas em torno do ciclo de solicitação/resposta HTTP, elas geralmente não lidam bem com um grande número de conexões WebSocket abertas. Manter um grande número de conexões abertas ao mesmo tempo requer uma arquitetura que receba alta simultaneidade a um baixo custo de desempenho. Essas arquiteturas geralmente são projetadas em torno de linhas de execução ou da chamada E/S sem bloqueio.

Implementações do lado do servidor

Versões do protocolo

O protocolo de transferência (um handshake e a transferência de dados entre cliente e servidor) para WebSocket agora é RFC6455. As últimas versões do Google Chrome e do Google Chrome para Android são totalmente compatíveis com o RFC6455, incluindo mensagens binárias. Além disso, o Firefox será compatível na versão 11 e o Internet Explorer na versão 10. Você ainda pode usar versões mais antigas do protocolo, mas isso não é recomendado porque sabe-se que elas são vulneráveis. Se você tiver implementações de servidor para versões mais antigas do protocolo WebSocket, recomendamos que faça o upgrade para a versão mais recente.

Casos de uso

Use o WebSocket sempre que precisar de uma conexão quase em tempo real de baixa latência entre o cliente e o servidor. Tenha em mente que isso pode envolver repensar a forma como você cria seus aplicativos de servidor com um novo foco em tecnologias como filas de eventos. Alguns exemplos de casos de uso são:

  • Jogos on-line multiplayer
  • Aplicativos de chat
  • Mostrador de esportes ao vivo
  • Atualização em tempo real de redes sociais

Demonstrações

Referências