Apresentando WebSockets - Trazendo soquetes para a web

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

A web foi construída com base no conhecido 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. Mesmo assim, toda a comunicação HTTP era dirigida pelo cliente, o que exigia interação do usuário ou sondagem periódica para carregar novos dados do servidor.

As tecnologias que permitem que o servidor envie dados ao cliente no mesmo momento em que sabe que novos dados estão disponíveis já existem há algum tempo. Elas são conhecidas por nomes como "Push" ou "Comet". Um dos problemas mais comuns para criar a ilusão de uma conexão iniciada pelo servidor é a chamada sondagem longa. Com a sondagem longa, o cliente abre uma conexão HTTP com o servidor que a mantém aberta até que a resposta seja enviada. Sempre que o servidor realmente tem novos dados, ele envia a resposta (outras técnicas envolvem solicitações em Flash, solicitações XHR de várias partes e os chamados arquivos HTML). 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 em jogos de tiro em primeira pessoa multiplayer no navegador ou em qualquer outro jogo on-line com um componente em tempo real.

Apresentando 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 palavras simples: há uma conexão persistente entre o cliente e o servidor e ambas as partes podem começar a enviar dados a qualquer momento.

Como 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ões WebSocket seguras, 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 está aberta, quando há mensagens recebidas ou quando há 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 apenas 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 registrados no registro da IANA. Atualmente, 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 for disparado), poderemos começar a enviar dados ao servidor usando o método send('your message') no objeto de conexão. Ele costumava aceitar apenas strings, mas agora também pode enviar mensagens binárias com as especificações mais recentes. 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.

O WebSocket também pode receber mensagens binárias na especificação mais recente. Os 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. É possível encontrar extensões aceitas pelo servidor examinando a propriedade de extensões do objeto WebSocket após o evento aberto. Não há nenhuma especificação de extensão publicada oficialmente até fevereiro de 2012.

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

Comunicação entre origens

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

Servidores proxy

Cada nova tecnologia tem um novo conjunto de problemas. No caso do WebSocket, é a compatibilidade com servidores proxy que media as 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 o "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 que um determinado cliente use 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 mesmo

O WebSocket ainda é uma tecnologia jovem e não foi totalmente implementada em todos os navegadores. No entanto, você pode usar o WebSocket hoje com bibliotecas que usam um dos substitutos mencionados acima sempre que ele não estiver disponível. Uma biblioteca que se tornou muito popular nesse domínio é a socket.io, que acompanha um cliente e uma implementação de servidor do protocolo e inclui substitutos. O socket.io ainda não oferece suporte a mensagens binárias desde fevereiro de 2012. Há também soluções comerciais, como o PusherApp, que podem ser facilmente integradas a qualquer ambiente da Web fornecendo uma API HTTP para enviar mensagens 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 de servidor. Embora pilhas de servidor tradicionais, como LAMP, sejam projetadas com base no 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 com baixo desempenho. Essas arquiteturas geralmente são projetadas com base em linha de execução ou E/S sem bloqueio.

Implementações do lado do servidor

Versões do protocolo

O protocolo com fio (um handshake e a transferência de dados entre o cliente e o servidor) do WebSocket agora se chama RFC6455. As últimas versões do Google Chrome e do Google Chrome para Android são totalmente compatíveis com 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 fazer 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. Lembre-se de que isso pode envolver repensar a maneira de criar seus aplicativos do lado do servidor com um novo foco em tecnologias como filas de eventos. Alguns exemplos de casos de uso são:

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

Demonstrações

Referências