O WebSocket protocolo, descrito na especificação RFC 6455 fornece uma forma de troca de dados entre o navegador e o servidor através de uma ligação persistente. Os dados podem ser passados em ambas as direcções como “pacotes”, sem quebrar a ligação e pedidos HTTP adicionais.

WebSocket é especialmente bom para serviços que requerem troca contínua de dados, por exemplo, jogos online, sistemas de negociação em tempo real e assim por diante.

Um exemplo simples

Para abrir uma ligação websocket, precisamos de criar new WebSocket usando o protocolo especial ws na url:

let socket = new WebSocket("ws://javascript.info");

Também há encriptado wss:// protocolo. É como HTTPS para websockets.

Sempre prefere wss://

O protocolo wss:// não só é encriptado, mas também mais fiável.

Isso porque ws:// os dados não são encriptados, visíveis para qualquer intermediário. Os antigos servidores proxy não sabem sobre WebSocket, podem ver cabeçalhos “estranhos” e abortar a ligação.

Por outro lado, wss:// é WebSocket sobre TLS, (o mesmo que HTTPS é HTTP sobre TLS), a camada de segurança do transporte encripta os dados no remetente e descriptografa no receptor. Assim, os pacotes de dados são passados encriptados através de proxies. Não conseguem ver o que está dentro e deixam-nos passar.

Após a tomada ser criada, devemos ouvir os eventos nela contidos. Existem totalmente 4 eventos:

  • open – ligação estabelecida,
  • message – dados recebidos,
  • error – erro de soquete,
  • close – ligação fechada.

…E se quisermos enviar algo, então socket.send(data) fará isso.

Aqui está um exemplo:

let socket = new WebSocket("wss://javascript.info/article/websocket/demo/hello");socket.onopen = function(e) { alert(" Connection established"); alert("Sending to server"); socket.send("My name is John");};socket.onmessage = function(event) { alert(` Data received from server: ${event.data}`);};socket.onclose = function(event) { if (event.wasClean) { alert(` Connection closed cleanly, code=${event.code} reason=${event.reason}`); } else { // e.g. server process killed or network down // event.code is usually 1006 in this case alert(' Connection died'); }};socket.onerror = function(error) { alert(` ${error.message}`);};

Para efeitos de demonstração, existe um pequeno servidor servidor.js escrito em Node.js, para o exemplo acima, a correr. Responde com “Olá do servidor, John”, depois espera 5 segundos e fecha a ligação.

Assim verá eventos openmessageclose.

Na verdade é isso, já podemos falar WebSocket. Muito simples, não é?

Agora vamos falar mais a fundo.

Abrir um websocket

Quando new WebSocket(url) é criado, começa a ligar-se imediatamente.

Durante a ligação o navegador (usando cabeçalhos) pergunta ao servidor: “Você suporta Websocket? E se o servidor responder “sim”, então a conversa continua no protocolo WebSocket, que não é de todo HTTP.

Aqui está um exemplo de cabeçalhos de browser para pedido feito por new WebSocket("wss://javascript.info/chat").

GET /chatHost: javascript.infoOrigin: https://javascript.infoConnection: UpgradeUpgrade: websocketSec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==Sec-WebSocket-Version: 13

  • Origin – a origem da página do cliente, por exemplo https://javascript.info. Os objectos do WebSocket são de origem cruzada por natureza. Não há cabeçalhos especiais ou outras limitações. Os servidores antigos são incapazes de lidar com WebSocket de qualquer forma, pelo que não há problemas de compabitividade. Mas Origin cabeçalho é importante, pois permite ao servidor decidir se deve ou não falar do WebSocket com este website.
  • Connection: Upgrade – sinais de que o cliente gostaria de alterar o protocolo.
  • Upgrade: websocket – o protocolo pedido é “websocket”.
  • Sec-WebSocket-Key – uma chave gerada aleatoriamente pelo navegador para segurança.
  • Sec-WebSocket-Version – versão do protocolo WebSocket, 13 é a actual.
O aperto de mão WebSocket não pode ser emulado

Não podemos usar XMLHttpRequest ou fetch para fazer este tipo de pedido HTTP, porque não é permitido JavaScript para definir estes cabeçalhos.

Se o servidor concordar em mudar para WebSocket, deverá enviar o código 101 de resposta:

101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=

Aqui Sec-WebSocket-AcceptSec-WebSocket-Key, recodificado usando um algoritmo especial. O navegador usa-o para se certificar de que a resposta corresponde ao pedido.

Depois, os dados são transferidos usando o protocolo WebSocket, veremos a sua estrutura (“frames”) em breve. E isso não é HTTP.

Extensões e sub-protocolos

Podem existir cabeçalhos adicionais Sec-WebSocket-Extensions e Sec-WebSocket-Protocol que descrevem extensões e sub-protocolos.

por exemplo:

  • Sec-WebSocket-Extensions: deflate-frame significa que o navegador suporta compressão de dados. Uma extensão é algo relacionado com a transferência dos dados, funcionalidade que estende o protocolo WebSocket. O cabeçalho Sec-WebSocket-Extensions é enviado automaticamente pelo navegador, com a lista de todas as extensões que suporta.

  • Sec-WebSocket-Protocol: soap, wamp

significa que gostaríamos de transferir não apenas quaisquer dados, mas os dados em protocolos SOAP ou WAMP (“The WebSocket Application Messaging Protocol”). Os subprotocolos WebSocket estão registados no catálogo da IANA. Assim, este cabeçalho descreve formatos de dados que vamos utilizar.

Este cabeçalho opcional é definido usando o segundo parâmetro de new WebSocket. Este é o conjunto de subprotocolos, por exemplo, se quisermos utilizar SOAP ou WAMP:

let socket = new WebSocket("wss://javascript.info/chat", );

O servidor deve responder com uma lista de protocolos e extensões que concorda em utilizar.

Por exemplo, o pedido:

GET /chatHost: javascript.infoUpgrade: websocketConnection: UpgradeOrigin: https://javascript.infoSec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==Sec-WebSocket-Version: 13Sec-WebSocket-Extensions: deflate-frameSec-WebSocket-Protocol: soap, wamp

Resposta:

101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=Sec-WebSocket-Extensions: deflate-frameSec-WebSocket-Protocol: soap

Aqui o servidor responde que suporta a extensão “deflate-frame”, e apenas SOAP dos subprotocolos solicitados.

Transferência de dados

Comunicação WebSocket consiste em “frames” – fragmentos de dados, que podem ser enviados de ambos os lados, e podem ser de vários tipos:

  • “text frames” – contêm dados de texto que as partes enviam umas às outras.
  • “frames de dados binários” – contêm dados binários que as partes enviam umas às outras.
  • “frames de ping/pong” são utilizados para verificar a ligação, enviados a partir do servidor, o navegador responde a estes automaticamente.
  • li> há também “frame de fecho de ligação” e algumas outras frames de serviço.

No browser, trabalhamos directamente apenas com frames de texto ou binários.

WebSocket .send() o método pode enviar tanto texto como dados binários.

uma chamada socket.send(body) permite body em string ou num formato binário, incluindo BlobArrayBuffer, etc. Não são necessárias definições: basta enviá-lo em qualquer formato.

Quando recebemos os dados, o texto vem sempre como string. E para os dados binários, podemos escolher entre Blob e ArrayBuffer formatos.

Isso é definido por socket.binaryType propriedade, é "blob" por defeito, por isso os dados binários vêm como Blob objectos.

Blob é um objecto binário de alto nível, integra-se directamente com <a><img> e outras etiquetas, de modo que é um padrão são. Mas para o processamento binário, para aceder a bytes de dados individuais, podemos alterá-lo para "arraybuffer":

socket.binaryType = "arraybuffer";socket.onmessage = (event) => { // event.data is either a string (if text) or arraybuffer (if binary)};

Limitação da taxa

Imagine, a nossa aplicação está a gerar muitos dados para enviar. Mas o utilizador tem uma ligação de rede lenta, talvez numa Internet móvel, fora de uma cidade.

Podemos chamar socket.send(data) repetidamente. Mas os dados serão armazenados em buffer (armazenados) na memória e enviados apenas tão rapidamente quanto a velocidade da rede o permita.

O socket.bufferedAmount armazena quantos bytes permanecem armazenados em buffer neste momento, à espera de serem enviados através da rede.

Podemos examiná-lo para ver se o socket está realmente disponível para transmissão.

// every 100ms examine the socket and send more data// only if all the existing data was sent outsetInterval(() => { if (socket.bufferedAmount == 0) { socket.send(moreData()); }}, 100);

Fechamento da ligação

Normalmente, quando uma parte quer fechar a ligação (tanto o browser como o servidor têm direitos iguais), enviam um “frame fechado de ligação” com um código numérico e uma razão textual.

O método para isso é:

socket.close(, );

  • code é um código especial de fecho de WebSocket (opcional)
  • reason é uma string que descreve o motivo do fecho (opcional)

então a outra parte em close manipulador de eventos obtém o código e o motivo, e.g.:

// closing party:socket.close(1000, "Work complete");// the other partysocket.onclose = event => { // event.code === 1000 // event.reason === "Work complete" // event.wasClean === true (clean close)};

Valores de código mais comuns:

  • 1000 – o fecho padrão, normal (utilizado se não code fornecido),
  • 1006 – nenhuma forma de definir tal código manualmente, indica que a ligação foi perdida (sem frame de fecho).

Há outros códigos como:

  • 1001 – a festa está a ir-se embora, por exemplo o servidor está a fechar, ou um browser deixa a página,
  • 1009 – a mensagem é demasiado grande para processar,
  • 1011 – erro inesperado no servidor,
  • …e assim por diante.

A lista completa pode ser encontrada em RFC6455, §7.4.1.

códigos WebSocket são um pouco como códigos HTTP, mas diferentes. Em particular, quaisquer códigos inferiores a 1000 são reservados, haverá um erro se tentarmos definir tal código.

// in case connection is brokensocket.onclose = event => { // event.code === 1006 // event.reason === "" // event.wasClean === false (no closing frame)};

Estado de ligação

Para obter o estado de ligação, adicionalmente há socket.readyState propriedade com valores:

  • 0 – “CONEXÃO”: a ligação ainda não foi estabelecida,
  • 1 – “ABERTO”: comunicando,
  • 2 – “FECHADO”: a ligação está a fechar,
  • 3 – “FECHADO”: a ligação está fechada.

Chat exemplo

Vejamos um exemplo de chat usando o navegador WebSocket API e o módulo Node.js WebSocket https://github.com/websockets/ws. Vamos prestar a principal atenção ao lado do cliente, mas o servidor também é simples.

HTML: precisamos de um <form> para enviar mensagens e um <div> para mensagens recebidas:

<!-- message form --><form name="publish"> <input type="text" name="message"> <input type="submit" value="Send"></form><!-- div with messages --><div></div>

De JavaScript queremos três coisas:

  1. Abrir a ligação.
  2. Na submissão do formulário – socket.send(message) para a mensagem.
  3. Na mensagem recebida – anexá-la a div#messages.

Aqui está o código:

let socket = new WebSocket("wss://javascript.info/article/websocket/chat/ws");// send message from the formdocument.forms.publish.onsubmit = function() { let outgoingMessage = this.message.value; socket.send(outgoingMessage); return false;};// message received - show the message in div#messagessocket.onmessage = function(event) { let message = event.data; let messageElem = document.createElement('div'); messageElem.textContent = message; document.getElementById('messages').prepend(messageElem);}

O código do lado do servidor está um pouco fora do nosso alcance. Aqui usaremos Node.js, mas não é necessário. Outras plataformas também têm os seus meios para trabalhar com WebSocket.

O algoritmo do lado do servidor será:

    1. Criar clients = new Set() – um conjunto de tomadas.
    2. Para cada tomada aceite, adicioná-lo ao conjunto clients.add(socket) e configurar message ouvinte de eventos para receber as suas mensagens.
    3. Quando uma mensagem recebida: iterar sobre clientes e enviá-la a todos.
    4. Quando uma ligação é fechada: clients.delete(socket).
    const ws = new require('ws');const wss = new ws.Server({noServer: true});const clients = new Set();http.createServer((req, res) => { // here we only handle websocket connections // in real project we'd have some other code here to handle non-websocket requests wss.handleUpgrade(req, req.socket, Buffer.alloc(0), onSocketConnect);});function onSocketConnect(ws) { clients.add(ws); ws.on('message', function(message) { message = message.slice(0, 50); // max message length will be 50 for(let client of clients) { client.send(message); } }); ws.on('close', function() { clients.delete(ws); });}

    Aqui está o exemplo de trabalho:

    Também é possível descarregá-lo (botão superior direito no iframe) e executá-lo localmente. Só não se esqueça de instalar Node.js e npm install ws antes de correr.

    Summary

    WebSocket é uma forma moderna de ter ligações browser-servidor persistentes.

    • WebSockets não têm limitações de origem cruzada.
    • são bem suportados em browsers.
    • podem enviar/receber cordas e dados binários.

    O API é simples.

    Métodos:

    • socket.send(data),
    • socket.close(, ).

    Eventos:

    • open,
    • message,
    • error,
    • , close.

    WebSocket por si só não inclui a reconexão, autenticação e muitos outros mecanismos de alto nível. Assim, existem bibliotecas cliente/servidor para isso, e também é possível implementar estas capacidades manualmente.

    Socket às vezes, para integrar o WebSocket no projecto existente, as pessoas executam o servidor WebSocket em paralelo com o servidor HTTP principal, e partilham uma única base de dados. Pedidos para utilização do WebSocket wss://ws.site.com, um subdomínio que leva ao servidor WebSocket, enquanto https://site.com vai para o principal servidor HTTP.

    Seguramente, outras formas de integração também são possíveis.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *