Você está na página 1de 143

Machine Translated by Google

WebSocket
COMUNICAÇÕES LEVES CLIENTE-SERVIDOR

André Lombardi
www.allitebooks.com
Machine Translated by Google

WebSocket
Até recentemente, criar aplicativos semelhantes a desktop no navegador significava “Este livro percorre
usar tecnologias ineficientes Ajax ou Comet para se comunicar com o servidor. Com
uma série de úteis
este guia prático, você aprenderá como usar o WebSocket, um protocolo que permite
que cliente e servidor se comuniquem simultaneamente em uma única conexão. exemplos, facilmente aplicados
Chega de comunicação assíncrona ou pesquisas longas! para o mundo real, junto
com discussões de
Para desenvolvedores com um bom conhecimento de JavaScript (e talvez de questões que os desenvolvedores
Node.js), o autor Andrew Lombardi fornece exemplos práticos úteis ao longo do livro
encontrará ao trabalhar
para ajudá-lo a se familiarizar com a API WebSocket. Você também aprenderá como
com o WebSocket
usar WebSocket com Transport Layer Security (TLS).
protocolo."
ÿ Aprenda como usar eventos, mensagens, atributos e —Joseph B. Ottinger
e métodos em seu aplicativo cliente Engenheiro Sênior, Edifecs, Inc.

ÿ Crie aplicativos de bate-papo bidirecionais no cliente e no servidor com


“Uma introdução completa
WebSocket como camada de comunicação
aos conceitos do WebSocket
ÿ Crie um subprotocolo sobre WebSocket para STOMP 1.0, o Simple
Text Oriented Messaging Protocol e implementação
ÿ Use opções para navegadores mais antigos que não oferecem suporte nativo detalhes."
WebSocket —Arun Gupta
Diretor de Defesa do Desenvolvedor, Red Hat
ÿ Proteja seu aplicativo WebSocket contra vários ataques
vetores com TLS e outras ferramentas

ÿ Depurar aplicativos aprendendo aspectos do WebSocket


vida útil

Andrew Lombardi, proprietário da empresa de consultoria Mystic Coders, passou os últimos seis
anos dando dezenas de palestras em conferências por toda a América do Norte e Europa sobre
tópicos que vão desde desenvolvimento backend em Java e HTML5 até construção para
dispositivos móveis usando apenas JavaScript.

JAVASCRIPT / LINGUAGENS DE PROGRAMAÇÃO


Twitter: @oreillymedia
facebook.com/oreilly
US$ 24,99 PODE $ 28,99
ISBN: 978-1-449-36927-9

www.allitebooks.com
Machine Translated by Google

WebSocket

André Lombardi

Boston

www.allitebooks.com
Machine Translated by Google

WebSocket

por Andrew Lombardi

Copyright © 2015 Mystic Coders, LLC. Todos os direitos reservados.

Impresso nos Estados Unidos da América.

Publicado por O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.

Os livros da O'Reilly podem ser adquiridos para uso educacional, comercial ou promocional de vendas. Edições online também estão
disponíveis para a maioria dos títulos (http://safaribooksonline.com). Para obter mais informações, entre em contato com nosso
departamento de vendas corporativas/institucionais: 800-998-9938 ou corporate@oreilly.com.

Editores: Simon St. Laurent e Brian MacDonald Indexadora: Wendy Catalano


Editor de Produção: Colleen Lobner Designer de Interiores: David Futato
Editor de texto: Kim Cofer Designer da capa: Karen Montgomery
Revisora: Sharon Wilkey Ilustradora: Rebecca Demarest

Setembro de 2015: Primeira edição

Histórico de revisões da primeira edição


04/09/2015: Primeiro lançamento

Consulte http://oreilly.com/catalog/errata.csp?isbn=9781449369279 para detalhes do lançamento.

O logotipo O'Reilly é uma marca registrada da O'Reilly Media, Inc. WebSocket, a imagem da capa de uma anêmona do mar e a
imagem comercial relacionada são marcas registradas da O'Reilly Media, Inc.

Embora a editora e o autor tenham envidado esforços de boa fé para garantir que as informações e instruções contidas nesta obra
sejam precisas, a editora e o autor isentam-se de qualquer responsabilidade por erros ou omissões, incluindo, sem limitação,
responsabilidade por danos resultantes do uso ou confiança neste trabalho. O uso das informações e instruções contidas neste
trabalho é por sua conta e risco. Se quaisquer exemplos de código ou outra tecnologia que este trabalho contém ou descreve estão
sujeitos a licenças de código aberto ou aos direitos de propriedade intelectual de terceiros, é sua responsabilidade garantir que seu
uso esteja em conformidade com tais licenças e/ou direitos.

978-1-449-36927-9

[LSI]

www.allitebooks.com
Machine Translated by Google

Índice

Prefácio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix

1. Início rápido. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Obtendo Node e npm 2

Instalando no Windows 2

Instalando no OS X 2

Instalando no Linux Olá, 2

mundo! Exemplo Por que 3

WebSocket? 7

Resumo 8

2. API WebSocket. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Inicializando 9

IU de exemplo de estoque 11
Eventos WebSocket 12

Evento: Aberto 13

Evento: Mensagem 14
Evento: Erro 15
Evento: PING/PONG 15
Evento: Fechar 15
Métodos WebSocket 16
Método: Enviar 16
Método: Fechar 17
Atributos do WebSocket 18

Atributo: readyState 18
Atributo: bufferedAmount 19

Atributo: protocolo 19

Servidor de exemplo de estoque 19

iii

www.allitebooks.com
Machine Translated by Google

Teste de suporte WebSocket 21


Resumo 21

3. Bate-papo bidirecional. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Sondagem Longa 23
Escrevendo um aplicativo de bate-papo básico 24
Cliente WebSocket 27
Identidade do cliente 27
Eventos e Notificações 29
O servidor 30
O cliente 31
Resumo 34

4. STOMP sobre WebSocket. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35


Implementando STOMP 36
Conectando-se 36
Conectando através do servidor 39
Configurando o RabbitMQ 42
Conectando o servidor ao RabbitMQ 44
O Daemon do Preço das Ações 47
Processando solicitações STOMP 49
Cliente 50
Usando RabbitMQ com Web-Stomp 56
Cliente STOMP para Web e Node.js 57
Instalando o plug-in Web-Stomp Echo 57
Client para Web-Stomp Resumo 57
59

5. Compatibilidade com WebSocket. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61


SockJS 62
SockJS Servidor de bate- 63
papo SockJS Cliente de 66
bate-papo 66
Socket.IO Adobe Flash 67
Socket Conexão 67
Socket.IO Servidor de bate- 68
papo Socket.IO Cliente de bate-papo 69
Pusher.com 70
Canais 71
Eventos 72
Pusher Chat Server 73
Pusher Chat Client 76

iv | Índice

www.allitebooks.com
Machine Translated by Google

Não se esqueça: Pusher é uma solução comercial 78

Proxy reverso 78

Resumo 78

6. Segurança WebSocket. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
TLS e WebSocket 79

Gerando um certificado autoassinado 79

Instalando no Windows 80

Instalando no OS X 80

Instalando no Linux 80

Configurando WebSocket por TLS 80

Exemplo de servidor WebSocket sobre TLS 82

Modelo de segurança baseado na origem 83

Sequestro de cliques 85

Opções de X-Frame para Framebusting 86


Negação de serviço 87

Máscara de quadro 87

Validando Clientes 88

Configurando Dependências e Inits 88

Ouvindo solicitações da Web 89


Servidor WebSocket 91

Resumo 92

7. Depuração e ferramentas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
O aperto de mão 95
O servidor 96
O cliente 97

Baixe e configure o ZAP 99


WebSocket seguro para o resgate 102

Validando o aperto de mão 102

Inspecionando Quadros 103

Cargas Mascaradas 103

Fechando conexão 108

Resumo 109

8. Protocolo WebSocket. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111


HTTP 0.9 — Nasce a Web HTTP 1.0 111
e 1.1 WebSocket Open 111

Handshake Sec-WebSocket-Key e 112

Sec-WebSocket-Accept WebSocket Cabeçalhos HTTP 113


WebSocket Frame 114
116

Índice | v

www.allitebooks.com
Machine Translated by Google

Barbatana 117
Códigos de operação de quadro
117

Mascaramento 118

Comprimento
118

Fragmentação 119
Aperto de mão próximo do WebSocket 119
Subprotocolos WebSocket 121
Extensões WebSocket 122

Implementações de servidores alternativos 123

Resumo 124

Índice. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

nós
| Índice

www.allitebooks.com
Machine Translated by Google

Foi Joaquim

www.allitebooks.com
Machine Translated by Google

www.allitebooks.com
Machine Translated by Google

Prefácio

A Web cresceu.

Antigamente, costumávamos codificar sites ricos em design usando uma confusão interminável de
tabelas aninhadas. Hoje podemos usar uma abordagem baseada em padrões com Cascading Style
Sheets (CSS) para obter designs que não eram possíveis na infância da Web. Assim como o CSS
inaugurou uma nova era de capacidade e legibilidade para os aspectos de design de um site, o
WebSocket pode fazer isso para comunicação bidirecional com o back-end.

O WebSocket fornece uma abordagem baseada em padrões para codificação para comunicação
bidirecional full-duplex que substitui os antigos hacks como Comet e long polling.
Hoje temos a capacidade de criar aplicativos semelhantes a desktop em um navegador sem recorrer
a métodos que esgotem os recursos do servidor.

Neste livro, você aprenderá maneiras simples de fornecer comunicação bidirecional entre servidor e
cliente, sem fazer o pessoal de TI chorar.

Quem deveria ler esse livro


Este livro é para programadores que desejam criar aplicações Web que possam se comunicar
bidirecionalmente entre servidor e cliente e que desejam evitar o uso de hacks que prevalecem na
Web hoje. A promessa do WebSocket é uma maneira melhor, baseada em padrões e suportada por
todos os navegadores modernos, com opções sensatas de fallback para aqueles que precisam de
suporte. Para aqueles que ainda não consideraram o Web-Socket, anote o tutorial do Comet que
você está lendo.

Este livro é apropriado para usuários novatos e experientes. Presumo que você tenha experiência
em programação e esteja familiarizado com JavaScript. Experiência com Node.js é útil, mas não
obrigatória. Este livro também beneficiará aqueles encarregados de manter servidores que executam
código WebSocket e são responsáveis por garantir a segurança da infraestrutura. Você precisa
conhecer as possíveis armadilhas da integração do WebSocket e o que isso significa para você. Os
capítulos anteriores podem ser menos úteis para

ix
Machine Translated by Google

você, mas os últimos três capítulos lhe darão conhecimento suficiente para saber o que está acontecendo
em sua rede.

Objetivos deste livro


Estive nas trincheiras e tive que implementar hacks aceitáveis para obter comunicação bidirecional para
clientes que precisavam da funcionalidade. Espero poder mostrar-lhe um caminho melhor, baseado em
padrões e simples de implementar. Para vários clientes ao longo dos anos, implantei com sucesso a
abordagem deste livro para comunicação com o back-end usando WebSocket em vez de pesquisas longas
e alcancei os objetivos que buscava.

Navegando neste livro


Costumo ler um livro folheando e retirando as peças relevantes para usar como referência durante a
codificação. Se você estiver realmente lendo este prefácio, a lista a seguir lhe dará uma ideia aproximada
dos objetivos de cada capítulo:

• Os capítulos 1 e 2 fornecem um guia de início rápido com instruções sobre as dependências necessárias
ao longo do livro e apresentam a API JavaScript. • O Capítulo 3 apresenta um exemplo

completo com código cliente e servidor usando chat. • No Capítulo 4 você escreve sua própria

implementação de um protocolo padrão e


coloque-o em cima do WebSocket.

• O Capítulo 5 é essencial para quem precisa de suporte a navegadores mais antigos. •

Finalmente, os Capítulos 6 a 8 abordam aspectos de segurança, depuração e uma visão geral do protocolo.

Convenções utilizadas neste livro


As seguintes convenções tipográficas são usadas neste livro:

Itálico
Indica novos termos, URLs, endereços de e-mail, nomes de arquivos e extensões de arquivos.

Largura constante

Usada para listagens de programas, bem como dentro de parágrafos para se referir a elementos do
programa, como nomes de variáveis ou funções, bancos de dados, tipos de dados, variáveis de
ambiente, instruções e palavras-chave.

Largura constante negrito

Mostra comandos ou outro texto que deve ser digitado literalmente pelo usuário.

x | Prefácio
Machine Translated by Google

Largura constante em itálico


Mostra o texto que deve ser substituído por valores fornecidos pelo usuário ou por valores
determinados pelo contexto.

Este elemento significa uma nota geral.

Usando exemplos de código


Material suplementar (exemplos de código, exercícios, etc.) está disponível para download em https://
github.com/kinabalu/websocketsbook.

Este livro está aqui para ajudá-lo a realizar seu trabalho. Em geral, se um código de exemplo for
oferecido com este livro, você poderá usá-lo em seus programas e documentação. Você não precisa
entrar em contato conosco para obter permissão, a menos que esteja reproduzindo uma parte
significativa do código. Por exemplo, escrever um programa que utilize vários trechos de código deste
livro não requer permissão. Vender ou distribuir um CD-ROM com exemplos de livros da O'Reilly
requer permissão. Responder a uma pergunta citando este livro e citando código de exemplo não
requer permissão. Incorporar uma quantidade significativa de código de exemplo deste livro na
documentação do seu produto requer permissão.

Agradecemos, mas não exigimos, atribuição. Uma atribuição geralmente inclui título, autor, editora e
ISBN. Por exemplo: “WebSocket de Andrew Lombardi (O'Reilly). Copyright 2015 Mystic Coders, LLC,
978-1-4493-6927-9.”

Se você acha que o uso de exemplos de código está fora do uso justo ou da permissão dada acima,
sinta-se à vontade para nos contatar em permissions@oreilly.com.

Livros on-line do Safari®

Livros on-line do Safari é uma biblioteca digital sob demanda que oferece
conteúdo especializado em formato de livro e vídeo dos principais autores
mundiais em tecnologia e negócios.

Profissionais de tecnologia, desenvolvedores de software, web designers e profissionais de negócios


e criativos usam Safari Books Online como principal recurso para pesquisa, resolução de problemas,
aprendizagem e treinamento de certificação.

Safari Books Online oferece uma variedade de planos e preços para empresa, governo, Educação, e
indivíduos.

Prefácio | XI
Machine Translated by Google

Os membros têm acesso a milhares de livros, vídeos de treinamento e manuscritos de pré-


publicação em um banco de dados totalmente pesquisável de editoras como O'Reilly Media,
Prentice Hall Professional, Addison-Wesley Professional, Microsoft Press, Sams, Que, Peachpit
Press, Focal Press, Cisco Press, John Wiley & Sons, Syngress, Morgan Kaufmann, IBM
Redbooks, Packt, Adobe Press, FT Press, Apress, Manning, New Riders, McGraw-Hill, Jones &
Bartlett, Course Technology e centenas de outros . Para obter mais informações sobre o Safari
Books Online, visite-nos online.

Como entrar em contato conosco

Por favor, envie comentários e perguntas sobre este livro à editora:

O'Reilly Media, Inc.


1005 Gravenstein Highway North
Sebastopol, CA 95472
800-998-9938 (nos Estados Unidos ou Canadá)
707-829-0515 (internacional ou local)
707-829-0104 (fax)

Temos uma página web para este livro, onde listamos erratas, exemplos e qualquer informação
adicional. Você pode acessar esta página em http://bit.ly/orm-websocket.

Para comentar ou tirar dúvidas técnicas sobre este livro, envie um e-mail para
bookquestions@oreilly.com .

Para obter mais informações sobre nossos livros, cursos, conferências e notícias, consulte
nosso site em http://www.oreilly.com.

Encontre-nos no Facebook: http://facebook.com/

oreilly Siga-nos no Twitter: http://twitter.com/oreillymedia

Assista-nos no YouTube: http://www.youtube.com/oreillymedia

Agradecimentos
Muitas pessoas tornaram este livro possível, incluindo meu maravilhoso e paciente editor Brian
MacDonald. A todos na O'Reilly que ajudaram a fazer este livro acontecer, um profundo e
profundo agradecimento.

Gostaria também de agradecer aos meus revisores técnicos pelas suas valiosas contribuições
e conselhos: Joe Ottinger e Arun Gupta. E obrigado a vocês que enviaram erratas na prévia do
livro para que pudéssemos resolvê-los antes de irmos para a produção.

Obrigado à mamãe e ao papai por colocarem um computador na minha frente e abrirem um


universo cada vez maior de criatividade e admiração.

xii | Prefácio
Machine Translated by Google

CAPÍTULO 1

Começo rápido

A API e o protocolo WebSocket são definidos na RFC 6455. O WebSocket oferece um canal de
comunicação bidirecional full-duplex que opera sobre HTTP por meio de um único soquete.

Os hacks existentes executados em HTTP (como pesquisas longas) enviam solicitações em intervalos,
independentemente de as mensagens estarem disponíveis, sem qualquer conhecimento do estado do
servidor ou cliente. A API WebSocket, entretanto, é diferente: o servidor e o cliente têm uma conexão
aberta a partir da qual podem enviar e receber mensagens. Para os preocupados com a segurança, o
WebSocket também opera sobre Transport Layer Security (TLS) ou Secure Sockets Layer (SSL) e é o
método preferido, como você verá no Capítulo 6.

Este livro ajudará você a entender a API WebSocket, o protocolo e como usá-lo em suas aplicações
web hoje.

Neste livro, JavaScript é usado para todos os exemplos de código. Node.js é usado para todo o código
do servidor e para códigos ou testes ocasionais do cliente quando um navegador não é adequado
para obter uma noção de funcionalidade. Para entender os exemplos, você precisará de algum nível
de proficiência em JavaScript. Se você quiser estudar JavaScript, recomendo JavaScript: e Good Parts
(O'Reilly) de Douglas Crockford.

O Node.js tem sido tão predominante nos últimos anos que as barreiras de entrada para os exemplos
deste livro são notavelmente baixas. Se você já fez algum desenvolvimento para a Web, há boas
chances de você ter desenvolvido em JavaScript ou pelo menos entendê-lo. O uso de Node.js e
JavaScript, portanto, visa apenas simplificar o processo de ensino e não deve ser interpretado como
um requisito para um projeto WebSocket.

Estão disponíveis bibliotecas e servidores que suportam WebSocket em quase todas as configurações
possíveis. O Capítulo 5 cobre diversas opções para implantar um recurso compatível com WebSocket

1
Machine Translated by Google

servidor, incluindo métodos de fallback para clientes que ainda não oferecem suporte para esta
tecnologia.

Obtendo Node e npm


Para garantir que você possa executar todos os exemplos do livro, recomendo fortemente que você
instale o Node.js e o npm em seu ambiente de desenvolvimento. Embora você possa aprender
tudo sobre o WebSocket sem mexer em nenhum código, eu não o recomendo. As seções a seguir
indicam algumas maneiras simples de colocar o Node em seu ambiente.

Instalação no Windows

Abordo apenas o download e a instalação do binário pré-compilado disponível no Windows. Se


você é masoquista e gostaria de compilá-lo sozinho, siga as instruções.

Para o restante de vocês, baixe o executável independente do Windows. Em seguida, pegue o


arquivo .zip mais recente do npm. Descompacte o npm .zip e coloque o node.exe baixado em um
diretório que você adiciona ao seu PATH. Você poderá executar scripts e instalar módulos usando
Node.js e npm, respectivamente.

Instalando no OS X

Dois dos métodos mais fáceis de instalar o Node.js e o npm são por meio de um pacote pré-
compilado para download ou por meio de um gerenciador de pacotes. Minha preferência é usar um
gerenciador de pacotes como o Homebrew para colocar o Node.js em sua máquina. Isso permite
uma atualização rápida e fácil sem a necessidade de baixar novamente um pacote da Web.
Supondo que você tenha o Homebrew instalado, execute este comando:

nó de instalação do brew

E se preferir usar os binários pré-compilados disponíveis, você pode encontrar o download no site
do Node.js. Quando você quiser instalar uma versão atualizada do Node.js, baixe e instale o pacote
mais recente e ele substituirá os binários existentes.

Instalando no Linux

Como existem mais sabores de Linux do que estrelas no céu, vou descrever apenas como compilá-
lo você mesmo e como obtê-lo via apt no Ubuntu. Se você estiver executando outra distribuição e
quiser usar o gerenciador de pacotes disponível em seu tipo específico, visite o wiki do Node.js.
para obter instruções sobre a instalação.

2 | Capítulo 1: Início Rápido


Machine Translated by Google

Usar o apt para instalar o Node.js requer algumas etapas simples:

sudo apt-get update


sudo apt-get install python-software-properties python g++ make sudo add-apt-
repository ppa:chris-lea/node.js sudo apt-get update
sudo apt-get install
nodejs

Isso instala o Node.js estável atual em sua distribuição Ubuntu, pronto para liberar JavaScript do
navegador e permitir que você escreva algum código do lado do servidor.

Se você quiser compilá-lo sozinho, supondo que o Git já esteja instalado e disponível em sua máquina,
digite o seguinte:

git clone git://github.com/joyent/node.git cd node git


checkout
v0.10.7 ./configure &&
make && make install

Verifique http://nodejs.org/ para obter a versão mais recente do


Node.js em seu sistema.

Olá Mundo! Exemplo


Ao abordar um novo tópico em desenvolvimento, prefiro começar com um exemplo rapidamente.
Portanto, usaremos o exemplo testado em batalha entre idiomas — “Hello, World!” — para iniciar uma
conexão com um servidor Node.js compatível com WebSocket e receber a saudação após a conexão.

História de Olá, mundo!


A encarnação inicial da primeira aplicação de todos em uma nova linguagem/tecnologia foi
escrita pela primeira vez em 1972, “A Tutorial Introduction to the Language B.” de Brian
Kernighan. O aplicativo foi utilizado para ilustrar variáveis externas da linguagem.

Você começará escrevendo o código que inicia um servidor compatível com WebSocket na porta 8181.
Primeiro, você usará o idioma CommonJS e exigirá o módulo ws e atribuirá essa classe ao objeto
WebSocketServer . Em seguida, você chamará o construtor com seu objeto de inicialização, que
consiste na definição da porta ou que contém a definição da porta.

O protocolo WebSocket é essencialmente um recurso de passagem de mensagens. Para começar,


você ouvirá um evento chamado connection. Ao receber um evento de conexão , o objeto WebSocket
fornecido será usado para enviar de volta a mensagem “Hello, World!” saudações.

Olá Mundo! Exemplo | 3


Machine Translated by Google

Para tornar a vida um pouco mais simples, e porque não gosto de reinventar a roda, será usada a maravilhosa
biblioteca WebSocket chamada ws . A biblioteca ws pode aliviar muita dor de cabeça ao escrever um servidor (ou
cliente) WebSocket, oferecendo uma API simples e limpa para seu aplicativo Node.js.

Instale-o usando npm:

npm instalar ws

Outra opção popular é usar a biblioteca WebSocket-Node.

Todos os exemplos deste livro assumirão que o código-fonte existe em uma pasta indicada pelo nome abreviado do
capítulo, então crie um diretório chamado ch1. Agora crie um novo arquivo chamado server.js no editor de sua
preferência e adicione este código para sua aplicação:

var WebSocketServer = require('ws').Server, wss =


new WebSocketServer({porta: 8181});

wss.on('conexão', function(ws)
{ console.log('cliente conectado');
ws.on('mensagem', function(mensagem)
{ console.log(mensagem);

}); });

Curto e direto ao ponto. Em seguida, execute o servidor para que ele esteja escutando o cliente que você está prestes
a codificar:

nó server.js

Crie um arquivo para o cliente chamado client.html e coloque-o no mesmo diretório do arquivo do servidor. Com este
exemplo simples, o cliente pode ser hospedado em qualquer lugar, até mesmo executado a partir do protocolo file:// .
Nos capítulos posteriores, você usará bibliotecas HTTP e precisará de um foco mais centrado na Web para
gerenciamento de arquivos e diretórios.

Nesta primeira passagem, entretanto, você usará uma página HTML básica para chamar o servidor WebSocket.
A estrutura da página HTML é um formulário simples, com um campo de texto e um botão para iniciar o envio. Os dois
métodos de envio de sua mensagem serão enviando um formulário (via Return/Enter) ou clicando no botão Enviar!
botão. Em seguida, você adicionará uma ação no envio do formulário e o evento onclick do botão para chamar a
função JavaScript sendMessage . Uma coisa a observar é que o código retorna false no envio do formulário para que
a página não seja atualizada.

A inicialização do WebSocket é bastante simples; você inicia uma conexão com um servidor Web-Socket na porta
8181 em localhost. A seguir, como a API WebSocket é baseada em eventos (mais sobre isso posteriormente), você
define uma função para o evento onopen gerar uma mensagem de status para uma conexão bem-sucedida com o
servidor. A função sendMessage apenas precisa chamar a função send na variável ws e capturar o valor dentro do
campo de texto da mensagem .

4 | Capítulo 1: Início Rápido


Machine Translated by Google

E pronto! Você tem seu primeiro exemplo de WebSocket.


<!DOCTYPE html>
<html lang="en">
<head>
<title>Demonstração de eco do WebSocket
</title> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, escala inicial=1"> <link
rel="stylesheet" href="http://bit.ly/cdn-bootstrap-css"> <link rel="stylesheet"
href="http://bit.ly/cdn-bootstrap-theme"> <script src="http://bit.ly/cdn-bootstrap-
jq"></script> <script> var ws = new WebSocket("ws://localhost:8181");
ws.onopen
= function(e) { console.log('Conexão com o servidor
aberta');

function sendMessage()
{ ws.send($('#message').val());

} </script>
</head>
<body lang="en">
<div class="vertical-center"> <div
class="container">
<p>&nbsp;</p>
<form role="form" id="chat_form" onsubmit="sendMessage(); return false;">
<div class="form-group">
<input class="form-control" type="text" name="message" id="message"
placeholder="Digite o texto para ecoar aqui" value="" autofocus/ > </div>
<button
type="button" id="send" class="btn btn-primary"
onclick="sendMessage();">Enviar!</button> </
form> </
div> </
div>
<script src="http://bit.ly/cdn-bootstrap-minjs"></script> </body> </html>

Ao longo do livro você usará duas maravilhosas bibliotecas predominantes na Web para exibição
e interação:

• Inicialização 3

• jQuery

Olá Mundo! Exemplo | 5


Machine Translated by Google

Em exemplos posteriores, dispensaremos a inclusão do script e das


tags de estilo CSS em favor da brevidade. Você pode usar o HTML
anterior como modelo para exemplos futuros e apenas remover o
conteúdo da tag <script> personalizada e o conteúdo entre as tags
<body> , mantendo intacta a inclusão do Bootstrap JavaScript.

Com isso, abra a página HTML no seu navegador preferido (sugiro Google Chrome ou Mozilla Firefox). Envie uma
mensagem e veja-a aparecer na saída do console do seu servidor.

Se você estiver usando o Chrome, ele possui um excelente recurso para visualizar conexões WebSocket na página
inicial. Vamos fazer isso agora. No menu hotdog, escolha Ferramentas ÿ Ferramentas de desenvolvedor (no
Windows: F12, Ctrl-Shift-I; em um Mac ÿ-ÿ-I).

A Figura 1-1 mostra as ferramentas para desenvolvedores do Google Chrome filtradas para chamadas WebSocket.
Echo é o primeiro aplicativo a ser escrito no espaço de rede.

Figura 1-1. Ferramentas para desenvolvedores do Chrome – guia Rede

Selecione a guia Rede e atualize o HTML de exemplo. Na tabela você deverá ver uma entrada para o HTML e uma
entrada para a conexão WebSocket com status “101 Protocolos de comutação”. Se você selecioná-lo, verá os
cabeçalhos de solicitação e os cabeçalhos de resposta para esta conexão:

GET ws://localhost:8181/ HTTP/1.1


Pragma: sem cache
Origem: null
Host: localhost:8181
Sec-WebSocket-Key: qalODNsUoRp+2K9FJty55Q==
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3)...

6 | Capítulo 1: Início Rápido

www.allitebooks.com
Machine Translated by Google

Atualização: websocket
Extensões Sec-WebSocket: x-webkit-deflate-frame
Controle de cache: sem cache
Conexão: Atualização
Versão Sec-WebSocket: 13

Conexão de protocolos de comutação


HTTP/1.1 101:
Atualização Sec-WebSocket-Accept:
nambQ7W9imtAIYpzsw4hNNuGD58= Atualização: websocket

Se você está acostumado a ver cabeçalhos HTTP, isso não deve ser diferente. Temos alguns cabeçalhos
extras aqui, incluindo Connection: Upgrade, Sec-Websocket-Key, Upgrade: websocket, que explicarei mais
detalhadamente no Capítulo 8. Por enquanto, saboreie seu primeiro exemplo de WebSocket e prepare-se
para saber o porquê O WebSocket deve estar no seu radar para o seu próximo projeto.

Por que WebSocket?


A capacidade atual de criar aplicativos semelhantes a desktop no navegador é alcançada principalmente
usando Comet e Ajax. Para usar qualquer uma das soluções, os desenvolvedores confiaram em hacks em
servidores e clientes para manter as conexões abertas por mais tempo e falsificar uma conexão de longa
duração.

Embora esses hacks funcionem tecnicamente, eles criam problemas de alocação de recursos nos servidores.
Com os métodos existentes, a latência percebida pelo usuário final pode ser baixa, mas a eficiência no
backend deixa muito a desejar. A pesquisa longa faz solicitações desnecessárias e mantém um fluxo
constante de abertura e fechamento de conexões para seus servidores lidarem. Não há facilidade para
colocar outros protocolos em camadas sobre Comet ou Ajax e, mesmo que você pudesse, a simplicidade
simplesmente não existe.

O WebSocket oferece a capacidade de usar uma solicitação HTTP atualizada (o Capítulo 8 aborda os
detalhes) e enviar dados de maneira baseada em mensagens, semelhante ao UDP e com toda a confiabilidade
do TCP. Isso significa uma conexão única e a capacidade de enviar e receber dados entre cliente e servidor
com penalidades insignificantes na utilização de recursos. Você também pode colocar outro protocolo em
camadas sobre o WebSocket e fornecê-lo de maneira segura por TLS. Os capítulos posteriores se aprofundam
nesses e em outros recursos, como pulsação, domínio de origem e muito mais.

Uma das armadilhas comuns de escolher entre WebSocket e pesquisas longas era o triste estado do suporte
do navegador. Hoje, o estado do suporte do navegador para WebSocket é muito melhor para o usuário final.

A Tabela 1-1 mostra o estado atual do suporte do navegador para WebSocket. Para obter informações mais
atualizadas sobre o suporte WebSocket, você pode consultar o site Can I Use.

Por que WebSocket? | 7


Machine Translated by Google

Tabela 1-1. O estado do suporte ao navegador WebSocket

Navegador Sem suporte Suporte parcial Apoio total

Ou seja
Versões 8.0, 9.0 Versão 10.0 e superior

Raposa de fogo
Versão 27.0 e superior

cromada Versão 31.0 e superior

Safári Versão 7 e superior

Ópera Versão 20.0 e superior

Safári iOS Versões 3.2, 4.0–4.1 Versões 4.2–4.3, 5.0–5.1 Versão 6.0 e superior

Ópera Mini Versões 5.0–7.0

Navegador Android Versões 2.1–4.3 Versão 4.4

Navegador BlackBerry Versões 7.0, 10.0

Ou seja, celular Versão 10.0

Como você descobrirá no Capítulo 5, você pode mitigar a falta de suporte em sobrancelhas mais antigas.
ers para WebSocket nativo usando bibliotecas de estrutura como SockJS ou Socket.IO.

Resumo
Este capítulo apresentou o WebSocket e como construir um servidor de eco simples usando
Node.js. Você viu como construir um cliente simples para testar seu servidor WebSocket,
junto com uma maneira simples de testar seu servidor WebSocket usando o Chrome Developer
Ferramentas. Os próximos capítulos exploram a API WebSocket e o protocolo, e você
aprenda como colocar outros protocolos em camadas sobre o WebSocket para ter ainda mais poder.

8 | Capítulo 1: Início Rápido


Machine Translated by Google

CAPÍTULO 2

API WebSocket

Este capítulo expõe os detalhes por trás do uso da interface de programação de aplicativos (API)
WebSocket. WebSocket é um canal de comunicação assíncrona full-duplex orientado a eventos para
suas aplicações web. Ele tem a capacidade de fornecer atualizações em tempo real que, no passado,
você usaria pesquisas longas ou outros hacks para obter. O principal benefício é reduzir as
necessidades de recursos tanto no cliente quanto (mais importante) no servidor.

Embora o WebSocket use HTTP como mecanismo de transporte inicial, a comunicação não termina
depois que uma resposta é recebida pelo cliente. Usando a API WebSocket, você pode se libertar
das restrições do típico ciclo de solicitação/resposta HTTP. Isso também significa que, enquanto a
conexão permanecer aberta, o cliente e o servidor poderão enviar mensagens livremente de forma
assíncrona, sem pesquisar nada de novo.

Ao longo deste capítulo, você construirá um cliente simples de cotações de ações usando WebSocket
como transporte de dados e aprenderá sobre sua API simples no processo. Você criará uma nova
pasta de projeto, ch2, para armazenar todo o código deste capítulo. O código do seu cliente estará
em um arquivo chamado client.html e o código do servidor em um arquivo chamado server.js.

Inicializando
O construtor do WebSocket requer uma URL para iniciar uma conexão com o servidor. Por padrão,
se nenhuma porta for especificada após o host, ele se conectará pela porta 80 (a porta HTTP) ou pela
porta 443 (a porta HTTPS).

Se você já estiver executando um servidor Web tradicional na porta 80, precisará usar um servidor
que entenda e possa fazer proxy da conexão WebSocket ou que possa passar a conexão para seu
aplicativo personalizado. O Capítulo 5 apresenta uma opção popular usando nginx para passar por
uma conexão atualizada com seu servidor baseado em Node.js.

9
Machine Translated by Google

Por enquanto, como você executará o servidor WebSocket localmente, sem um servidor web fazendo
proxy da conexão, você pode simplesmente inicializar o servidor Web nativo do navegador.
Objeto Socket com o seguinte código:

var ws = new WebSocket("ws://localhost:8181");

Agora você tem um objeto WebSocket chamado ws que pode ser usado para escutar eventos. A seção
“Eventos WebSocket” na página 12 detalha vários eventos disponíveis para escuta.
A Tabela 2-1 lista os parâmetros do construtor disponíveis com WebSocket.

Tabela 2-1. Parâmetros do construtor WebSocket


Nome do parâmetro Descrição

URL ws:// ou wss:// (se estiver usando TLS)

protocol (opcional) Parâmetro que especifica subprotocolos que podem ser usados como um array ou string única

O segundo parâmetro opcional no construtor WebSocket são os protocolos, passados nos cabeçalhos
como Sec-WebSocket-Protocol. Pode ser uma única sequência de protocolo ou uma matriz de sequências
de protocolo. Eles indicam subprotocolos, portanto, um único servidor pode implementar vários
subprotocolos WebSocket. Se nada for passado, uma string vazia será assumida. Se forem fornecidos
subprotocolos e o servidor não aceitar nenhum deles, a conexão não será estabelecida. No Capítulo 4
você construirá um subprotocolo para STOMP e aprenderá como usá-lo no WebSocket.

Se houver uma tentativa de iniciar uma conexão WebSocket usando HTTPS no site de origem, mas
usando o método de protocolo não TLS ws ://, um SECURITY_ERR será lançado. Além disso, você
receberá o mesmo erro se tentar se conectar a um servidor WebSocket por meio de uma porta à qual o
agente do usuário bloqueia o acesso (normalmente 80 e 443 são sempre permitidos).

A seguir está uma lista de tipos de protocolo disponíveis para uso com WebSocket:

Protocolos registrados
Nas especificações do WebSocket RFC 6455, a seção 11.5 define o registro de nomes de
subprotocolos para registros mantidos pela IANA.

Protocolos
abertos Além disso, você pode usar protocolos abertos que não estão registrados, como Extensible
Messaging and Presence Protocol (XMPP) ou Simple Text Oriented Message Protocol (STOMP) e
vários outros.

Protocolos
personalizados Você é livre para criar qualquer protocolo que desejar, desde que seu servidor e
cliente o suportem. É recomendado que você use nomes que contenham o formato ASCII

10 | Capítulo 2: API WebSocket


Machine Translated by Google

versão do nome de domínio do originador do subprotocolo; por exemplo, chat.acme.com.

IU de exemplo de estoque

O exemplo que você construirá depende de dados estáticos para facilitar sua vida. Seu servidor terá
uma lista de símbolos de ações com valores predefinidos e randomizará as alterações de preço em
um espectro de pequenos valores positivos/negativos.

Para mostrar uma interface de usuário mais limpa e facilitar o processo de modificação de CSS, você
usará o Bootstrap do Twitter . e jQuery. Copie e cole o conteúdo do seguinte trecho de código em
seu arquivo client.html:

<!DOCTYPE html>
<html lang="en"><head>
<title> Gráfico de ações sobre WebSocket</title>

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta
name="viewport" content="width=device-width, escala inicial =1"> <link rel="stylesheet"
href="http://bit.ly/cdn-bootstrap-css"> <link rel="stylesheet" href="http://bit.ly/
cdn-bootstrap -theme"> <script src="http://bit.ly/cdn-bootstrap-jq"></script>
<script language="text/javascript"> // o código do capítulo vai aqui
</script> < /head> <body lang="en"> <div
class="vertical-center"> <div

class="container">

<h1>Gráfico de ações sobre WebSocket</


h1> <table class="table" id="stockTable">
<thead>
<tr>
<th>Símbolo</th>
<th>Preço</th>
</tr>
</thead>
<tbody id="stockRows">
<tr>
<td><h3>AAPL</h3></td>
<td id="AAPL">
<h3><span class="label label-default">95,00</h3> </td> </tr> <tr>

<td><h3>MSFT</h3></td>
<td id="MSFT">
<h3><span class="label label-default">50,00</h3> </td>

UI de exemplo de ações | 11
Machine Translated by Google

</tr>
<tr>
<td><h3>AMZN</h3></td>
<td id="AMZN">
<h3><span class="label label-default">300,00</h3> </td> </tr> <tr>

<td><h3>GOOG</h3></
td> <td
id="GOOG"> <h3><span class="label label-default">550,00</h3> </
td> </
tr>
<tr>
<td><h3>YHOO</h3></td>
<td id="YHOO">
<h3><span class="label label-default">35,00< /h3> </td> </tr> </

tbody> </
table>

</div>
</div>
<script src="http://bit.ly/maxcdn-bootstrap-js"></script> </body></html>

Eventos WebSocket
A API do WebSocket é baseada em eventos. Esta seção cobre os quatro eventos
que seu código de cotação da bolsa pode escutar. Darei descrições de cada um, descreverei como
lidar com situações que você verá em campo e construirei o exemplo usando o que aprender. Por
exemplo, você precisa definir alguns bits de dados de amostra para passar para o
servidor:

var stock_request = {"ações": ["AAPL", "MSFT", "AMZN", "GOOG", "YHOO"]};

var ações = {"AAPL": 0,


"MSFT": 0,
"AMZN": 0,
"GOOG": 0,
"YHOO": 0};

A Figura 2-1 mostra a aparência do seu aplicativo padrão depois de conectar o servidor e o cliente.

A primeira estrutura, stock_request, é passada após a conexão bem-sucedida entre cliente e servidor
e solicita que o servidor continue informando sobre os preços atualizados dessas ações específicas.
A segunda estrutura, ações, é uma matriz associativa simples

12 | Capítulo 2: API WebSocket


Machine Translated by Google

que irá reter os valores alterados passados de volta do servidor e então usados para modificar o texto na
tabela e nas cores.

Figura 2-1. Gráfico de ações sobre WebSocket

O WebSocket dispara quatro eventos, que estão disponíveis na API JavaScript e definidos pelo W3C:

• aberto

• mensagem

• erro

• fechar

Com JavaScript, você escuta esses eventos disparando com o manipulador on<event name> ou com o
método addEventListener() . Seu código fornecerá um retorno de chamada que será executado sempre
que o evento for acionado.

Evento: Open

Quando o servidor WebSocket responde à solicitação de conexão e o handshake é concluído, o evento


open é acionado e a conexão é estabelecida. Quando isso acontecer, o servidor terá concluído o
handshake e estará pronto para enviar e receber mensagens da sua aplicação cliente:

Eventos WebSocket | 13
Machine Translated by Google

// Conexão WebSocket estabelecida


ws.onopen = function(e)
{ console.log("Conexão estabelecida");
ws.send(JSON.stringify(stock_request));
};

A partir deste manipulador você pode enviar mensagens para o servidor e exibir o status
na tela, e a conexão estará pronta e disponível para comunicação bidirecional. A
mensagem inicial enviada ao servidor via WebSocket é a estrutura stock_request como
uma string JSON. Seu servidor agora sabe quais ações você deseja receber atualizações
e as enviará de volta ao cliente em intervalos de um segundo.

Evento: Mensagem
Depois de estabelecer uma conexão com o servidor WebSocket, ele estará disponível
para enviar mensagens (você verá isso em “Métodos WebSocket” na página 16) e
receber mensagens. A API WebSocket preparará mensagens completas para serem
processadas no manipulador onmessage .

O Capítulo 8 aborda o protocolo WebSocket com mais detalhes, incluindo informações


sobre frames e o fluxo de dados entre o servidor e o cliente. Por enquanto, a única coisa
a lembrar é que quando o servidor tiver dados, a API WebSocket chamará o manipulador
onmessage :

// função de atualização
da UI var changeStockEntry = function(symbol, originalValue, newValue) {
var valElem = $('#' + símbolo + 'span');
valElem.html(newValue.toFixed(2));
if(newValue < originalValue)
{ valElem.addClass('label-danger');
valElem.removeClass('label-sucesso');
} else if(newValue > originalValue)
{ valElem.addClass('label-success');
valElem.removeClass('rótulo-perigo');
}
}

// Manipulador de mensagens
WebSocket ws.onmessage =
function(e) { var stocksData =
JSON.parse(e.data); for(var símbolo
em stocksData)
{ if(stocksData.hasOwnProperty(symbol)) { changeStockEntry(symbol,
stocks[symbol], stocksData[symbol]); ações[símbolo] = açõesData[símbolo];
}
}
};

14 | Capítulo 2: API WebSocket


Machine Translated by Google

Você pode ver neste pequeno trecho que o manipulador está recebendo uma mensagem do
servidor por meio de um retorno de chamada onmessage . Ao consultar dados, o atributo data
conterá valores de estoque atualizados. O trecho de código anterior faz o seguinte:

1. Analisa a resposta JSON em e.data 2. Itera

sobre o array associativo 3. Garante que


a chave exista no array 4. Chama seu
fragmento de atualização da UI 5.
Atribui os novos valores de estoque ao seu array local

Você está transmitindo strings regulares aqui, mas o WebSocket tem suporte completo para
envio de texto e dados binários.

Evento: Erro

Quando ocorre uma falha por qualquer motivo, o manipulador anexado ao evento de erro é
acionado. Quando ocorre um erro, pode-se presumir que a conexão WebSocket será fechada e
um evento close será acionado. Como o evento close ocorre logo após um erro em alguns casos,
os atributos code e reason podem fornecer alguma indicação sobre o que aconteceu. Aqui está
um exemplo de como lidar com o caso de erro e, possivelmente, reconectar-se também ao
servidor WebSocket:

ws.onerror = function(e)
{ console.log(" Falha no WebSocket, erro", e);
handleErrors(e);
};

Evento: PING/PONG

O protocolo WebSocket chama dois tipos de quadros: PING e PONG. A API do cliente WebSocket
JavaScript não oferece capacidade de enviar um quadro PING ao servidor. Os quadros PING
são enviados apenas pelo servidor e as implementações do navegador devem enviar de volta
quadros PONG em resposta.

Evento: Fechar

O evento close é acionado quando a conexão WebSocket é fechada e o retorno de chamada


onerror será executado. Você pode acionar manualmente a chamada do evento onclose
executando o método close() em um objeto WebSocket, que encerrará a conexão com o servidor.
Depois que a conexão for encerrada, a comunicação entre cliente e servidor não continuará. O
exemplo a seguir zera a matriz stocks quando um evento de fechamento é disparado para
mostrar recursos de limpeza:

Eventos WebSocket | 15
Machine Translated by Google

ws.onclose = function(e)
""
{ console.log(e.reason + + e.code);
for( símbolo var em ações) {
if(stocks.hasOwnProperty(symbol))
{ stocks[symbol] = 0;
}
}
}

ws.close(1000, 'Conexão WebSocket fechada')

Conforme mencionado brevemente em “Evento: Erro” na página 15, dois atributos, código e motivo,
são transmitidos pelo servidor e podem indicar uma condição de erro a ser tratada e/ou um motivo
para o evento de fechamento (diferente da expectativa normal). Qualquer um dos lados pode
encerrar a conexão por meio do método close() no objeto WebSocket, conforme mostrado no
código anterior. Seu código também pode usar o atributo booleano wasClean para descobrir se o
encerramento foi limpo ou para ver o resultado de um estado de erro.

O valor readyState passará de fechado (2) para fechado (3). Agora vamos passar para os métodos
disponíveis para seu objeto WebSocket.

Métodos WebSocket
Os criadores do WebSocket mantiveram seus métodos bastante simples – existem apenas dois:
send() e close().

Método: Enviar
Quando sua conexão for estabelecida, você estará pronto para começar a enviar (e receber)
mensagens de/para o servidor WebSocket. O aplicativo cliente pode especificar que tipo de dados
está sendo transmitido e aceitará vários, incluindo strings e valores binários . Conforme mostrado
anteriormente, o código do cliente está enviando uma string JSON de ações listadas:

ws.send(JSON.stringify(stock_request));

É claro que realizar esse envio em qualquer lugar não será apropriado. Como já discutimos, o
WebSocket é orientado a eventos, portanto, você precisa garantir que a conexão esteja aberta e
pronta para receber mensagens. Você pode conseguir isso de duas maneiras principais.

Você pode realizar seu envio dentro do evento onopen :

var ws = new WebSocket("ws://localhost:8181");


ws.onopen = function(e)
{ ws.send(JSON.stringify(stock_request));
}

16 | Capítulo 2: API WebSocket

www.allitebooks.com
Machine Translated by Google

Ou você pode verificar o atributo readyState para garantir que o objeto WebSocket esteja pronto
para receber mensagens:

function processEvent(e) {
if(ws.readyState === WebSocket.OPEN) { //
Soquete aberto, envie!
ws.enviar(e); }
else { //
Mostra um erro, coloca-o na fila para envio posterior, etc.
}
}

Método: Fechar
Você fecha a conexão WebSocket ou encerra uma tentativa de conexão por meio do método
close() . Depois que esse método for chamado, nenhum outro dado poderá ser enviado ou recebido
dessa conexão. E chamá-lo várias vezes não tem efeito.

Aqui está um exemplo de chamada do método close() sem argumentos:

// Fecha a conexão WebSocket


ws.close();

Opcionalmente, você pode passar um código numérico e um motivo legível por meio do método
close() . Isso fornece alguma indicação ao servidor sobre o motivo pelo qual a conexão foi encerrada
no lado do cliente. O código a seguir mostra como passar esses valores.
Note que se não passar um código, o status 1000 é assumido, o que significa CLOSE_NORMAL:

// Fecha a conexão WebSocket com razão. ws.close(1000,


"Adeus, Mundo!");

A Tabela 2-2 lista os códigos de status que você pode usar no método WebSocket close() .

Tabela 2-2. Códigos de fechamento do WebSocket

Código Nome Descrição


de status

0–999 Reservado e não utilizado.

1000 CLOSE_NORMAL Fechamento normal; a conexão foi concluída com sucesso.

1001
CLOSE_GOING_AWAY O endpoint está desaparecendo devido a uma falha no servidor ou porque o
o navegador está saindo da página que abriu a conexão.

1002 CLOSE_PROTOCOL_ O endpoint está encerrando a conexão devido a um erro de protocolo.


ERRO

Métodos WebSocket | 17
Machine Translated by Google

Status Nome Descrição


código

1003 CLOSE_UNSUPPORTED A conexão está sendo encerrada porque o endpoint recebeu dados de um tipo que

Não posso aceitar.

1004 CLOSE_TOO_LARGE O endpoint está encerrando a conexão porque um quadro de dados foi recebido

isso é muito grande.

1005 CLOSE_NO_STATUS Reservado. Indica que nenhum código de status foi fornecido, embora um tenha sido

esperado.

1006 Reservado. Usado para indicar que uma conexão foi fechada de forma anormal.
CLOSE_ABNORMAL

1007–1999 Reservado para uso futuro pelo padrão WebSocket.

2000–2999 Reservado para uso por extensões WebSocket.

3000–3999 Disponível para uso por bibliotecas e estruturas. Não pode ser usado por aplicativos.

4000–4999 Disponível para uso por aplicativos.

Atributos do WebSocket
Quando o evento para open é acionado, o objeto WebSocket pode ter vários possíveis
atributos que podem ser lidos em seus aplicativos cliente. Esta seção apresenta
atributos e as práticas recomendadas para usá-los em seu código de cliente.

Atributo: readyState
O estado da conexão WebSocket pode ser verificado por meio do WebSocket somente leitura
atributo do objeto readyState. O valor de readyState mudará e é uma boa
ideia verificá-lo antes de se comprometer a enviar quaisquer dados para o servidor.

A Tabela 2-3 mostra os valores que você verá refletidos no atributo readyState .

Tabela 2-3. Constantes readyState


Nome do Atributo Valor do atributo Descrição

WebSocket.CONNECTING 0 A conexão ainda não está aberta.

WebSocket.OPEN 1 A conexão está aberta e pronta para se comunicar.

WebSocket.FECHAMENTO 2 A conexão está em processo de fechamento.

WebSocket.FECHADO 3 A conexão está fechada ou não pôde ser aberta.

18 | Capítulo 2: API WebSocket


Machine Translated by Google

Cada um desses valores pode ser verificado em diferentes pontos para depuração e para
entender o ciclo de vida da sua conexão com o servidor.

Atributo: bufferedAmount
Também incluída nos atributos está a quantidade de dados armazenados em buffer para
envio ao servidor. Embora isso seja usado principalmente ao enviar dados binários, como o
tamanho dos dados tende a ser muito maior, o navegador se encarregará de enfileirar
adequadamente os dados para envio. Como você está lidando apenas com o código do
cliente neste ponto (o próximo capítulo trata do protocolo), grande parte dos bastidores fica oculta à sua visão.
O uso do atributo bufferedAmount pode ser útil para garantir que todos os dados sejam
enviados antes de fechar uma conexão ou executar sua própria limitação no lado do cliente.

Atributo: protocolo

Refletindo sobre o construtor do WebSocket, o argumento opcional do protocolo permite


enviar um ou mais subprotocolos que o cliente está solicitando. O servidor decide qual
protocolo escolher, e isso se reflete neste atributo para a conexão Web-Socket. O handshake
quando concluído deverá conter uma seleção dentre aquele enviado pelo cliente, ou vazio se
nenhum tiver sido escolhido ou oferecido.

Servidor de exemplo de estoque

Agora que você tem um cliente funcional que se conectará a um servidor WebSocket para
recuperar cotações de ações, é hora de mostrar a aparência do servidor:

var WebSocketServer = require('ws').Servidor,


wss = novo WebSocketServer({porta: 8181});

var ações =
{ "AAPL": 95,0,
"MSFT": 50,0,
"AMZN": 300,0,
"GOOG": 550,0,
"YHOO": 35,0
}

function randomInterval(min, max) { return


Math.floor(Math.random()*(max-min+1)+min);
}

var stockUpdater;
var randomStockUpdater = function() { for
(var símbolo em ações)
{ if(stocks.hasOwnProperty(symbol)) { var
randomChange = randomInterval(-150, 150); var floatChange
= randomChange / 100; ações[símbolo] +=
floatChange;

Servidor de exemplo de ações | 19


Machine Translated by Google

} var randomMSTime = randomInterval(500, 2500);


stockUpdater = setTimeout(function()
{ randomStockUpdater(); },
randomMSTime)

randomStockUpdater();

wss.on('conexão', function(ws) { var


clientStockUpdater; var
sendStockUpdates = function(ws) {
if(ws.readyState == 1) { var
stocksObj = {};

for(var i=0; i<clientStocks.length; i++) { símbolo =


clientStocks[i]; açõesObj[símbolo]
= ações[símbolo];
}

ws.send(JSON.stringify(stocksObj));
}

} clientStockUpdater = setInterval(function()
{ sendStockUpdates(ws); },
1000);

var clienteEstoques = [];

ws.on('mensagem', function(mensagem) {
var stock_request = JSON.parse(mensagem);
clientStocks = stock_request['estoques'];
enviarStockUpdates(ws);
});

ws.on('close', function() { if(typeof


clientStockUpdater !== 'indefinido') {
clearInterval(clientStockUpdater);
}
});
});

Após a execução, o código do servidor executa uma função por um período de tempo variável
(entre 0,5s e 2,5s) e atualiza os preços das ações. Ele faz isso para aparecer o mais aleatório
possível em um exemplo de livro, sem exigir que o código saia e recupere os preços reais
das ações (veja o Capítulo 4 para isso). Seu frontend espera receber uma lista estática de
cinco ações recuperadas do servidor. Simples. Após receber o evento de conexão do cliente,
o servidor configura uma função para ser executada a cada segundo e envia de volta a lista
de cinco ações com preços aleatórios uma vez por segundo. O servidor pode aceitar solicitações de

20 | Capítulo 2: API WebSocket


Machine Translated by Google

ações diferentes, desde que esses símbolos de ações e um preço inicial sejam adicionados
ao objeto JavaScript de ações definido no servidor.

Teste de suporte WebSocket


Se você codificou alguma coisa para a Web ao longo dos anos, não deve ser surpresa que os
navegadores nem sempre tenham suporte para a tecnologia mais recente. Como alguns
navegadores mais antigos não suportam a API WebSocket, é importante verificar a
compatibilidade antes de usá-la. O Capítulo 5 apresenta alternativas caso os navegadores
clientes usados pela sua comunidade de usuários não suportem a API WebSocket. Por
enquanto, aqui está uma maneira rápida de verificar se a API é compatível com o cliente:

if (window.WebSocket)
{ console.log("WebSocket: suportado"); // ...
código aqui para fazer coisas do WebSocket } else

{ console.log("WebSocket: unsupported"); // ... modo


fallback ou erro de volta ao usuário
}

Resumo
Este capítulo abordou detalhes essenciais da API WebSocket e como usar cada uma delas em
seu aplicativo cliente. Ele discutiu os eventos, mensagens, atributos e métodos da API e
mostrou alguns exemplos de código ao longo do caminho.

No Capítulo 3, você escreverá um aplicativo de bate-papo bidirecional, aprendendo como


transmitir mensagens entre vários clientes conectados.

Teste de suporte WebSocket | 21


Machine Translated by Google
Machine Translated by Google

CAPÍTULO 3

Bate-papo bidirecional

Seu primeiro exemplo completo é construir um bate-papo bidirecional usando WebSocket. O


resultado final será um servidor que aceita conexões e mensagens WebSocket para sua “sala
de bate-papo” e espalha as mensagens para os clientes conectados. O protocolo WebSocket
em si é simples, portanto, para escrever seu aplicativo de bate-papo, você gerenciará a coleta
de dados de mensagens em uma matriz e manterá o soquete e o UUID exclusivo do cliente em
variáveis com escopo local.

Sondagem Longa

A pesquisa longa é um processo que mantém ativa uma conexão com o servidor sem que os
dados sejam enviados imediatamente de volta ao cliente. A pesquisa longa (ou uma solicitação
HTTP de longa duração) envia uma solicitação do servidor que é mantida aberta até que tenha
dados, e o cliente irá recebê-la e reabrir a conexão logo após receber os dados do servidor. Na
verdade, isso permite uma conexão persistente com o servidor para enviar e receber dados.

Na prática, duas técnicas comuns estão disponíveis para conseguir isso. Na primeira técnica,
XMLHttpRequest é iniciado e então mantido aberto, aguardando uma resposta do servidor. Uma
vez recebido, outra solicitação é feita ao servidor e mantida aberta, aguardando mais dados. A
outra técnica envolve escrever tags de script personalizadas, possivelmente apontando para um
domínio diferente (solicitações entre domínios não são permitidas com o primeiro método). As
solicitações são então tratadas de maneira semelhante e reabertas no típico estilo de pesquisa
longa.

A pesquisa longa é a forma mais comum de implementar esse tipo de aplicação na Web
atualmente. O que você verá neste capítulo é um método de implementação muito mais simples
e eficiente. Nos capítulos subsequentes você abordará o problema de compatibilidade de
navegadores mais antigos que talvez ainda não suportem WebSocket.

23
Machine Translated by Google

Escrevendo um aplicativo de bate-papo básico

O Capítulo 1 mostrou um servidor básico que aceitava uma conexão WebSocket e enviava
qualquer mensagem recebida de um cliente conectado ao console. Vamos dar uma olhada nesse
código e adicionar recursos necessários para implementar seu bate-papo bidirecional:

var WebSocketServer = require('ws').Server, wss =


new WebSocketServer({porta: 8181});

wss.on('conexão', function(socket)
{ console.log('cliente conectado');
socket.on('message', function(message)
{ console.log(message);
});
});

O WebSocketServer fornecido pelo popular módulo ws Node é inicializado e começa a escutar


na porta 8181. Você pode acompanhar isso ouvindo um evento de conexão do cliente e os
eventos de mensagem subsequentes a seguir. O evento de conexão aceita uma função de
retorno de chamada onde você passa um objeto de soquete a ser usado para ouvir mensagens
após uma conexão bem-sucedida. Isso funciona bem para mostrar uma conexão simples para
nossos propósitos, e agora você vai desenvolver isso rastreando os clientes que se conectam e
enviando essas mensagens para todos os outros clientes conectados.

O protocolo WebSocket não fornece nenhuma dessas funcionalidades por padrão; a


responsabilidade pela criação e rastreamento é sua. Nos capítulos posteriores, você se
aprofundará em bibliotecas como Socket.IO, que estendem a funcionalidade do WebSocket e
fornecem uma API mais rica e compatibilidade retroativa com navegadores mais antigos.

A Figura 3-1 mostra a aparência atual do aplicativo de bate-papo.

Com base no código do Capítulo 1, importe um módulo Node para gerar um UUID.
Primeiramente, você usará o npm para instalar o node-uuid:

% npm instalar nó-uuid

var uuid = require('node-uuid');

Um UUID é usado para identificar cada cliente que se conectou ao servidor e adicioná-los a uma
coleção. Um UUID permite direcionar mensagens de usuários específicos, operar nesses
usuários e fornecer dados direcionados a esses usuários conforme necessário.

Identificador universalmente
exclusivo Um UUID é um identificador padronizado comumente usado na construção de
sistemas distribuídos e pode ser considerado “praticamente único”. De modo geral, você não
terá colisões, mas isso não é garantido. Portanto, você não terá problema em usar isso como
seu identificador para seu aplicativo de bate-papo simples.

24 | Capítulo 3: Bate-papo Bidirecional


Machine Translated by Google

Figura 3-1. Seu primeiro aplicativo de bate-papo WebSocket

A seguir, você aprimorará a conexão com o servidor com identificação e registro:

var clientes = [];

wss.on('conexão', function(ws) { var


client_uuid = uuid.v4();
clients.push({"id": client_uuid, "ws": ws});
console.log('cliente [%s ] conectado', client_uuid);

Atribuir o resultado da função uuid.v4 à variável client_uuid permite referenciá-lo posteriormente ao identificar
envios de mensagens e qualquer evento de fechamento . Um objeto de metadados simples na forma de
JSON contém o UUID do cliente junto com o objeto Web-Socket.

Quando o servidor recebe uma mensagem do cliente, ele itera sobre todos os clientes conectados conhecidos
usando a coleção de clientes e envia de volta um objeto JSON contendo a mensagem e o ID do remetente
da mensagem. Você pode perceber que isso também envia de volta a mensagem ao cliente que iniciou, e
essa simplicidade é intencional. No cliente frontend você não atualiza a lista de mensagens a menos que ela
seja retornada pelo servidor:

ws.on('mensagem', function(mensagem) {
for(var i=0; i<clientes.length; i++) { var
clientSocket = clientes[i].ws;
console.log('cliente [%s]: %s', clientes[i].id, mensagem);
clienteSocket.send(JSON.stringify({ "id":
client_uuid,

Escrevendo um aplicativo de bate-papo básico | 25


Machine Translated by Google

"mensagem":
mensagem }));

} });

O servidor WebSocket agora recebe eventos de mensagens de qualquer um dos clientes


conectados. Depois de receber a mensagem, ele percorre os clientes conectados e envia
uma string JSON que inclui o identificador exclusivo do cliente que enviou a mensagem e a
própria mensagem. Cada cliente conectado receberá esta string JSON e poderá mostrá-la
ao usuário final.

Um servidor deve lidar com estados de erro normalmente e ainda continuar funcionando.
Você ainda não definiu o que fazer no caso de um evento de fechamento do WebSocket ,
mas falta algo que precisa ser resolvido no código do evento da mensagem . A coleção de
clientes conectados precisa levar em conta a possibilidade de o cliente ter desaparecido e
garantir que, antes de enviar uma mensagem, ainda haja uma conexão WebSocket aberta.
O novo código é o seguinte:

ws.on('mensagem', function(mensagem) {
for(var i=0; i<clientes.length; i++) { var
clientSocket = clientes[i].ws;
if(clientSocket.readyState === WebSocket.OPEN) {
console.log('cliente [%s]: %s', clientes[i].id, mensagem);
clientSocket.send(JSON.stringify({ "id":
client_uuid, "message":
mensagem }));

} });

Agora você tem um servidor que aceitará conexões de clientes WebSocket e retransmitirá
as mensagens recebidas para todos os clientes conectados. A última coisa a ser tratada é
o evento close :

ws.on('close', function() { for(var


i=0; i<clients.length; i++) { if(clients[i].id ==
client_uuid) { console.log('client [%s ]
desconectado', client_uuid); clientes.splice(i, 1);

} });

O servidor escuta um evento de fechamento e, ao recebê-lo para esse cliente, itera pela
coleção e remove o cliente. Junte isso à verificação do sinalizador readyState do seu objeto
WebSocket e você terá um servidor que funcionará com seu novo cliente.

26 | Capítulo 3: Bate-papo Bidirecional

www.allitebooks.com
Machine Translated by Google

Posteriormente neste capítulo, você transmitirá o estado dos clientes desconectados e conectados
junto com suas mensagens de bate-papo.

Cliente WebSocket

O cliente echo simples do Capítulo 1 pode ser usado como ponto de partida para seu aplicativo web
de bate-papo. Todo o tratamento da conexão funcionará conforme especificado e você precisará
ouvir o evento onmessage que estava sendo ignorado anteriormente:

ws.onmessage = função (e) {


var dados = JSON.parse(e.data);
var mensagens = document.getElementById('mensagens');
var mensagem = document.createElement("li");
mensagem.innerHTML =
dados.mensagem; mensagens.appendChild(mensagem);
}

O cliente recebe uma mensagem do servidor na forma de um objeto JSON. O uso da função de
análise integrada do JavaScript retorna um objeto que pode ser usado para extrair o campo da
mensagem. Vamos adicionar uma lista simples e não ordenada acima do formulário para que as
mensagens possam ser anexadas usando os métodos DOM mostrados na função. Adicione o
seguinte acima do elemento do formulário:

<ul id="mensagens"></ul>

As mensagens serão anexadas à lista usando o método DOM appendChild e mostradas em cada
cliente conectado. Até agora você apenas arranhou a superfície da funcionalidade que mostra as
mensagens contínuas fornecidas pelo protocolo WebSocket.
Na próxima seção você implementará um método para identificar clientes por um apelido.

Identidade do cliente
A especificação WebSocket foi deixada relativamente simplista em termos de implementação e
carece de alguns dos recursos vistos em alternativas. Até agora, em seu código, você já percorreu
um longo caminho para identificar cada cliente individualmente. Agora você pode adicionar identidades
de apelidos ao código do cliente e do servidor:

var apelido = client_uuid.substr(0, 8);


clientes.push({"id": client_uuid, "ws": ws, "apelido": apelido});

O servidor é modificado para adicionar o campo apelido a um objeto JSON armazenado localmente
para este cliente. Para identificar exclusivamente um cliente conectado que não identificou uma
opção de apelido, você pode usar os primeiros oito caracteres do UUID e atribuí-los à variável de
apelido . Tudo isso será enviado de volta por meio de uma conexão WebSocket aberta entre o
servidor e todos os seus clientes conectados.

Cliente WebSocket | 27
Machine Translated by Google

Você usará uma convenção usada com clientes do Internet Relay Chat (IRC) e aceitará /nick
new_nick como o comando para alterar o apelido do cliente na sequência aleatória:

if(message.indexOf('/nick') == 0) {
var apelido_array = mensagem.split(' ')
if(apelido_array.length >= 2) { var
apelido_antigo = apelido; apelido
= apelido_array[1]; for(var i=0;
i<clientes.length; i++) { var clientSocket =
clientes[i].ws; var apelido_message =
"Cliente " + apelido_antigo + " alterado para + apelido;
"
clientSocket.send(JSON.stringify({

"id": client_uuid,
"apelido": apelido,
"mensagem": apelido_mensagem }));

}
}
}

Este código verifica a existência do comando /nick seguido por uma sequência de caracteres que
representa um apelido. Atualize sua variável de apelido e você poderá criar uma string de
notificação para enviar a todos os clientes conectados através do console aberto existente.
conexão.

Os clientes ainda não conhecem esse novo campo, pois o JSON que você enviou originalmente
incluía apenas id e mensagem. Adicione o campo com o seguinte código:

clientSocket.send(JSON.stringify({ "id":
client_uuid, "apelido":
apelido, "mensagem":
mensagem }));

A função appendLog no frontend do cliente precisa ser modificada para suportar a adição da
variável de apelido :

função anexarLog (apelido, mensagem) { var


mensagens = document.getElementById ('mensagens'); var
mensagemElem = document.createElement("li"); var
"
message_text = "[" + apelido + "] - + mensagem;
messageElem.innerHTML = message_text;
messages.appendChild(messageElem);
}

A Figura 3-2 mostra seu aplicativo de bate-papo com a adição de identidade.

28 | Capítulo 3: Bate-papo Bidirecional


Machine Translated by Google

Figura 3-2. Bate-papo habilitado para identidade

Sua nova assinatura de função inclui apelido junto com mensagem, e agora você pode
prefaciar cada mensagem com o apelido do cliente. A pedido do cliente, você pode ver um
apelido precedendo as mensagens em vez de uma sequência aleatória de caracteres antes
de cada mensagem.

Eventos e Notificações
Se você estivesse no meio de uma conversa e outra pessoa aparecesse magicamente na sua
frente e começasse a falar, isso seria estranho. Para aliviar isso, você pode adicionar
notificação de conexão ou desconexão e enviá-la de volta a todos os clientes conectados.

Seu código tem vários casos em que você teve o trabalho de iterar todos os clientes
conectados, verificar o readyState do soquete e enviar uma string semelhante codificada em
JSON com valores variados. Para garantir, você extrairá isso em uma função genérica e a
chamará de vários lugares em seu código:

function wsSend(tipo, client_uuid, apelido, mensagem) {


for(var i=0; i<clientes.length; i++) { var
clientSocket = clientes[i].ws;
if(clientSocket.readyState === WebSocket.OPEN)
{ clientSocket.send(JSON.stringify({ "type":
type, "id":
client_uuid,

Eventos e Notificações | 29
Machine Translated by Google

"apelido": apelido,
"mensagem":
mensagem }));
}
}
}

Com esta função genérica, você pode enviar notificações para todos os clientes conectados,
manipular o estado da conexão e codificar a string conforme o cliente espera, assim:

wss.on('conexão', function(ws) {
...
wsSend("mensagem", client_uuid, apelido, mensagem);
...
});

Enviar mensagens para todos os clientes após a conexão agora é simples. Mensagens de
conexão, mensagens de desconexão e qualquer notificação necessária agora são tratadas com
sua nova função.

O servidor
Aqui está o código completo do servidor:

var WebSocket = require('ws'); var


WebSocketServer = WebSocket.Server,
wss = novo WebSocketServer({porta: 8181});
var uuid = require('node-uuid');

var clientes = [];

function wsSend(tipo, client_uuid, apelido, mensagem) {


for(var i=0; i<clientes.length; i++) { var
clientSocket = clientes[i].ws;
if(clientSocket.readyState === WebSocket.OPEN)
{ clientSocket.send(JSON.stringify({ "type":
type, "id":
client_uuid,
"nickname": apelido,
"message":
mensagem }));
}
}
}

var índicecliente = 1;

wss.on('conexão', function(ws) { var


client_uuid = uuid.v4(); var apelido
= "AnonymousUser"+clientIndex; clientIndex+=1;
clientes.push({"id":
client_uuid, "ws": ws, "apelido": apelido});

30 | Capítulo 3: Bate-papo Bidirecional


Machine Translated by Google

console.log('cliente [%s] conectado', client_uuid);

var connect_message = apelido + "conectou";


wsSend("notificação", client_uuid, apelido, connect_message);

ws.on('mensagem', function(mensagem) {
if(message.indexOf('/nick') === 0) {
var apelido_array = mensagem.split(' ');
if(nickname_array.length >= 2) { var
old_nickname = apelido; apelido =
apelido_array[1]; var
apelido_message = "Cliente "+old_nickname+" alterado para "+nickname;
wsSend("nick_update", client_uuid, apelido, apelido_mensagem);

} } else
{ wsSend("mensagem", client_uuid, apelido, mensagem);

} });

var closeSocket = function(customMessage) { for(var


i=0; i<clients.length; i++) { if(clientes[i].id ==
client_uuid) { var shutdown_message;
if(customMessage)
{ desconectar_message
= customMessage; } else

{ mensagem_desconectada = apelido + " desconectou";


}
wsSend("notificação", client_uuid, apelido, mensagem de desconexão);
clientes.splice(i, 1);
}
}

} ws.on('fechar', function()
{ closeSocket();
});

process.on('SIGINT', function()
{ console.log("Fechando coisas");
closeSocket('Servidor desconectado');
process.exit();

}); });

O cliente
Aqui está o código completo do cliente:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demonstração de bate-papo WebSocket bidirecional </title>

O Cliente | 31
Machine Translated by Google

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, escala inicial=1"> <link
rel="stylesheet" href="http://bit.ly /cdn-bootstrap-css"> <link rel="stylesheet"
href="http://bit.ly/cdn-bootstrap-theme"> <script src="http://bit.ly/cdn-bootstrap-
jq"></script>

<script>
var ws = new WebSocket("ws://localhost:8181"); var
apelido = ""; ws.onopen
= function(e)
{ console.log('Conexão com o servidor aberta');

} functionappendLog (tipo, apelido, mensagem) {


var mensagens = document.getElementById('mensagens'); var
mensagemElem = document.createElement("li"); var
prefácio_label;
if(type==='notificação')
{ preface_label = "<span class=\"label label-info\">*";
} else if(type=='nick_update') {
preface_label = "<span class=\"label label-warning\">*"; } else { preface_label
= "<span
class=\"label label-success\">" + apelido + "";

} var message_text = "<h2>" + preface_label + "&nbsp;&nbsp;" +


mensagem + "</h2>";
mensagemElem.innerHTML = mensagem_texto;
mensagens.appendChild(messageElem);
}

ws.onmessage = função (e) {


var dados = JSON.parse(e.data);
apelido = dados.apelido;
anexarLog(dados.tipo, dados.apelido, dados.mensagem);
console.log("ID: [%s] = %s", dados.id, dados.message);

} ws.onclose = function(e)
{ appendLog("Conexão fechada");
console.log("Conexão fechada");

} function enviarMensagem() {
var mensagemField = document.getElementById('mensagem');
if(ws.readyState === WebSocket.OPEN) {
ws.send(messageField.value);

} mensagemField.value = '';
mensagemField.focus();

} função desconectar() {
close();

} </script>

32 | Capítulo 3: Bate-papo Bidirecional


Machine Translated by Google

</head>
<body lang="en">
<div class="vertical-center"> <div
class="container"> <ul
id="messages" class="list-unstyled">

</ul>
<hr />
<form role="form" id="chat_form" onsubmit="sendMessage(); return false;">
<div class="form-group">
<input class="form-control" type="text" id="message" name="message"
placeholder="Digite o texto para ecoar aqui" value="" autofocus/ > </div>
<button
type="button" id="send" class="btn btn-primary"
onclick="sendMessage();">Enviar mensagem</button>
</form>
</div>
</ div>
<script src="http://bit.ly/cdn-bootstrap-minjs"></script> </body> </
html>

A Figura 3-3 mostra o aplicativo de bate-papo com a adição de notificações.

Figura 3-3. Chatbsoc habilitado para notificação

O Cliente | 33
Machine Translated by Google

Resumo
Neste capítulo você construiu um cliente e servidor de chat completo usando o protocolo
WebSocket. Você construiu continuamente um aplicativo de bate-papo simplista em algo
mais robusto, usando apenas a API WebSocket como sua tecnologia preferida. Experiências
eficazes e otimizadas entre aplicativos internos, chat ao vivo e camadas de outros protocolos
sobre HTTP são possibilidades nativas do WebSocket.

Tudo isso é possível com outras tecnologias e, como você provavelmente já aprendeu, há
mais de uma maneira de resolver um problema. Comet e Ajax são testados em batalha para
oferecer experiências semelhantes ao usuário final, conforme fornecido pelo WebSocket.
Usá-los, no entanto, está repleto de ineficiência, latência, solicitações desnecessárias e
conexões desnecessárias ao servidor. Somente o WebSocket remove essa sobrecarga e
fornece um soquete full-duplex, bidirecional e pronto para o rock 'n' roll.

No próximo capítulo você verá um protocolo popular para colocar em camadas sobre o
WebSocket, para fornecer transporte sem a sobrecarga do HTTP.

34 | Capítulo 3: Bate-papo Bidirecional


Machine Translated by Google

CAPÍTULO 4

STOMP sobre WebSocket

Nos capítulos anteriores você construiu aplicativos simples usando a API WebSocket tanto no lado
do servidor quanto no cliente. Você construiu um aplicativo de bate-papo multicliente com Web-
Socket como camada de comunicação. O Capítulo 2 discutiu brevemente o uso de subprotocolos
com WebSocket. Agora você pegará tudo que aprendeu até agora e colocará outro protocolo sobre
o WebSocket.

STOMP, um acrônimo para Simple Text Oriented Messaging Protocol, é um protocolo simples do
tipo HTTP para interagir com qualquer corretor de mensagens STOMP. Qualquer cliente STOMP
pode interagir com o message broker e ser interoperável entre linguagens e plataformas.

Neste capítulo você criará um cliente e um servidor que se comunicam usando o protocolo STOMP
através de WebSocket em vez de TCP. Você aprenderá como se conectar ao RabbitMQ usando o
plug-in Web-Stomp, que usa WebSocket como protocolo de ligação subjacente.

Como nos capítulos anteriores, você criará uma nova pasta de projeto para os exemplos do
Capítulo 4 com o nome abreviado ch4. Os exemplos neste capítulo novamente usam um ticker da
bolsa e mensagens para se inscrever para receber atualizações da bolsa. Além disso, há dois
exemplos neste capítulo, portanto crie um subdiretório chamado proxy. Você criará vários arquivos
para construir uma tabela de trabalho real de preços de ações com tecnologia STOMP sobre
WebSocket. Aqui estão os arquivos que você usará:

client.html
A base de código do frontend; como antes, copie o modelo usado no Capítulo 1.

server.js
O proxy WebSocket que se comunica com RabbitMQ usando AMQP enquanto escuta STOMP
por WebSocket.

35
Machine Translated by Google

stomp_helper.js
Uma biblioteca conveniente que você criará para enviar e receber solicitações STOMP.

daemon.js
Um daemon que extrai ações do Yahoo Finance usando YQL e puxa e envia mensagens
para RabbitMQ.

Implementando STOMP
STOMP é um protocolo de texto simples semelhante à convenção HTTP de um comando
maiúsculo como CONNECT, seguido por uma lista de pares chave/valor de cabeçalho e, em
seguida, conteúdo opcional, que no caso de STOMP é terminado em nulo. Também é possível
e altamente recomendado passar o comprimento do conteúdo como parâmetro para qualquer
comando, e o servidor usará esse valor como o comprimento do conteúdo passado.

Conectando-se Como

você viu no Capítulo 2, a API nativa do navegador para conexão com um servidor WebSocket
utiliza dois parâmetros: URL e protocolo. Desses dois parâmetros, apenas o URL é necessário,
mas agora você utilizará o segundo. Se você pesquisar protocolos registrados no WebSocket
Subprotocol Name Registry, você encontrará uma entrada para STOMP 1.0, que usa o
identificador v10.stomp. Como discutiremos no Capítulo 8, não é necessário usar um
subprotocolo registrado com WebSocket. O subprotocolo precisa ser suportado pelo cliente e
pelo servidor. No seu cliente, então, abra uma conexão da seguinte maneira:

var ws;

var connect = function() { if(!ws


|| ws.readyState !== 1) { ws = new
WebSocket("ws://localhost:8181", "v10.stomp");
ws.addEventListener('mensagem', onMessageHandler);
ws.addEventListener('aberto', onOpenHandler);
ws.addEventListener('fechar', onCloseHandler);
}
}

conectar();

Assim como nos exemplos anteriores, você abre uma conexão com um servidor WebSocket na
porta 8181. Mas, além disso, você passa um segundo parâmetro no construtor, que pode ser
uma string ou um array de strings identificando os subprotocolos solicitados do servidor. Observe
também que uma função connect adiciona os ouvintes de eventos para abertura, mensagem e
fechamento usando o método addEventListener . Este é o método essencial de conexão. Se
você precisar se reconectar após uma conexão perdida, os manipuladores de eventos não serão
reconectados automaticamente se você estiver usando o método ws.on<eventname> .

36 | Capítulo 4: STOMP sobre WebSocket

www.allitebooks.com
Machine Translated by Google

Depois de abrir a conexão WebSocket, um evento open é acionado e você pode enviar e receber oficialmente
mensagens do servidor. Se você fizer referência ao documento do protocolo STOMP 1.0 , o seguinte será mostrado
como o método de conexão inicial a um servidor compatível com STOMP:

CONECTAR

login: <nome de
usuário> senha: <senha>

^@

Para nosso exemplo, você usará websockets como nome de usuário e RabbitMQ como senha para toda
autenticação com o servidor STOMP e RabbitMQ. Portanto, dentro do seu código, passe o seguinte com a função
de envio do WebSocket:

var frame = "CONNECT\n" +


"login: websockets\n"; +
"senha: coelhomq\n"; + "apelido:
anônimo\n"; + "\n\n\0";

ws.send(quadro);

Você pode ver no documento do protocolo STOMP 1.0 que cada quadro enviado termine com o terminador nulo
^@ ou, se o cabeçalho de comprimento de conteúdo for passado, ele será usado.
Devido à simplicidade do WebSocket, você está mapeando cuidadosamente os frames STOMP sobre os frames do
WebSocket nestes exemplos. Se o servidor aceitar as informações de conexão e autenticação, ele repassará o
seguinte ao cliente, o que inclui:
um ID de sessão a ser usado em chamadas posteriores ao servidor:

Sessão
CONECTADA: <session-id>

^@

A introdução do capítulo mencionou stomp_helper.js e, antes de chegar ao código do servidor, vamos revisar a
biblioteca que ajudará no envio e recebimento de quadros compatíveis com STOMP (Exemplo 4-1).

Exemplo 4-1. Código da biblioteca STOMP

(função(exportações)
{ exportações.process_frame = função(dados) {
var linhas = data.split("\n"); var
quadro = {};
frame['cabeçalhos'] = {};
if(lines.length>1)
{ frame['command'] = linhas[0]; var
x = 1;
while(linhas[x].comprimento>0)
{ var header_split = linhas[x].split(':');

Implementando STOMP | 37
Machine Translated by Google

var chave = header_split[0].trim(); var


val = header_split[1].trim();
frame['cabeçalhos'][chave] = val;
x += 1;

} frame['content'] = linhas
.splice(x + 1, linhas.comprimento -
x) .join("\n");

quadro['conteúdo'] = quadro['conteúdo']
.substring(0, frame['content'].length - 1);
}
quadro de retorno ;
};

exports.send_frame = function(ws, frame) { var


data = frame['command'] + "\n"; var
header_content = ""; for(var
chave no frame['headers'])
{ if(frame['headers'].hasOwnProperty(key)) {
header_content += key +
": " +
frame['headers'][key] + "\n";

} dados += header_content;
dados += "\n\n";
dados += quadro['conteúdo'];
dados += "\n\0";
ws.enviar(dados);
};

exports.send_error = function(ws, mensagem, detalhe)


{ headers = {};
if(mensagem) headers['mensagem'] = mensagem;
else headers['message'] = "Nenhuma mensagem de erro fornecida";

exports.send_frame(ws,
{ "command": "ERROR",
"headers": cabeçalhos,
"content": detalhe
});
};

})(typeof exports === 'indefinido'? this['Stomp']={}: exporta);

Os itens cerimoniais que precedem e seguem as funções nesta biblioteca permitem que isso
seja usado no navegador e no lado do servidor com Node.js em um require
declaração.

38 | Capítulo 4: STOMP sobre WebSocket


Machine Translated by Google

A primeira função a ser descrita é process_frame, que usa um quadro STOMP como parâmetro
chamado data e cria um objeto JavaScript contendo tudo o que foi analisado para uso em sua
aplicação. Conforme descrito na Tabela 4-1, ele divide o comando, todos os cabeçalhos e
qualquer conteúdo dentro do quadro e retorna um objeto totalmente analisado.

Tabela 4-1. Estrutura de objeto JavaScript

Chave Descrição

comando Comando STOMP passado pelo quadro

headers Um objeto JavaScript com chaves/valores para os cabeçalhos passados

conteúdo Qualquer conteúdo enviado no quadro que foi terminado em nulo ou que segue o cabeçalho de comprimento de conteúdo

A seguir, e igualmente importante, está a função send_frame , que aceita um objeto Web-
Socket e um quadro STOMP na forma de um objeto JavaScript exatamente como você envia
de volta da função process_frame . A função send_frame pega cada um dos valores passados,
cria um quadro STOMP válido e o envia pelo parâmetro WebSocket passado .

A função restante é send_error, que utiliza os parâmetros mostrados na Tabela 4-2.

Tabela 4-2. Parâmetros aceitos para a chamada send_error


Nome Descrição

WebSocket A conexão WebSocket ativa

mensagem Mensagem de erro explicando o que deu errado

detalhe Mensagem de detalhe opcional passada no corpo

Você poderá usar o conjunto de funções mencionado acima para enviar e receber quadros
STOMP sem qualquer análise de string no código do cliente ou servidor.

Conectando via Servidor No lado

do servidor, ao receber um evento de conexão , sua tarefa inicial para se conectar é analisar o
que é recebido no quadro da mensagem (usando a biblioteca stomp_helper.js) e enviar de
volta um comando CONNECTED ou um comando CONNECTED. ERRO se falhou:

wss.on('conexão', function(ws) { var


sessionid = uuid.v4();

Implementando STOMP | 39
Machine Translated by Google

ws.on('message', function(message) { var


frame = Stomp.process_frame(message); var
headers = frame['headers'];
switch(frame['command']) { case
"CONNECT":
Stomp. send_frame(ws,
{ comando: "CONNECTED",
cabeçalhos:
{ sessão: sessionid,
},
conteúdo: ""
});

quebrar;
padrão: Stomp.send_error(ws, "Nenhum quadro de comando
válido"); quebrar;
}
});
...
});

Como você viu nos exemplos anteriores, o evento de conexão é recebido e o trabalho
começa. Existe uma camada extra graças ao STOMP, que é controlada de certa forma pela
sua biblioteca. Depois de atribuir um sessionid a um UUID e ao receber um evento de
mensagem do cliente, você o executa por meio da função process_frame para obter um
objeto JavaScript que representa o quadro recebido. Para processar qualquer comando
enviado, o programa utiliza uma instrução case e, ao receber o comando CONNECT , você
envia de volta um quadro STOMP informando ao cliente que a conexão foi recebida e aceita
junto com o sessionid desta sessão.

Dê uma olhada rápida na Figura 4-1, que mostra um evento de conexão concluído .

Olhando para a captura de tela, você verá um novo cabeçalho para a solicitação e resposta
HTTP: Sec-WebSocket-Protocol. No Capítulo 8 você pode ler uma discussão mais
aprofundada sobre os vários cabeçalhos e se aprofundar no protocolo em detalhes. Aqui no
exemplo de ações, a solicitação enviada inclui o subprotocolo v10.stomp. Se o servidor aceitar
este subprotocolo, ele, por sua vez, responderá com esse nome de subprotocolo e o cliente
poderá continuar enviando e recebendo quadros para o servidor. Se o servidor não falar
v10.stomp, você receberá um erro.

40 | Capítulo 4: STOMP sobre WebSocket


Machine Translated by Google

Figura 4-1. Conexão WebSocket bem-sucedida com subprotocolo

A implementação padrão da biblioteca ws aceitará qualquer subprotocolo enviado. Vamos escrever algum código
extra para garantir que apenas o protocolo v10.stomp seja aceito aqui. Para fazer isso, você escreverá um
manipulador especial ao inicializar o objeto Web SocketServer :

var WebSocketServer = require('ws').Server, wss =


new WebSocketServer({porta: 8181,
handleProtocols: function(protocol, cb) {
var v10_stomp = protocolo[protocol.indexOf("v10.stomp")];
if(v10_stomp)
{ cb(verdadeiro,
v10_stomp); retornar;

}
cb(falso); }});

No Capítulo 2, a visão geral da API WebSocket mostrou que você pode passar mais de um subprotocolo. No código
do seu manipulador, você terá que descompactar uma matriz de subprotocolos que inclui aquele que o cliente
procura. Como você está usando o Node.js, você pode usar convenções como Array.indexOf sem se preocupar com
coisas como o Internet Explorer não oferecer suporte a ele. Com o código anterior, você executou com êxito um
handshake aceitando um novo subprotocolo.

Conforme observado anteriormente, seu primeiro exemplo de implementação do STOMP será o aplicativo de ações.
Você enviará solicitações via STOMP do cliente para o servidor, e o servidor enviará e receberá mensagens com
RabbitMQ enquanto o daemon de ações cospe inter-

Implementando STOMP | 41
Machine Translated by Google

atualizações discretas dos preços. Para começar, instale um servidor RabbitMQ para enfileirar
suas mensagens no servidor.

Configurando o RabbitMQ
Você precisará ter um nó RabbitMQ em execução para o seu servidor WebSocket fazer proxy
das solicitações. Para fazer isso, você precisará ter o Vagrant configurado em sua máquina de
desenvolvimento. Vagrant é uma ferramenta útil para criar máquinas virtuais de desenvolvimento
portáteis e leves. Instalá-lo é tão fácil quanto obter o binário de instalação adequado para o seu
sistema operacional na página de download do Vagrant.

Vagrant é uma ferramenta leve para criar e configurar ambientes


de desenvolvimento reproduzíveis e portáteis. Ele usa VirtualBox
ou VMWare internamente para instâncias virtualizadas e permite
vários provedores, incluindo Puppet, Chef, Ansible e até mesmo
scripts de shell simples.

Depois de instalar o Vagrant com sucesso, crie um novo arquivo na pasta do projeto chamado
Vagrantle e inclua o seguinte:

Vagrant.configure("2") do |config|
config.vm.hostname = "websockets-mq"
config.vm.box = "precise64"
config.vm.box_url = "http://bit.ly/ubuntu-vagrant-precise-box-amd64"

config.vm.network:porta_encaminhada , convidado: 5672, host: 5672


config.vm.network : porta_encaminhada, convidado: 15672, host: 15672

config.vm.provision "shell", caminho: "setup_rabbitmq.sh"

config.vm.provider :virtualbox do |v| v.name


= "websockets-mq"
fim
fim

O arquivo de configuração será usado para criar uma nova instância do Vagrant usando a
imagem em config.vm.box_url. Ele encaminha as portas 5672 e 15672 para a máquina local e
especifica um provisionamento baseado em shell a ser executado no vagrant up, que está
incluído no código a seguir:

#!/ bin/ bash

cat >> /etc/apt/sources.list <<EOT deb


http://www.rabbitmq.com/debian/ testando EOT principal

wget http://www.rabbitmq.com/rabbitmq-signing-key-public.asc apt-key add


RabbitMQ-signing-key-public.asc

42 | Capítulo 4: STOMP sobre WebSocket


Machine Translated by Google

atualização do apt-get

apt-get install -q -y tela htop vim curl wget apt-get install -q -y


coelhomq-server

# RabbitMQ Plugins
service RabbitMQ-Server Stop
RabbitMQ-plugins Enable RabbitMQ_Management Service
RabbitMQ-Server Start

# Crie nosso usuário websockets e remova o convidado


coelhomqctl delete_user convidado
coelhomqctl add_user websockets coelhomq
coelhomqctl set_user_tags administrador de websockets coelhomqctl
set_permissions -p / websockets " .*" ".*" ".*"

lista de plugins RabbitMQ

O script de provisionamento de shell faz o seguinte:

• Adiciona uma nova fonte para a instalação mais recente

do RabbitMQ • Instala algumas dependências junto com o servidor

RabbitMQ • Ativa o plug-in RabbitMQ_management

• Remove o usuário convidado e cria seu novo usuário padrão RabbitMQ:websockets •


Concede privilégios de administrador a esse usuário

Agora, na linha de comando, inicialize e provisione a nova instância do Vagrant com o seguinte:

vagabundo

Este comando lê o Vagrantle e executa o script de provisionamento para instalar o servidor


RabbitMQ em uma instância Ubuntu 12.04 amd64 para uso nos exemplos. O código a seguir
mostra uma impressão semelhante ao que você deverá ver após concluir o comando.
Imediatamente após esta saída, o Vagrant executará o shell script de provisionamento que
configura o RabbitMQ:

Trazendo a máquina 'padrão' com o provedor 'virtualbox' ... ==> padrão:


Importando caixa base 'precise64'... ==> padrão: Endereço
MAC correspondente para rede NAT... ==> padrão: Configurando o nome
da VM: websockets-mq ==> padrão: Limpando todas as portas
encaminhadas definidas anteriormente ... ==> padrão: Limpando todas as
interfaces de rede definidas anteriormente ... ==> padrão: Preparando interfaces de
rede com base na configuração...
padrão: Adaptador 1: nat ==>
padrão: Encaminhamento de portas...
padrão: 5672 => 5672 (adaptador 1) padrão:
15672 => 15672 (adaptador 1)

Configurando o RabbitMQ | 43
Machine Translated by Google

padrão: 22 => 2222 (adaptador 1) ==>


padrão: Inicializando VM... ==>
padrão: Aguardando a inicialização da máquina. Isso pode levar alguns minutos...
padrão: endereço SSH: 127.0.0.1:2222
padrão: nome de usuário SSH: vagrant
padrão: método de autenticação SSH: chave privada

O Vagrantle incluído, que fornece a configuração do Vagrant, abre as seguintes portas:

tcp/5672
A porta padrão para amqp

tcp/15672
A interface de gerenciamento web

Conectando o servidor ao RabbitMQ


Depois de instalar as dependências adequadas, é hora de voltar e fazer o servidor se
comunicar com o RabbitMQ. A conexão com o RabbitMQ pode acontecer independentemente
do trabalho do WebSocket. Após a execução do servidor, você abrirá uma conexão com o
RabbitMQ e realizará duas ações com a conexão:

• Ouvir a fila stocks.result para atualizações sobre preços •


Publicar solicitações de estoque em um intervalo definido na fila stocks.work

Para fazer isso com o seu servidor, você precisará conversar sobre AMQP com RabbitMQ.
Existem muitas bibliotecas para Node.js falar sobre AMQP, e a mais simples que encontrei é
node-amqp. Use o comando npm para instalar a biblioteca na pasta do seu projeto:

npm instalar amqp

Suas ações iniciais serão mediante uma solicitação CONNECT válida iniciada do cliente para
o servidor. Você criará uma conexão com a instância RabbitMQ em execução, usando as
informações de autenticação transmitidas pelo cliente.

Veja como você se conectará à instância RabbitMQ instalada:

amqp = requer('amqp');

var conexão = amqp.createConnection( { host:


'localhost',
login: 'websockets',
senha: 'rabbitmq'
});

A biblioteca que está sendo usada (amqp) dispara eventos que podem ser escutados usando
retornos de chamada. No trecho a seguir, ele escuta o evento ready e executa a função de
retorno de chamada fornecida. Ao garantir que a conexão está pronta, você começa a ouvir o

44 | Capítulo 4: STOMP sobre WebSocket


Machine Translated by Google

stocks.result fila e inscreva-se para receber atualizações de mensagens que são


repassadas por meio dele. Essas mensagens conterão preços atualizados para os
estoques solicitados. Você notará que dentro dos blocos, a biblioteca stomp_helper.js
está sendo usada para enviar quadros MESSAGE de volta aos clientes que solicitaram
atualizações sobre ações específicas:
connection.on('pronto', function() {
connection.queue('stocks.result', {autoDelete: false, durável: true}, function(q)

{ q.subscribe(function(message) { var data;


try { data
=
JSON.parse(message.data.toString ('utf8')); } catch(err)
{ console.log(err);

} for(var i=0; i<data.length; i++) { for(var


cliente em ações)
{ if(stocks.hasOwnProperty(client)) { var ws
= stocks[client].ws; for(var
símbolo em ações[cliente]) {
if(estoques[cliente].hasOwnProperty(símbolo)
&& símbolo === dados[i]['símbolo'])
{ ações[cliente][símbolo] = dados[i]['preço']; var
preço = parseFloat(estoques[cliente][símbolo]);
Stomp.send_frame(ws, {
"comando": "MESSAGEM",
"cabeçalhos": {
"destino": "/fila/estoques." + símbolo
},
conteúdo: JSON.stringify({preço: preço})
});
}
}
}
}
}
});
});
});

Conectando o servidor ao RabbitMQ | 45


Machine Translated by Google

A carga recebida da fila de mensagens stocks.result é semelhante a esta:

[
{
"símbolo":"AAPL",
"preço":149,34
},
{
"símbolo":"GOOG",
"preço":593.2600000000037
}
]

Depois de analisar a carga útil, o bloco de código itera sobre o resultado e sobre uma lista
mestra de ações armazenadas em todos os clientes conectados. No processo de iteração
sobre um objeto JavaScript, você deve verificar se o valor que está sendo passado durante
a iteração faz parte do objeto usando myObject.hasOwnProperty(myIterator Value). Ele
mapeia o preço atualizado com o preço que está sendo armazenado e envia uma
mensagem de volta ao cliente conectado usando STOMP sobre esse destino específico.

Quando o cliente solicita um novo estoque, ele é adicionado à lista mestre de estoques.
Um bloco de código separado é executado em um intervalo para enviar a lista mestre para
uma fila stocks.work , que é coletada pelo daemon.js para encontrar o preço atualizado e
enviá-lo de volta pela fila stocks.result . Um dos principais motivos pelos quais você faz
isso é que é mais fácil escalar e o sistema pode processar mais solicitações, se necessário,
adicionando mais daemons, sem qualquer efeito adverso. O código a seguir mostra o
método atualizador . Ele cria uma matriz de strings de símbolos de ações e a publica na
fila stocks.work :
var atualizador = setInterval(function() {

var st = [];
for(var cliente em ações) { for(var
símbolo em ações[cliente]) { if(symbol !==
'ws') { st.push(symbol);

}
}

} if(st.length>0)
{ connection.publish('stocks.work',
JSON.stringify({"estoques": st}),
{deliveryMode: 2});

} }, 10.000);

46 | Capítulo 4: STOMP sobre WebSocket

www.allitebooks.com
Machine Translated by Google

O Daemon do Preço das Ações

O código a seguir é para o daemon, que recebe uma matriz de símbolos de ações e
gera um objeto JSON com os valores atualizados usando o Yahoo YQL. Crie um
novo arquivo chamado daemon.js e insira o seguinte trecho:
#!/ usr/ bin/ nó env

var solicitação = require('solicitação'),


amqp = require('amqp');

module.exports = Estoques;

function Ações() { var


self = this;
}

Stocks.prototype.lookupByArray = function(ações, cb) {


'"'
var csv_stocks = + stocks.join('","') + '"';

var env_url = '&env=http%3A%2F%2Fdatatables.org%2Falltables.env&format=json'; var url =


'https://query.yahooapis.com/v1/public/yql'; var dados =
encodeURIComponent(
'selecione * em yahoo.finance.quotes onde o símbolo está (' +
csv_stocks + ')'); var
data_url = url + '?q='
+ dados
+
env_url;

request.get({url: data_url, json: true}, função


(erro, resposta, corpo) { var stocksResult
= []; if (!error &&
response.statusCode == 200) { var totalReturned =
body.query.count ; for (var i = 0; i <
totalRetornado; ++i) {
var estoque = body.query.results.quote[i]; var
stockReturn =
{ 'símbolo': estoque.symbol,
'preço': estoque.Ask
};

stocksResult.push(stockReturn);
}

cb(resultado de
ações); }
else { console.log(erro);
}
});
};

Conectando o servidor ao RabbitMQ | 47


Machine Translated by Google

var main = function() { var


connection = amqp.createConnection({
host: 'localhost', login:
'websockets', senha:
'rabbitmq'
});

var ações = novas ações();


connection.on('pronto', function() {
connection.queue('stocks.work', {autoDelete: false, durável: true}, function(q)

{ q.subscribe(function(message) { var
json_data = message.data.toString('utf8'); var dados ;

console.log(json_data);
tente
{ dados = JSON.parse(json_data); }
catch(err)
{ console.log(err);

} stocks.lookupByArray(data.stocks, function(stocks_ret) { var data_str


= JSON.stringify(stocks_ret);
connection.publish('stocks.result', data_str,
{deliveryMode: 2});
});

}); });
});

};

if(require.main === módulo) { main();

Este daemon pode ser executado usando node daemon.js e se conectará ao RabbitMQ
e processará o trabalho que extrai da fila de mensagens do RabbitMQ. Várias
convenções devem ser notadas no servidor WebSocket STOMP, incluindo o método
de conexão e o processamento do evento ready . O daemon ouvirá a fila stocks.work ,
no entanto, para obter uma lista de ações a serem pesquisadas e, no final, enviará o
resultado de volta para a fila stocks.result . Se você der uma olhada na função
Stocks.proto type.lookupByArray , ela emitirá uma chamada YQL do Yahoo para as
ações solicitadas e retornará a carga JSON, como visto anteriormente.

48 | Capítulo 4: STOMP sobre WebSocket


Machine Translated by Google

Processando solicitações STOMP


Antes de mergulhar na interação do servidor com RabbitMQ, você viu como obter
conexão com STOMP via WebSocket usando sua biblioteca. Vamos continuar e
detalhar o restante dos comandos necessários para interagir com o frontend:
wss.on('conexão', function(ws) { var
sessionid = uuid.v4();

ações[sessionid] = {};
sessão_conectada.push(ws);
ações[sessionid]['ws'] = ws;

ws.on('mensagem', function(mensagem) {
var frame = Stomp.process_frame(mensagem);
var cabeçalhos = frame['cabeçalhos'];
switch(frame['command']) { case
"CONNECT":
Stomp.send_frame(ws, {
comando: "CONNECTED",
cabeçalhos:
{ sessão: sessionid
},
conteúdo: ""
});

quebrar; case
"ASSINAR": var subscribeSymbol =
symbolFromDestination( frame['headers']['destination']);
ações[sessionid][subscribeSymbol] = 0; quebrar;
case
"UNSUBSCRIBE": var
unsubscribeSymbol = símboloFromDestination(
frame['cabeçalhos']['destino']); excluir
ações[sessionid][unsubscribeSymbol]; quebrar; case

"DISCONNECT":
console.log("Desconectando");
fecharSocket();

quebrar;
padrão: Stomp.send_error(ws, "Nenhum quadro de
comando válido"); quebrar;
}
});

var símboloFromDestination = function(destino) {


retornar destino.substring(destination.indexOf('.') + 1,
destino.comprimento);
};

var closeSocket = function() {

Processando solicitações STOMP | 49


Machine Translated by Google

close();
if(ações[sessionid] && ações[sessionid]['ws']) { ações[sessionid]
['ws'] = null;

} excluir ações[sessionid];
};

ws.on('fechar', function()
{ closeSocket();
});

process.on('SIGINT', function() {
console.log("Fechando via break");
fecharSocket();
processo.exit();
});

Assim como nos exemplos anteriores, após uma conexão bem-sucedida, é gerado um UUID
que atuará como seu ID de sessão para passar de um lado para o outro no quadro STOMP.
O quadro será analisado e colocado no objeto JavaScript. A partir daí você executa
diferentes ações com base no comando frame passado. Você já viu o código para
CONNECT, então vamos nos concentrar em SUBSCRIBE, UNSUBSCRIBE e DISCONNECT.

Tanto a assinatura quanto o cancelamento modificam seu objeto de ações . Ao se inscrever, você
adiciona um novo símbolo à lista existente de ações para esse ID de sessão. O cancelamento da
assinatura é feito apenas removendo esse símbolo da lista para que ele não seja repassado ao cliente.
O recebimento de um comando DISCONNECT do cliente envolve o fechamento do WebSocket e a
limpeza de quaisquer referências a ele e ao cliente no objeto stocks . Por se tratar de um aplicativo
para ser executado a partir do console, existe a possibilidade de receber um Ctrl-C, o que interromperia
a conexão. Para lidar com isso, conecte-se ao evento SIGINT que é acionado, para que você possa
fechar o soquete normalmente e em seus próprios termos.

Cliente
O cliente é uma interface simples com ações que variam de preço com base nos dados retornados do
servidor. O formulário na parte superior recebe um símbolo de ação como entrada e tenta SUBSCRIBE
via STOMP para obter atualizações do servidor. Enquanto a solicitação de assinatura está sendo
enviada, uma linha da tabela é adicionada para o novo símbolo, bem como um espaço reservado para
“Recuperando…” enquanto aguarda o retorno dos dados.

A Figura 4-2 mostra um exemplo funcional da aplicação de cotação da bolsa.

50 | Capítulo 4: STOMP sobre WebSocket


Machine Translated by Google

Figura 4-2. Exemplo de ações de STOMP sobre WebSocket

A marcação do exemplo é mostrada no código a seguir. Ele descreve um formulário simples que
chama o método subscribe (descrito a seguir) e a tabela contendo os símbolos de ações, os
preços atualizados do serviço e um botão Remover. Além disso, foi adicionado um indicador de
status de conexão ao servidor WebSocket:

<div class="vertical-center"> <div


class="container">

<div class="bem">

<form role="form" class="form-inline" id="add_form"


onsubmit="subscribe($('#symbol').val()); return false;"> <div class="form-
group ">
<input class="form-control" type="text" id="symbol"
name="símbolo" placeholder=" Símbolo de estoque: ou seja, AAPL" value=""
foco automático />
</div>

<button type="submit" class="btn btn-primary">Adicionar</button>

</form>

</div>

<tabela class="tabela" id="stockTable">


<thead>

Cliente | 51
Machine Translated by Google

<tr>
<th>Símbolo</th>
<th>Preço</th>
<th>Ações</th> </
tr> </
thead>
<tbody id="stockRows"> <tr
id="norows">
<td colspan="3">
Nenhum estoque encontrado, adicione
um
acima
</td> </tr>
</tbody> </table>

<div class="text-right">
<p>
<a id="conexão" class="btn btn-danger"
href="#" onclick="connect();">Off-line</a>
</p>
</div>
</div>
</div>

Diversas funções compõem seu aplicativo cliente e serão descritas separadamente


na ordem em que são executadas. A primeira função é subscribe, que adiciona
um novo símbolo à interface e o comunica ao servidor:

var subscrever = função(símbolo) {


if(stocks.hasOwnProperty(symbol))
{ alert('Você já adicionou o ' + símbolo + return; 'símbolo');

ações[símbolo] = 0,0;
Stomp.send_frame(ws,
{ "command": "ASSINAR",
"cabeçalhos": {
"destino": "/fila/estoques." + símbolo,
},
""
conteúdo:
});
var tbody = document.getElementById('stockRows');

var novaRow = tbody.insertRow(tbody.rows.length); newRow.id


= símbolo + '_row';

newRow.innerHTML = '<td><h3>' + símbolo + '</h3></td>' +


'<td id="' + símbolo + '">' + '<h3>' +
'<span
class="label label-default">Recuperando..' + '</h3>' +

52 | Capítulo 4: STOMP sobre WebSocket


Machine Translated by Google

'</td>' +
'<td>' +
'<a href="#" onclick="unsubscribe(\'' + símbolo + '\');"
class="btn btn-danger">Remover</a></td>';

if(!$('#norows').hasClass('hidden')) { $
('#norows').addClass('hidden');
}

$('#símbolo').val(''); $
('#symbol').focus();
}
A primeira coisa a fazer sempre que receber uma entrada do usuário é realizar a validação, que é
feita para verificar se você já possui aquele símbolo em sua lista e retornar um erro caso seja
encontrado. Se tudo estiver bem, você inicializa o símbolo em sua lista de ações e envia um novo
quadro SUBSCRIBE para o servidor. O restante do código é para a interface do usuário e adiciona
uma linha da tabela com valores padrão enquanto aguarda um valor legítimo do servidor.

Se um cliente puder assinar uma atualização de estoque, ele também poderá cancelar a assinatura.
O próximo trecho faz exatamente isso e é referenciado no código anterior para remoção:

Object.size = function(obj) { var size


= 0, chave; for (chave
em obj) { if
(obj.hasOwnProperty(key)) size++;
}
tamanho de retorno ;
};

var cancelar assinatura = function(símbolo)


{ Stomp.send_frame(ws,
{ "comando": "CANCELAR
ASSINATURA", "cabeçalhos": {
"destino": "/fila/estoques." + símbolo,
},
conteúdo: ""
});
$('#' + símbolo + '_row').remove();

excluir ações[símbolo];

if(Object.size(stocks) === 0) { $
('#norows').removeClass('hidden');
}
}
Para cancelar a assinatura, você executa as seguintes tarefas:

1. Envie o comando UNSUBSCRIBE em um quadro STOMP com o símbolo como parte de


o destino.

Cliente | 53
Machine Translated by Google

2. Remova a linha da tabela na interface do usuário.

3. Remova a entrada no objeto stocks .

4. Verifique se há mais símbolos no objeto de ações e, caso contrário,


reexibir o bloco HTML #norows .

As funções nos dois trechos de código anteriores representam todas as ações que um usuário
pode realizar com sua interface: assinar e cancelar. Agora vamos voltar à função connect() ,
mostrada anteriormente, sem detalhes sobre seus manipuladores. A primeira é a forma mais
elaborada usando a biblioteca stomp_helper.js para lidar com eventos abertos :

var onOpenHandler = função(ões) {


Stomp.send_frame(ws, {
"command": "CONNECT",
"headers":
{ login: "websockets",
senha: "rabbitmq"
},
conteúdo: ""
});
}

Resumindo, ao obter uma conexão com seu servidor WebSocket, você envia seu comando
CONNECT com informações de autenticação pelo quadro STOMP. Para encerrar a conexão, siga
um caminho semelhante e forneça uma notificação para a interface do usuário:

estava online = falso;

var statusChange = function(novoStatus) {


$('#connection').html((newStatus ? 'Online' : 'Off-line')); $
('#connection').addClass((newStatus ? 'btn-success' : 'btn-danger')); $
('#connection').removeClass((newStatus ? 'btn-danger' : 'btn-success')); on-line = novoStatus;

var switchOnlineStatus = function() {


if(on-line) logoff(); senão conectar();
}

var logoff = function()


{ statusChange(false);

Stomp.send_frame(ws,
{ "command":
"DISCONNECT" }
);
retorna falso;
}

54 | Capítulo 4: STOMP sobre WebSocket


Machine Translated by Google

O código HTML contém um botão de status que, quando clicado, executará a função
switchOnlineStatus . Isso irá desconectá-lo do servidor ou reconectá-lo como visto
anteriormente. A função logoff envia seu comando DISCONNECT usando um quadro
STOMP para informar ao servidor para executar suas próprias rotinas de desconexão.

Todo o trabalho realizado no servidor para recuperar estoques por meio do RabbitMQ é
colocado em ação no código a seguir. Como você verá, seu onMessageHandler pega
dados do servidor e atualiza o frontend com os novos valores:
var updateStockPrice = function(símbolo, originalValue, newValue) {
var valElem = $('#' + símbolo + 'span');
valElem.html(newValue.toFixed(2));
varValor perdido = (novoValor < valororiginal);
valElem.addClass((lostValue ? 'label-danger' : 'label-success'))
valElem.removeClass((lostValue ? 'label-success' : 'label-danger'))
}

var onMessageHandler = função(ões) {


quadro = Stomp.process_frame(e.data);
switch(frame['command']) { case
"CONNECTED":
statusChange(true);

quebrar; case
"MESSAGE": var destino = frame['cabeçalhos']['destino']; var
conteúdo;
tente
{ content = JSON.parse(frame['content']); }
catch(ex)
{ console.log("exceção:", ex);

} var sub_stock = destino.substring( destino.indexOf('.')


+ 1, destino.comprimento
);
updateStockPrice(sub_estoque, ações[sub_estoque], conteúdo.preço);
ações[sub_stock] = content.price; quebrar;

}
}

Quando um novo evento de mensagem é passado, o código processará esses dados


como um quadro STOMP. O processo será verificar os comandos CONNECTED ou
MESSAGE do quadro. Os comandos que serão processados incluem o seguinte:

CONECTADO

Chame statusChange(true) para alterar o status do botão para “Online”

MENSAGEM

Recuperar o cabeçalho de destino, analisar o conteúdo e atualizar o preço das ações


na interface

Cliente | 55
Machine Translated by Google

O cliente tem partes ativas com a parte de assinatura/cancelamento de assinatura/desconexão


e as partes passivas que atendem ao recebimento de dados do servidor. Os eventos MESSAGE
disparados serão vinculados a um destino STOMP e os estoques serão atualizados de acordo
com os dados recuperados.

Você implementou com sucesso as funções mais básicas disponíveis no protocolo


STOMP 1.0. O mapeamento entre STOMP e WebSocket pode ser simples, e há mais
alguns comandos que deixamos não implementados em seu proxy baseado em nó:
BEGIN, COMMIT, ACK e no lado do servidor RECEIPT.

Mapear STOMP sobre WebSocket alcança duas coisas: mostra como colocar um protocolo
diferente em camadas sobre WebSocket usando a parte do subprotocolo da especificação e
permite conversar com um servidor AMQP sem precisar especificamente de um componente de
servidor escrito. Na próxima seção, você aprenderá como se conectar ao RabbitMQ com SockJS
usando o plugin Web-Stomp com RabbitMQ. Você aprenderá mais sobre como usar o SockJS
no Capítulo 5, que aborda a compatibilidade com navegadores mais antigos. Várias opções
estão disponíveis para mensagens, incluindo estas populares:

• ActiveMQ

• ActiveMQ Apollo
• HornetQ

Usando RabbitMQ com Web-Stomp


Ao longo deste capítulo, você escreveu uma implementação de servidor do STOMP para fazer
proxy de comandos de maneira eficaz para o RabbitMQ usando AMQP. Esperamos que isso
tenha mostrado como pode ser fácil colocar outro protocolo em camadas sobre o WebSocket.
Agora, para finalizar o capítulo, você aprenderá como configurar o RabbitMQ com Web-Stomp,
um plug-in que permite que o RabbitMQ aceite STOMP. O plug-in expõe uma ponte compatível
com SockJS sobre HTTP, que é uma biblioteca de transporte alternativa (isso é discutido com
mais detalhes no Capítulo 5). Melhora a compatibilidade para navegadores mais antigos que
não possuem suporte nativo para WebSocket.

Protocolo avançado de enfileiramento de

mensagens O protocolo avançado de enfileiramento de mensagens (AMQP) é um protocolo de


camada de aplicação de padrão aberto para middleware orientado a mensagens. Os recursos
definidores do AMQP são orientação de mensagens, enfileiramento, roteamento (incluindo ponto a
ponto e publicação e assinatura), confiabilidade e segurança.

56 | Capítulo 4: STOMP sobre WebSocket

www.allitebooks.com
Machine Translated by Google

Cliente STOMP para Web e Node.js Para

uma implementação mais completa do seu trabalho neste capítulo, baixe a biblioteca STOMP
Over WebSocket. Ele fornece uma biblioteca cliente JavaScript para acessar servidores usando
STOMP 1.0 e 1.1 via WebSocket, e uma biblioteca Node.js para fazer o mesmo via WebSocket
junto com uma opção para soquetes TCP via STOMP.

Instalando o plug-in Web-Stomp Vamos

editar o script shell de provisionamento usado anteriormente no capítulo para configurar o RabbitMQ.
No script, após parar o servidor RabbitMQ durante a instalação, você adicionará a seguinte
linha:

plugins RabbitMQ ativam RabbitMQ_web_stomp

Além disso, sua máquina virtual precisa de edição, então encaminhe a porta 15674, que é
aberta pelo plug-in instalado anteriormente para escutar solicitações SockJS. Você modificará
o Vagrantle existente e adicionará a seguinte linha com todas as outras opções de
configuração de rede:

config.vm.network:forwarded_port, convidado: 15674, host: 15674

Depois de fazer isso, se a instância original do VirtualBox ainda estiver em execução, você
poderá executar vagrant halt ou vagrant destroy e, em seguida, executar novamente o vagrant
up para recriar a instância. Se você destruiu, pronto, a nova porta será aberta e o novo plug-
in será ativado. Se você parou, poderá executar as seguintes tarefas:

vagrant ssh
sudo su -
plugins RabbitMQ ativam RabbitMQ_web_stomp

Isso habilita um novo plug-in chamado Web-Stomp e expõe a porta 15674. Rabbit padronizou
o uso de SockJS para toda comunicação WebSocket, e discutiremos essa biblioteca mais
detalhadamente no Capítulo 5. Para continuar, você vai querer baixar o JavaScript STOMP
biblioteca disponível em stomp.js. Então você pode continuar alterando seu código de cliente
para usar o endpoint Web-Stomp.

Cliente Echo para Web-Stomp

Vamos construir um cliente echo simples que se inscreve em uma fila chamada /topic/echo e
então envia e recebe mensagens. No topo do seu arquivo HTML, inclua as seguintes
instruções JavaScript:

<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script> <script


src="stomp.min.js"></script>

Você pode optar por baixar a versão minimizada conforme mencionado neste código ou a
versão não minimizada, se preferir. Em ambos os casos, você pode baixar a biblioteca stomp-
websocket no GitHub.

Usando RabbitMQ com Web-Stomp | 57


Machine Translated by Google

Seu HTML será quase idêntico ao exemplo de eco anterior e você modificará o JavaScript para
atender às suas necessidades usando o plug-in RabbitMQ Web-Stomp e a biblioteca Stomp.js:

<!DOCTYPEhtml>
<html><head>
<title>Echo Server</title> </
head>
<body lang="en">
<h1>Web Stomp Echo Server</h1>

<ul id="mensagens">

</ul>

<form onsubmit="send_message(); return false;">


<input type="text" name="message" style="largura: 200px;"
id="message" placeholder="Digite o texto para ecoar aqui"
value="" foco automático />
<input type="button" value="Enviar!" onclick="send_message();" />

</form>
</body>
</html>

Sua primeira tarefa é inicializar o endpoint RabbitMQ SockJS e, em seguida, passá-lo para a
biblioteca JavaScript STOMP. A biblioteca Stomp.js permite usar WebSocket nativo ou qualquer
coisa que ofereça a mesma API, como SockJS. Como o SockJS não oferece suporte a
pulsação, você o manterá desligado. A biblioteca Stomp.js oferece diversas oportunidades
para retorno de chamada e para executar qualquer tarefa que você desejar nos dados
retornados. Aqui, você está apenas enviando os dados para o console:

var ws = new SockJS('http://localhost:15674/stomp'); var cliente


= Stomp.over(ws);

cliente.heartbeat.outgoing = 0;
cliente.heartbeat.incoming = 0;

cliente.debug = function(str)
{ console.log(str);
}

Ao se conectar a uma fila RabbitMQ, você simplesmente oferecerá detalhes de login e alguns
retornos de chamada junto com o host (ou host virtual nos termos do RabbitMQ). A função
append_log será idêntica à mostrada anteriormente, mas a implementação dos retornos de
chamada necessários para conexão, erro e uma nova função send_message é mostrada aqui:

client.connect('websockets', 'rabbitmq', connect_callback, error_callback. '/');

var connect_callback = function(x) { id =


client.subscribe("/topic/echo", function(message) {

58 | Capítulo 4: STOMP sobre WebSocket


Machine Translated by Google

anexar_log(mensagem.corpo);
console.log(JSON.stringify(mensagem.body));

});
};

var error_callback = function(erro)


{ console.log(error.headers.message);
};

Em connect_callback você emite um comando subscribe para a fila /topic/echo para que qualquer
mensagem que apareça nesse compartimento seja anexada à área de texto da interface do usuário.
A implementação de error_callback simplesmente envia qualquer erro recebido ao console para
depuração conforme necessário.

Agora você tem um cliente que irá ecoar mensagens despejadas na fila para uma área de texto.
Em seguida, você conectará o processo de envio a uma nova função send_message que se parece
muito com a versão WebSocket:

var enviar_mensagem = função(dados) {


client.send("/topic/echo", {}, document.getElementById('message').value);
};

A principal diferença aqui é que, em vez de apenas enviar pelo WebSocket, você fornece a fila
(destino) e cabeçalhos extras, dos quais você não passa nenhum neste exemplo.

Resumo
Neste capítulo você criou um subprotocolo sobre WebSocket para STOMP 1.0. À medida que o
servidor foi construído, o cliente evoluiu para suportar os comandos necessários ao longo da rede
para suportar o protocolo. No final, embora o cliente que você construiu não suporte totalmente o
STOMP 1.0, ele permitiu que você testemunhasse como é fácil colocar outro protocolo em camadas
sobre o WebSocket e conectá-lo a um corretor de mensagens como o RabbitMQ.

Como você viu no Capítulo 2, implementar STOMP sobre WebSocket é um dos “Protocolos
Registrados” (e também se enquadra em um “Protocolo Aberto”). Nada impede você de usar as
informações deste capítulo para criar seu próprio protocolo de comunicação, porque a especificação
WebSocket suporta isso totalmente.

O próximo capítulo explora os problemas de compatibilidade que você enfrenta ao escolher


implementar o WebSocket e como garantir que você possa começar a usar o poder do WebSocket
hoje mesmo.

Resumo | 59
Machine Translated by Google
Machine Translated by Google

CAPÍTULO 5

Compatibilidade WebSocket

A tecnologia por trás do WebSocket permite a comunicação bidirecional entre cliente e servidor. Uma
implementação WebSocket nativa minimiza o uso de recursos do servidor e fornece um método
consistente de comunicação entre cliente e servidor.
Tal como acontece com a adoção do HTML5 em navegadores clientes, o cenário de suporte é relegado
aos navegadores modernos. Isso significa que não há suporte para qualquer usuário com Internet
Explorer inferior a 10 e suporte para navegador móvel inferior ao iOS Safari 6 e Chrome para Android.

Aqui estão apenas algumas das versões com suporte RFC 6455 WebSocket:

• Internet Explorer 10 •
Firefox 6

• Cromo 14

• Safári 6.0

• Ópera 12.1
• iOS Safári 6.0

• Chrome para Android 27.0

Este capítulo descreve opções para suportar navegadores mais antigos anteriores à especificação
Web- Socket RFC 6455. quando você quiser aproveitar as vantagens da comunicação bidirecional em
sua aplicação. As plataformas que você verá resolvem problemas de compatibilidade com navegadores
de clientes mais antigos e adicionam uma camada de organização às suas mensagens.

61
Machine Translated by Google

SockJS
SockJS é uma biblioteca JavaScript que fornece um objeto semelhante ao WebSocket no navegador.
A biblioteca é compatível com muitos outros navegadores devido ao uso condicional de vários navegadores.
transportes típicos do navegador. Ele usará WebSocket se a opção estiver disponível como primeiro
escolha. Se uma conexão nativa não estiver disponível, ela poderá voltar ao streaming e, finalmente,
polling se isso também não estiver disponível. Isso fornece navegador quase completo e restrições
suporte de proxy, conforme mostrado na Tabela 5-1.

Tabela 5-1. Transportes suportados

Navegador WebSockets Transmissão Votação

Ou seja, 6, 7 Não Não pesquisa jsonp

IE 8, 9 (cookies=não) Não streaming xdr pesquisa xdr

IE 8, 9 (cookies=sim) Não arquivo iframe-html pesquisa iframe-xhr

Ou seja, 10 rfc6455 streaming xhr pesquisa xhr

Cromo 6-13 hixie-76 streaming xhr pesquisa xhr

Cromo 14+ hybi-10 / rfc6455 xhr-streaming pesquisa xhr

Firefox <10 Não streaming xhr pesquisa xhr

Firefox 10+ hybi-10 / rfc6455 xhr-streaming pesquisa xhr

Safári 5 hixie-76 streaming xhr pesquisa xhr

Ópera 10.70+ Não iframe-eventsource iframe-xhr-polling

Konqueror Não Não pesquisa jsonp

Para usar totalmente a biblioteca SockJS, você precisa de um servidor equivalente. A biblioteca possui vários
opções para a contraparte do servidor, com mais sendo escritas o tempo todo. A seguir está
uma amostra de algumas das bibliotecas de servidor disponíveis:

• Nó SockJS

• SockJS-erlang

• SockJS Tornado

• Torcido em SockJS

• SockJS-ruby

62 | Capítulo 5: Compatibilidade com WebSocket


Machine Translated by Google

• SockJS-netty •

SockJS-gevent (bifurcação SockJS-gevent)

• SockJS-go

Para as nossas necessidades, vamos optar por uma solução totalmente JavaScript.

Servidor de bate-papo SockJS

Você revisitará seu aplicativo de bate-papo e fará alterações para usar as bibliotecas SockJS para
servidor e cliente.

Conforme mencionado, para usar totalmente a biblioteca cliente SockJS no navegador, você precisa
de um componente de servidor válido:

var expresso = require('expresso'); var


http = requer('http'); var sockjs
= require('sockjs'); var uuid =
require('uuid');

Sua lista de novas bibliotecas agora inclui SockJS, http da biblioteca Node.js padrão e Express.

Node.js possui um gerenciador de pacotes totalmente desenvolvido com


npm. Eles geralmente são instalados juntos, e uma simples chamada para
npm install [package] irá baixar a revisão mais recente. A instalação criará
um diretório node_modules se ele não existir e colocará os módulos dentro
dele. Se desejar instalar o módulo globalmente, você pode usar o
sinalizador -g . Para mais informações, confira os documentos.

Essas dependências não estarão disponíveis no Node.js por padrão, então execute os seguintes
comandos para instalá-las:

npm instalar sockjs


npm instalar expresso

A seguir, você criará um objeto SockJS e escutará o evento de conexão . Os eventos usados com
o nó SockJS são ligeiramente diferentes daqueles semelhantes dos clientes WebSocket:

• conexão

• dados (equivalente a mensagem com WebSocket)


• fechar

• erro

SockJS | 63
Machine Translated by Google

Express faz algo interessante com sua biblioteca exportando uma função como interface
para seu módulo. Isso é usado para criar um novo aplicativo Express e pode ser escrito
de duas maneiras:
var aplicativo = expresso();

Ou o muito mais conciso:


var expresso = require('expresso')();

Isso cria um aplicativo Express e permite atribuí-lo à variável imediatamente. Nos


bastidores, há alguma mágica do JavaScript acontecendo ao atribuir a função a
module.exports:
exportações = módulo.exportações = createApplication;

...

function criarAplicativo() {
...
}

Agora você pode criar seu novo servidor SockJS inicializando o express, criando um
httpServer com o aplicativo express e, finalmente, criando um servidor SockJS que
escuta o evento de conexão :
var aplicativo =
expresso(); var httpServer = http.createServer(app);
var sockServer = sockjs.createServer();

sockServer.on('conexão', function(conn) {
...
conn.on('mensagem', function(mensagem)
{ if(message.indexOf('/nick') === 0) {
var apelido_array = mensagem.split(' ');
if(nickname_array.length >= 2) { var
old_nickname = apelido; apelido
= apelido_array[1]; var
"
apelido_message = "Cliente" + apelido; + apelido_antigo + " mudou para

wsSend("nick_update", client_uuid, apelido, apelido_mensagem);

} } else
{ wsSend("mensagem", client_uuid, apelido, mensagem);

} });
...
}

A única alteração no tratamento de eventos do seu código anterior é escutar um evento


chamado dados em vez de mensagem. Além disso, você faz um pequeno ajuste em seu
método wsSend para levar em conta as diferenças com a API SockJS:

64 | Capítulo 5: Compatibilidade com WebSocket


Machine Translated by Google

var CONEXÃO = 0; var


ABERTO = 1; var
FECHAMENTO = 2; var
FECHADO = 3;

function wsSend(tipo, client_uuid, apelido, mensagem) {


for(var i=0; i<clientes.comprimento; i++) {
var clientSocket = clientes[i].connection;
if(clientSocket.readyState === OPEN)
{ clientSocket.write(JSON.stringify({ "type":
type, "id":
client_uuid,
"nickname": apelido,
"message":
mensagem }));
}
}
}

O objeto WebSocket que você usou anteriormente tinha constantes para a propriedade
readyState , mas aqui você as definirá no código do cliente (para evitar sobrecarregar o
código com números inteiros). O objeto de conexão SockJS tem a mesma propriedade
readyState , e você irá verificá-la em relação à constante OPEN , que tem o valor 1. A outra
grande mudança é o método para enviar dados de volta ao cliente, que é .write(message)
em vez disso de .send(mensagem).

Agora que você converteu tudo da versão WebSocket para usar o código específico do
SockJS, inicialize um novo aplicativo com Express e vincule o prefixo /chat à sua instância
http.Server :

var aplicativo =
expresso(); var httpServer = http.createServer(app);

sockServer.installHandlers(httpServer, {prefixo:'/chat'});
httpServer.listen(8181, '0.0.0.0');

O servidor HTTP escutará na porta 8181 e responderá às solicitações de escuta em


qualquer IP da máquina, como indica 0.0.0.0 .

No exemplo do Capítulo 3 você abriu seu arquivo HTML sem um servidor HTTP presente.
Com SockJS e as outras alternativas deste capítulo, você optará por servir o cliente e o
servidor a partir do mesmo servidor HTTP. Aqui você configura seu client.html e style.css
para serem enviados de volta mediante solicitação para http:// localhost:8181/ client.html:

express.get('/client.html', function (req, res)


{ res.sendfile(__dirname + '/client.html');
});

express.get('/style.css', function (req, res)


{ res.sendfile(__dirname + '/style.css');
});

SockJS | 65
Machine Translated by Google

Agora você converteu com êxito o servidor WebSocket simples em um que usa a biblioteca SockJS.

Cliente de bate-papo SockJS

Vejamos como converter o cliente para usar a biblioteca SockJS. A primeira coisa que você precisará
no início de qualquer outro JavaScript será incluir a biblioteca SockJS:

<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>

Esta biblioteca fornece o objeto SockJS, que imita a biblioteca WebSocket incluída na maioria dos
navegadores modernos. A inicialização também muda porque você não está usando o protocolo ws
ou wss , mas sim http como transporte inicial:

var sockjs = new SockJS("http://127.0.0.1:8181/chat");

Para o código do cliente WebSocket, você usou o nome da variável ws. Aqui parece mais apropriado
renomeá-lo para sockjs. Encontre todas as instâncias de uso de ws no código do Capítulo 3 e
substitua-as por sockjs. Essa é a extensão das mudanças exigidas para o cliente. SockJS oferece
uma migração fácil do WebSocket nativo para a biblioteca SockJS.

SockJS oferece suporte para um ou mais protocolos de streaming para todos os principais
navegadores, todos funcionando entre domínios e suportando cookies. Os transportes de polling
serão usados no caso de navegadores e hosts mais antigos com proxies restritivos como um substituto viável.

A seguir, você mudará seu aplicativo de bate-papo para usar a plataforma Socket.IO.

Soquete.IO

Usar o WebSocket diretamente é uma decisão fácil quando você pode controlar os clientes que estão
usando seu sistema. Como a maioria das organizações precisa atender a um ambiente de cliente
heterogêneo, outra alternativa é o Socket.IO. O desenvolvimento por trás do Socket.IO visa tornar
possíveis aplicativos em tempo real, independentemente do navegador.

A biblioteca é capaz de realizar esse feito recorrendo graciosamente a diferentes tecnologias que
realizam coisas semelhantes. Os transportes usados caso o WebSocket não esteja disponível no
cliente incluem o seguinte:

• Soquete Adobe Flash

• Sondagem longa

Ajax • Streaming multiparte Ajax


• Iframe eterno

66 | Capítulo 5: Compatibilidade com WebSocket


Machine Translated by Google

• Sondagem JSONP

Usar a implementação nativa do WebSocket seria semelhante a usar o TCP diretamente para
comunicação. Certamente é possível fazer isso e talvez na maioria dos casos seja a escolha certa,
mas não há vergonha em usar uma estrutura para fazer parte do trabalho pesado para você. Por
padrão, Socket.IO usará uma conexão WebSocket nativa se a interrogação do navegador considerar
possível.

Soquete Adobe Flash

Um dos transportes alternativos fornecidos pelo Socket.IO é o Adobe Flash Socket. Isso permite que
uma conexão semelhante ao WebSocket seja usada no Adobe Flash em vez do suporte nativo. Isso
tem a vantagem de uma conexão de soquete, com poucas desvantagens. No entanto, uma das
desvantagens é exigir que outra porta esteja aberta para o servidor de políticas.
Por padrão, Socket.IO verificará a porta 10843 e tentará usá-la, se disponível.

Conectando A

conexão com um servidor Socket.IO é obtida primeiro capturando as bibliotecas do cliente. Se o cliente
que você está usando for JavaScript, o método mais simples de fazer isso é simplesmente referenciar
o servidor Socket.IO e incluir o arquivo socket.io.js:
<script src="http://localhost:8181/socket.io/socket.io.js"></script>

O caminho mais fácil de servir a biblioteca cliente é a partir do próprio servidor Socket.IO. Se o seu
servidor web e o Socket.IO estiverem sendo servidos pelo mesmo host e porta, você poderá omitir o
host e a porta da chamada e referenciá-los como qualquer outro arquivo servido pelo servidor web.
Para servir a biblioteca cliente Socket.IO no mesmo host e porta, você terá que configurar seu servidor
web para encaminhar solicitações ao servidor Socket.IO ou clonar o repositório socket.io-client e
coloque os arquivos onde desejar.

Se você quiser armazenar em cache agressivamente a biblioteca cliente Socket.IO, uma configuração adicional
que você pode fazer é incluir o número da versão na solicitação, da seguinte forma:

<script src="/socket.io/socket.io.v1.0.js"></script>

Conforme discutimos no Capítulo 2, o WebSocket usa quatro eventos, ou “quadros de controle”. Com
o Socket.IO, tudo fica muito mais aberto no departamento de eventos. Os seguintes eventos são
disparados do próprio framework:

conexão

A conexão inicial de um cliente que fornece um argumento de soquete , que pode ser usado para
comunicação futura com o cliente.

mensagem O evento emitido quando o cliente invoca socket.send.

Soquete.IO | 67
Machine Translated by Google

desconectar
O evento que é acionado sempre que a conexão cliente-servidor é fechada.

qualquer
coisa Qualquer evento, exceto os reservados listados. O argumento data são os dados enviados e o retorno
de chamada é usado para enviar uma resposta.

Primeiras coisas primeiro. Depois de incluir a biblioteca cliente JavaScript, você precisa abrir uma conexão com
o servidor:

var soquete = io.connect('http://localhost:8181');

Agora que você tem uma conexão Socket.IO, pode começar a escutar eventos específicos que serão emitidos
pelo servidor. Seu aplicativo cliente pode escutar qualquer evento nomeado vindo do endpoint e também pode
emitir seus próprios eventos para serem ouvidos e reagirem no lado do servidor.

Servidor de bate-papo Socket.IO

Vamos revisitar novamente o exemplo do chat. Copie seu código do SockJS literalmente e faça a inicialização
semelhante à biblioteca anterior:

var socketio = require('socket.io');

...

var aplicativo =
expresso(); var httpServer = http.createServer(app);
var io = socketio.listen(servidor);

Como o Socket.IO usa nomenclatura aberta para eventos, não há necessidade de encaixar diferentes eventos de
entrada na mesma construção de mensagem . Portanto, com seu código Socket.IO você divide as mensagens e
as solicitações de apelidos em eventos separados:

conn.on('mensagem', function(dados)
{ wsSend("mensagem", client_uuid, apelido, mensagem);
});

...

conn.on('apelido', function(apelido) { var


apelido_antigo = apelido; apelido
= apelido.apelido; var
"
apelido_message = "Cliente " + apelido_antigo + " alterado para + apelido;
wsSend('apelido', cliente_uuid, apelido, apelido_mensagem);
})

68 | Capítulo 5: Compatibilidade com WebSocket


Machine Translated by Google

Você enviou o código para analisar uma solicitação de apelido para o cliente e também pode ouvir um
evento separado enviado do servidor para mensagens específicas de apelido e processá-las
logicamente de maneira diferente, se desejar.

Cliente de bate-papo Socket.IO

Quando o cliente deseja se comunicar com o servidor, ele executa a mesma função da API e emite um
evento nomeado que o servidor pode escutar. Devido à natureza de servir o HTML do Socket.IO no
mesmo servidor HTTP, você pode fazer referência ao Socket.IO do mesmo domínio sem especificar:

<script src="/socket.io/socket.io.js"></script>

Com SockJS, ele mapeia de perto as especificações nativas do WebSocket. Com o Socket.IO, a única
semelhança é ouvir eventos e enviá-los de volta ao servidor. Vários eventos são disparados a partir da
estrutura Socket.IO, o que deve ajudar a mantê-lo conectado e informado sobre a conexão e o status:

conectar
Emitido quando a conexão com o servidor é bem-sucedida

conectando
Emitido quando a conexão está sendo tentada com o servidor

desconectar
Emitido quando a conexão com o servidor foi desconectada

connect_failed
Emitido quando Socket.IO não conseguiu estabelecer uma conexão com todo e qualquer
mecanismo de transporte para fallback

erro
Emitido quando ocorre um erro que não é tratado por outros tipos de eventos

message
Emitido quando uma mensagem é recebida por meio de um socket.send e callback é uma função
de confirmação opcional

reconnect_failed
Emitido quando Socket.IO não consegue restabelecer uma conexão funcional após a conexão
cair

reconectar
Emitido quando Socket.IO se reconecta com sucesso ao servidor.

reconectando
Emitido quando Socket.IO está tentando se reconectar com o servidor

Soquete.IO | 69
Machine Translated by Google

qualquer
coisa Qualquer evento, exceto os reservados listados. O argumento de dados são os dados enviados e o retorno
de chamada é usado para enviar uma resposta

Além disso, como discutimos anteriormente, o socket.io-client está disponível se você quiser servir a
biblioteca sem usar o mecanismo normal.

Em sua nova base de código do cliente, o tamanho aumenta um pouco para lidar com o envio da
análise do comando de apelido para o frontend e a emissão de seu novo apelido de evento:

function sendMessage()
{ var messageField = document.getElementById('message'); var
mensagem = mensagemField.value;
if(message.indexOf('/nick') === 0) {
var apelido_array = mensagem.split(' ');
if(apelido_array.length >= 2)
{ socket.emit('apelido',
{ apelido: apelido_array[1]
});

} } else
{ socket.send(messageField.value);

} mensagemField.value = '';
mensagemField.focus();
}

Como você pode ver, você moveu o código originalmente no servidor para o lado do cliente e está
usando a chamada socket.emit(channel, data) do Socket.IO para enviar sua mudança de apelido
para o servidor.

Todo o resto no cliente é praticamente o mesmo. Você usa o método on(channel, data) do Socket.IO
para ouvir eventos específicos (reservados ou não) e processá-los normalmente.

Agora que você escreveu seu primeiro projeto Socket.IO, você pode consultar a documentação e
revise os recursos extras que ele oferece além do WebSocket e o que discutimos.

Vamos passar para mais um projeto, que é de natureza comercial e está no mesmo campo do
Socket.IO em termos de recursos agregados e valor que oferece além da implementação nativa do
WebSocket.

Pusher. com
A opção final que você verá é uma camada que fica sobre o WebSocket e oferece os substitutos que
você viu em outras soluções. A equipe por trás do Pusher criou uma lista impressionante de recursos
para usar com seu aplicativo, caso você opte por usar o serviço deles. Da mesma forma que as
outras duas soluções, Pusher implementou uma camada

70 | Capítulo 5: Compatibilidade com WebSocket


Machine Translated by Google

no topo do WebSocket por meio de sua API, juntamente com um método de teste de métodos
alternativos aceitáveis, caso os outros falhem.

A API é capaz de executar fallbacks de maneira muito semelhante ao Socket.IO, testando o suporte
WebSocket e, no caso de falha, usando o popular cliente web-socket.js, que substitui um objeto Flash
pelo suporte WebSocket no navegador. Se o Flash não estiver instalado ou se firewalls ou proxies
impedirem uma conexão bem-sucedida, o substituto final usará transportes baseados em HTTP.

Semelhante aos eventos que estão no topo do transporte do Socket.IO, a API Pusher tem mais alguns
truques na manga. Possui canais do tipo público e privado, o que permite filtrar e controlar a
comunicação com o servidor. Também está disponível um tipo especial de canal para presença, onde
o cliente pode cadastrar dados de associado para mostrar o status online.

A principal diferença aqui é que você está incluindo terceiros em sua comunicação entre servidor e
cliente. Seu servidor receberá comunicação do código do cliente provavelmente usando uma simples
chamada Ajax de HTML e, com base nisso, usará a API REST Pusher.com para acionar eventos. O
cliente será conectado ao Pusher.com, esperançosamente, por meio do WebSocket, se estiver
disponível no navegador ou em um dos métodos alternativos, e receberá eventos acionados no
aplicativo. Somente com várias restrições atendidas um cliente pode disparar eventos e transmiti-los
pela rede sem passar primeiro pela API do seu próprio servidor.

Vejamos alguns dos aspectos particulares da API Pusher.com, porque eles são bastante extensos.

Canais
Usar WebSocket nativo é uma maneira perfeita de obter comunicação bidirecional com o entendimento
de que os clientes devem suportar o protocolo WebSocket, que você pode superar quaisquer problemas
de proxy e que construirá qualquer código de infraestrutura necessário para facilitar a vida do cliente.
Processo interno. Você obterá um fluxo de dados do quadro de mensagem de texto ou binário, e cabe
a você analisá-lo, entendê-lo e passá-lo para qualquer manipulador configurado em seu código.

A API Pusher fornece um pouco disso para você. Canais são uma construção de programação comum
e usados com esta API para filtrar dados e controlar o acesso. Um canal passa a existir simplesmente
quando um cliente se inscreve nele e vincula eventos a ele.

Pusher possui bibliotecas para muitas das principais estruturas e linguagens populares atualmente.
Nós nos concentramos, como sempre, em JavaScript. Aqui você verá como se inscrever em um canal
chamado channelName. Depois de ter sua variável de canal , você poderá usá-la para enviar e receber
eventos.

Pusher.com | 71
Machine Translated by Google

Com a maioria das operações do canal, você pode vincular-se a um evento que irá notificá-lo sobre o sucesso
ou falha da assinatura – pusher:subscription_succeeded:

var canal = pusher.subscribe(canalNome);

Dessa forma, você criou um canal público nomeado no qual qualquer cliente conectado ao servidor pode assinar
ou cancelar a assinatura. E cancelar a assinatura também é tão simples quanto possível. Basta fornecer o
channelName e a API cancelará sua inscrição para ouvir nesse canal:

pusher.unsubscribe(nomedocanal);

A API também fornece assinatura de canal privado. A permissão deve ser autorizada por meio de um URL de
autenticação solicitado por HTTP. Todos os canais privados são prefixados com private como convenção de
nomenclatura, conforme mostrado no exemplo de código a seguir. A autenticação pode acontecer via Ajax ou
JSONP:

var privateChannelName = "private-mySensitiveChannelName"; var


privateChannel = pusher.subscribe(privateChannelName);

Um dos recursos mais necessários ao usar a comunicação bidirecional é o gerenciamento de estado para a
presença dos membros. O Pusher fornece para isso chamadas especializadas para a presença do usuário,
juntamente com eventos a serem escutados para garantir a integridade.

Os eventos a seguir são aqueles que você pode ouvir para garantir que as expectativas foram atendidas:

pusher:subscription_succeeded Comum em
todas as chamadas de canal. A vinculação a esse evento permite garantir que uma assinatura foi bem-
sucedida.

empurrador:subscription_error
Vincule-se a este evento para ser notificado quando uma assinatura falhar.

empurrador: membro_added
Este evento é acionado quando um usuário entra em um canal. Este evento é acionado apenas uma vez
por usuário único, mesmo que um usuário tenha ingressado em vários canais de presença.

pusher:member_removed Este
evento é acionado quando um usuário sai de um canal. Como um usuário pode ingressar em vários
canais, esse evento é acionado somente quando o último canal é fechado.

Eventos

Os eventos no Pusher são a forma como as mensagens são transmitidas entre o servidor e o cliente. Um canal,
seja público ou privado, pode conter eventos que transmitem esses dados ao cliente. Se você deseja filtrar
mensagens em grupos diferentes, os eventos não são o caminho, mas os canais, sim. Os eventos no Pusher
são apropriadamente nomeados no passado porque são notificações de coisas que aconteceram no sistema.

72 | Capítulo 5: Compatibilidade com WebSocket


Machine Translated by Google

Se você tiver um canal chamado chat, você gostaria de estar ciente quando novas mensagens estivessem ocorrendo
para poder pintar isso na GUI:

var pusher = new Pusher('APP_KEY'); var


canal = pusher.subscribe('chat-websocket'); channel.bind('new-
message', function(data) { // adiciona novas mensagens
à nossa coleção } );

A vinculação por meio de um canal não é necessária. Com a mesma facilidade com que você vincula eventos de
disparo de um canal, você pode fazer isso usando a variável root pusher :

var pusher = new Pusher('APP_KEY');


pusher.bind(eventName, function(data) { //
processa os dados de eventName
});

Obviamente, a API do Pusher e os padrões de uso que você pode ter são bastante vastos. A API Pusher é bem
projetada e capaz de processar um número absurdo de mensagens por dia e um número de conexões simultâneas.
Na próxima seção você realizará o mesmo exercício feito anteriormente com Socket.IO e construirá uma pequena
aplicação de chat usando a API Pusher.

Servidor de bate-papo empurrador

Você escreveu um aplicativo de bate-papo simples usando Socket.IO e SocksJS, e agora é hora de pegar o
conhecimento que você adquiriu com a API e a maneira de fazer as coisas do Pusher.com e reescrever o bate-papo.
A principal diferença é que o envio de suas mensagens de chat do cliente será feito por meio de uma API que você
criou em seu servidor. Todos os eventos acionados para o Pusher.com acontecem por meio do seu servidor, e os
eventos vinculados nos canais são passados do Pusher.com para o cliente usando o WebSocket ou o substituto.

Vamos primeiro descrever seu servidor, incluindo um shell das chamadas de API e as dependências necessárias.
Primeiramente, você precisa instalar as dependências do seu nó usando npm:

$ npm install node-uuid $


npm install pusher $ npm
install express $ npm install
body-parser

Você instalou e usou o node-uuid em vários outros exemplos de servidores. Esta seção é obviamente sobre a API
Pusher.com, então você instalará sua biblioteca Node.js. Para ouvir e analisar o corpo das mensagens como JSON,
você está usando express e body-parser.

Aqui está um shell da aparência do seu servidor:

var expresso = require('expresso'); var


http = requer('http');

Pusher.com | 73
Machine Translated by Google

var Pusher = require('pusher'); var


uuid = require('node-uuid'); var
bodyParser = require('body-parser');

var aplicativo =
expresso(); app.use(bodyParser.json());

var httpServer = http.createServer(app);

var pusher = new


Pusher({ appId: 'SEU-
APP-ID', chave: 'SEU-APP-KEY',
segredo: 'SEU-APP-SEGREDO'
});

var clientes = {}; var


índicecliente = 1;

function sendMessage(tipo, client_uuid, apelido, mensagem) { }

app.post("/apelido", function(req, res) { });

app.post("/login", function(req, res) { });

app.post("/chat", function(req, res) { });

app.ouvir(8181);

app.get('/client.html', function (req, res)


{ res.sendfile(__dirname + '/client.html'); });

Como você pode ver, você solicitou e incluiu suas dependências, acionou o express com o
analisador de corpo e fez com que ele escutasse na porta 8181 e atendesse seu modelo de
cliente. Sua API consiste nas chamadas listadas na Tabela 5-2.

Tabela 5-2. Chamadas de API

Ponto final do método HTTP Descrição

PUBLICAR /nickname Atualiza o apelido do cliente e notifica todos os clientes conectados

PUBLICAR /Conecte-se Conexão inicial que atribui um apelido anônimo e um ID de cliente exclusivo

PUBLICAR /bater papo As mensagens do chat são repassadas junto com o apelido e ID do cliente

74 | Capítulo 5: Compatibilidade com WebSocket


Machine Translated by Google

A chamada sendMessage não faz parte da API, mas é uma função de conveniência usada
por vários exemplos. Ele aciona um evento do tipo no chat do canal, que você vinculou ao
iniciar o servidor. O JSON que você está transmitindo para todas as mensagens inclui o ID
do cliente, o apelido, se aplicável, e a mensagem:

function sendMessage(tipo, client_uuid, apelido, mensagem) {


pusher.trigger('chat', type, { "id":
client_uuid, "apelido":
apelido, "mensagem":
mensagem });

A primeira chamada de API que um cliente deve fazer é fazer login. O cliente receberá um
identificador exclusivo na forma de uuid e um apelido indexado exclusivo:

app.post("/login", function(req, res) {


var cliente_uuid = uuid.v4(); var
apelido = "AnonymousUser" + clientIndex;
índicecliente+=1;

clientes[client_uuid] = { 'id':
client_uuid, 'apelido':
apelido
};

res.status(200).send(
JSON.stringify(clientes[client_uuid])
);
});

É provável que seus clientes queiram que seus próprios apelidos sejam representados no aplicativo
de chat. Uma chamada para /nickname fará a alteração solicitada e acionará um apelido de evento
para permitir que os clientes mostrem a alteração no frontend:

app.post("/apelido", function(req, res) {


var old_nick = clientes[req.body.id].apelido;

var apelido = req.body.apelido;


clientes[req.body.id].nickname = apelido;

sendMessage('apelido',
req.body.id,
apelido,
"
old_nick + "alterou o apelido para + apelido);

res.status(200).send('');
});

Pusher.com | 75
Machine Translated by Google

A mais simples de todas é a mensagem de chat. Você aceita o ID do cliente, pega o apelido do
seu array existente e usa a mensagem passada no JSON e aciona um evento de mensagem
até o canal de chat Pusher.com:

app.post("/chat", function(req, res)


{ sendMessage('mensagem',
req.body.id,
clientes[req.body.id].apelido,
req.body.message);

res.status(200).send('');
});

Cliente de bate-papo pusher

Seu servidor agora está aguardando a conexão de um cliente. Você usará o mesmo modelo
HTML dos capítulos anteriores e o HTML do chat do Capítulo 3 para tornar a vida mais simples.
Vamos descrever o que é necessário para sincronizar seu cliente com Pusher.com.

Primeiro, você precisa incluir a biblioteca Pusher.com em seu código HTML:

<script src="http://js.pusher.com/2.1/pusher.min.js"></script>

Dentro do seu código JavaScript, você inicializa seu objeto Pusher com a chave do aplicativo
fornecida no painel do Pusher e se inscreve imediatamente no canal de bate-papo :

var pusher = new Pusher('SEU-APP-KEY'); var


canal = pusher.subscribe('chat'); var id;

pusher.connection.bind('connected', function() { $.ajax({ url:


'http://
localhost:8181/login', type: 'POST', dataType:
'json',
contentType:
"application/ json", completo:
function(xhr, status) { if(xhr.status ===
200) { console.log("login bem-
sucedido.");
}
},
sucesso: função(resultado)
{ appendLog('*', resultado.apelido + id = " conectado");
resultado.id;
}
})
});

pusher.connection.bind('disconnected', function() { appendLog('*',


'Conexão fechada');
});

76 | Capítulo 5: Compatibilidade com WebSocket


Machine Translated by Google

função desconectar()
{ pusher.disconnect();
}

canal.bind('mensagem', function(dados) {
anexarLog(dados.apelido, dados.mensagem);
});

canal.bind('apelido', function(dados)
{ appendLog('*', data.message);
});

O objeto de conexão Pusher emitirá vários eventos, e você estará preocupado apenas com conectado e
desconectado. Após se inscrever no canal de chat , você se vincula a dois eventos específicos desse canal:
mensagem e apelido. Para cada um deles, você mostrará mensagens de notificação no frontend do cliente. Ao
vincular e receber o evento conectado , você envia sua solicitação de login para a API do servidor e recebe de
volta seu ID de cliente para ser transmitido em mensagens subsequentes. A Figura 5-1 é um exemplo de aplicativo
de bate-papo usando Pusher.com.

Figura 5-1. Exemplo de bate-papo pusher

Você viu um exemplo concreto de uso dos princípios básicos da API disponível para você. A intenção é mostrar o
que é possível com alternativas ao WebSocket, e o Pusher é definitivamente digno de consideração como uma
alternativa ao WebSocket puro.

Pusher.com | 77
Machine Translated by Google

Não se esqueça: Pusher é uma solução comercial


Ao contrário de WebSocket, Socket.IO e SocksJS, esta estrutura é um serviço comercial.
A avaliação da solução e dos benefícios que ela proporciona deve ser feita pela sua equipe.
Em geral, os diferentes níveis de preços são baseados em conexões, mensagens e se a
conexão está ou não protegida por criptografia SSL. Para uma avaliação mais aprofundada,
revise a página de preços do Pusher.

Proxy reverso
Uma das coisas que você provavelmente também será solicitado a fazer é proxy da conexão
WebSocket por trás de um servidor web. Os dois servidores web mais comuns são nginx e
Apache. A configuração para eles é bastante simples, com o nginx tendo a funcionalidade
integrada no próprio servidor e o Apache usando um módulo chamado proxy_wstunnel. Em vez
de entrar em muitos detalhes sobre como configurar esses dois servidores para fazer proxy das
conexões, aqui estão dois artigos de blog que os discutem:

• nginx •
apache

Resumo
Este capítulo apresentou três maneiras populares de aproveitar o poder da comunicação
bidirecional ao lidar com uma API de nível superior. Essas soluções oferecem o poder do
WebSocket caso seu cliente esteja usando um navegador de cliente moderno e recorrem ao
soquete Flash ou outras soluções menos otimizadas para clientes mais antigos. Além disso, as
duas últimas estruturas adicionam recursos que não são suportados nativamente pelo
WebSocket, limitando a quantidade de código que você terá que escrever para suportar a
comunicação de suas aplicações. O próximo capítulo analisa os métodos para proteger sua
comunicação WebSocket.

78 | Capítulo 5: Compatibilidade com WebSocket


Machine Translated by Google

CAPÍTULO 6

Segurança WebSocket

Este capítulo detalha o aparato de segurança WebSocket e as várias maneiras de usá-lo para
proteger os dados que estão sendo transmitidos pelo protocolo subjacente. Você aprenderá por que
é sempre uma boa ideia comunicar-se por TLS (Transport Layer Security) para evitar proxies
ineficazes e ataques man-in-the-middle e garantir a entrega de quadros. A discussão se concentra
na configuração da conexão WebSocket por TLS com wss:// (Web-Socket Secure), segurança
baseada na origem, mascaramento de quadros e limites específicos impostos pelos navegadores
para garantir que as mensagens não sejam sequestradas.

Como acontece com qualquer discussão sobre segurança, o conteúdo deste capítulo apresenta os
dados mais conhecidos da atualidade sobre como proteger adequadamente sua comunicação
WebSocket. Porém, a segurança é inconstante e o jogo de gato e rato jogado com aqueles que
procuram explorar e aqueles que trabalham para bloquear é constante e interminável. A validação
de dados e múltiplas verificações são ainda mais importantes ao usar o WebSocket. Você começará
configurando o WebSocket por TLS.

TLS e WebSocket
Todas as demonstrações até agora usaram a versão não criptografada da comunicação WebSocket
com a string de conexão ws:// . Na prática, isso deveria acontecer apenas nas hierarquias mais
simples, e toda comunicação via WebSocket deveria acontecer por TLS.

Gerando um certificado autoassinado


Uma conexão válida baseada em TLS via WebSocket não pode ser feita sem uma certificação válida
cate. O que abordarei rapidamente aqui é uma maneira de gerar um certificado autoassinado usando
OpenSSL. A primeira coisa que você precisa fazer é garantir que, caso ainda não tenha o OpenSSL
instalado, você segue o conjunto de instruções apresentadas a seguir que são específicas para sua
plataforma.

79
Machine Translated by Google

Instalando no Windows Esta

seção cobre apenas o download e a instalação do binário pré-compilado disponível no Windows. Como
discutimos no Capítulo 1, para os masoquistas entre nós, você pode baixar o código-fonte e compile
você mesmo.

Para o resto de nós, baixe o executável independente do Windows. Você deverá ser capaz de executar
o OpenSSL depois disso por meio dos exemplos seguindo as instruções nas instalações do OS X e
Linux.

Instalando no OS X O

método mais fácil de instalar o OpenSSL no OS X é através de um gerenciador de pacotes como o


Homebrew. Isso permite uma atualização rápida e fácil sem a necessidade de baixar novamente um
pacote da Web. Supondo que você tenha o Homebrew instalado:

preparar instalar o openssl

Instalando no Linux

Existem tantas versões do Linux que seria impossível ilustrar como instalar em todas elas. Vou reiterar
como instalá-lo via apt no Ubuntu. Se você estiver executando outra distro, você pode ler o guia de
compilação e instalação instruções do OpenSSL.

Usar o apt para instalação requer alguns passos simples:

sudo apt-get update sudo


apt-get install openssl

Configurando WebSocket sobre TLS Agora

que você tem OpenSSL instalado, você poderá usá-lo para gerar um certificado a ser usado em testes
ou para enviá-lo a uma autoridade de certificação.

Uma autoridade certificadora (CA) emite certificados digitais em uma


infraestrutura de chave pública (PKI). A CA é uma entidade confiável que
certifica a propriedade de uma chave pública como sujeito nomeado do certificado.
O certificado pode ser usado para validar essa propriedade e criptografar
todas as informações transmitidas pela rede.

Aqui está o que você fará no seguinte bloco de código:

• Gerar uma chave de 2.048 bits com uma senha •

Reescrever essa chave removendo a senha • Criar

uma solicitação de assinatura de certificado (CSR) a partir dessa chave

80 | Capítulo 6: Segurança WebSocket


Machine Translated by Google

• Gerar um certificado autoassinado a partir da chave e do CSR

A primeira coisa a fazer é gerar uma chave de 2.048 bits. Faça isso usando o comando openssl para gerar o par de
chaves RSA:

% openssl genrsa -des3 -passout pass:x -out server.pass.key 2048

Gerando chave privada RSA, módulo longo de 2.048 bits


.................................................. .............................
++........++++
e é 65537 (0x10001)

Em seguida, você gera uma chave privada sem senha para eventual criação de um CSR, que pode ser usado para
um certificado autoassinado ou para receber um certificado autorizado por uma autoridade de certificação. Depois de
gerar a chave, você também pode remover a chave com a senha:

% openssl rsa -passin pass:x -in server.pass.key -out server.key escrevendo


chave RSA % rm
server.pass.key

Agora que você tem sua chave privada, você pode usá-la para criar um CSR que será usado para gerar o certificado
autoassinado para enviar comunicação WebSocket segura:

% openssl req -new -key server.key -out server.csr -subj '/


C=US/ST=Califórnia/L=Los Angeles/O=Mystic Coders, LLC/ OU=Tecnologia
da Informação/CN=ws.mysticcoders. com/
emailAddress=fakeemail AT gmail DOT com/
subjectAltName=DNS.1=endpoint.com' > server.csr

Se você deseja configurar um certificado de servidor adequado, o arquivo CSR é tudo que você precisa. Você
receberá um arquivo de certificado da Autoridade de Certificação, que poderá então usar. Enquanto você espera,
vamos obter o certificado autoassinado para teste e substituí-lo mais tarde.

Use o código a seguir para gerar seu certificado para uso no código do servidor:

% openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt Assinatura ok
subject=/C=US/
ST=Califórnia/L=Los Angeles/...
Obtendo a chave privada

No diretório que você escolheu para executar tudo, agora você deve ter três arquivos:

• servidor.chave

• servidor.csr

• servidor.crt

Se decidir enviar itens a uma Autoridade de Certificação para obter um certificado validado, você enviará o arquivo
server.csr junto com o procedimento de configuração para receber uma chave. Porque você é

TLS e WebSocket | 81
Machine Translated by Google

apenas usando um certificado autoassinado aqui para fins de teste, você continuará
com o certificado gerado server.crt. Decida onde você manterá a chave privada e os
arquivos de certificado (nesse caso, você os colocará em /etc/ssl/certs).

Exemplo de servidor WebSocket sobre TLS

No código a seguir, você verá um exemplo de uso do módulo https para permitir que
a comunicação WebSocket bidirecional aconteça por TLS e escute na porta 8080:
var fs = require('fs');

// você provavelmente carregará a configuração de config var


cfg = { ssl:
true, port:
8080,
ssl_key: '/etc/ssl/certs/server.key', ssl_cert: '/
etc/ssl/certs/server.crt '
};

var httpsServ = require('https'); var


WebSocket = require('ws'); var
WebSocketServer = WebSocket.Server;

var aplicativo = null;

// processamento de solicitação
fictícia var processRequest = function( req, res ) {
res.writeHead(200);
res.end("Olá!\n");
};

app = httpsServ.createServer({ chave:


fs.readFileSync( cfg.ssl_key ), certificado:
fs.readFileSync( cfg.ssl_cert )

}, processRequest ).listen( cfg.port );

var wss = new WebSocketServer( { servidor: app });

wss.on( 'conexão', função ( wsConnect ) {

wsConnect.on( 'mensagem', função ( mensagem ) {


console.log( mensagem );
});

});

82 | Capítulo 6: Segurança WebSocket


Machine Translated by Google

Alterar o código do cliente para usar uma conexão WebSocket por TLS é trivial:

var ws = new WebSocket("wss://localhost:8080");

Ao fazer essa conexão, a página da web usada para carregá-la também deve se conectar por TLS. Na
verdade, se você tentar carregar uma conexão WebSocket insegura de um site usando o protocolo https ,
ocorrerá um erro de segurança na maioria dos navegadores modernos por tentar carregar conteúdo
inseguro. Conteúdo misto é um vetor de ataque comum e sua permissão é justamente desencorajada. Na
maioria dos navegadores modernos, o uso de conteúdo misto não só é ativamente desencorajado, como
também é proibido. Chrome, Firefox e Internet Explorer geram erros de segurança e se recusam a se
comunicar por qualquer outro meio que não seja o WebSocket Secure caso a página que está sendo
carregada também seja veiculada por TLS. O Safari, infelizmente, não faz a coisa certa. A Figura 6-1 é
um exemplo do Chrome que mostra os erros no console ao tentar se conectar a um servidor WebSocket
inseguro.

Figura 6-1. Erro de segurança de conteúdo misto com o Chrome

Qualys Labs tem um bom gráfico identificar os navegadores que lidam com conteúdo misto de maneira
adequada e aqueles que não o fazem.

Agora que sua conexão está criptografada, veremos outros métodos de segurança do canal de
comunicação na próxima seção.

Modelo de segurança baseado na origem

Sempre houve uma corrida entre aqueles que procuram explorar vulnerabilidades num mecanismo de
transporte e aqueles que procuram protegê-lo. O protocolo WebSocket não é exceção. Quando o
XMLHttpRequest (XHR) apareceu pela primeira vez com o Internet Explorer, ele estava limitado à política
de mesma origem (SOP) para todas as solicitações ao servidor. Existem inúmeras maneiras de explorar
isso, mas funcionou bastante bem. À medida que o uso do XHR evoluiu, porém, tornou-se necessário
permitir o acesso a outros domínios. O Cross Origin Resource Sharing (CORS) foi o resultado desse
esforço; se usado corretamente, o CORS pode minimizar ataques de script entre sites e ainda permitir
flexibilidade.

CORS, ou Cross-Origin Resource Sharing, é um método de controle de


acesso empregado pelo navegador, geralmente para solicitações Ajax de
um domínio fora do domínio de origem. Para obter mais informações sobre
o CORS, consulte a documentação da Mozilla.

Modelo de segurança baseado na origem | 83


Machine Translated by Google

O WebSocket não impõe nenhuma restrição de política de mesma origem ao acesso a


servidores WebSocket. Também não emprega CORS. O que resta em relação à validação
do Origin é a verificação do lado do servidor. Todos os exemplos anteriores usaram a
biblioteca ws simples e rápida com Node.js. Você continuará fazendo isso e verá na
inicialização como é simples empregar uma verificação de origem para garantir que a conexão
do navegador seja apenas a origem esperada:

var WebSocketServer = require('ws').Server, wss = new


WebSocketServer({ porta: 8181,
origem: 'http://
mydomain.com', verifyClient:
function(info, callback) {
if(info.origin === 'http://meudominio.com') { callback(true);
retornar;

}
retorno de chamada(falso);
}
}),

Se você escrever uma função verifyClient para a biblioteca que está usando, poderá enviar
um retorno de chamada com verdadeiro ou falso , indicando uma validação bem-sucedida de
qualquer informação, incluindo o cabeçalho Origin . Após o sucesso, você verá uma troca
HTTP atualizada válida para o Origin http:// mydomain.com.

A troca HTTP que acontece como resultado é a seguinte:

GET ws://mydomain.com/ HTTP/1.1


Origem: http://mydomain.com Host:
http://mydomain.com Sec-
WebSocket-Key: zy6Dy9mSAIM7GJZNf9rI1A==
Atualização: websocket
Conexão: Atualização
Sec-WebSocket-Version : 13

Conexão de protocolos de comutação


HTTP/1.1 101:
Atualização Sec-WebSocket-Accept:
EDJa7WCAQQzMCYNJM42Syuo9SqQ= Atualização: websocket

Se o cabeçalho Origin não corresponder, a biblioteca ws enviará de volta um cabeçalho 401


Unauthorized. A conexão nunca completará o handshake e nenhum dado poderá ser enviado
e recebido. Se isso acontecer, você receberá uma resposta semelhante a esta:

HTTP/1.1 401 Tipo de conteúdo


não autorizado: texto/html

Deve-se notar também que a verificação do cabeçalho Origin não constitui uma conexão
segura e autorizada por um cliente válido. Você poderia facilmente passar o cabeçalho Origin
adequado de um script executado fora do navegador. O cabeçalho Origin pode ser assustador

84 | Capítulo 6: Segurança WebSocket


Machine Translated by Google

alimentado com quase nenhum esforço fora do navegador. Portanto, estratégias adicionais devem ser
empregadas para garantir que sua conexão seja autorizada.

O principal benefício de exigir o cabeçalho Origin é combater ataques de Cross-Site Request Forgery
(CSRF) do tipo WebSocket, também chamados de Cross-Site WebSocket Hijacking (CSWSH), seja
possível, pois o cabeçalho Origin é passado pelo agente do usuário e não pode ser modificado pelo
código JavaScript. A confiança implícita, portanto, vai para o navegador do cliente neste caso e para
as restrições que ele impõe ao código baseado na web.

Clickjacking

Uma outra área de preocupação com o WebSocket e a Web em geral é chamada de clickjacking . O
processo envolve enquadrar o site solicitado pelo cliente e executar o código no quadro oculto sem o
conhecimento do usuário.

Para combater isso, os desenvolvedores web criaram métodos chamados framebusting. para garantir
que o site que seus usuários estão visitando não seja enquadrado de forma alguma.

Uma maneira simples e ingênua de sair de um quadro é a seguinte:

if (top.location ! = localização) {
top.location = self.location;
}
Isso tende a falhar, entretanto, devido a inconsistências na forma como os navegadores lidaram com
essas propriedades com JavaScript no passado. Outros problemas que surgem são a disponibilidade
de JavaScript no sistema, ou possivelmente no iframe, que pode ser restrito em determinados
navegadores.

A técnica de framebusting baseada em JavaScript mais completa disponível, que vem de um estudo
da Stanford Web Security Research sobre framebusting, está descrito no seguinte trecho:

<estilo>
corpo { display: nenhum; } </
style>

<script>
if(self===topo) {
documentos.getElementsByTagName("corpo")[0].style.display = 'bloco'; } else

{ top.location = self.location;

} </script>

De todas as soluções baseadas em JavaScript, esta permite impedir que o usuário visualize sua
página se ela estiver sendo enquadrada. A página também permanecerá em branco se o JavaScript
estiver desativado ou se for tentada qualquer outra forma de explorar o código framebusting. Como o
Web-Socket é baseado em JavaScript, eliminar qualquer quadro removerá qualquer habilidade de um invasor.

Modelo de segurança baseado na origem | 85


Machine Translated by Google

sequestrar o navegador e executar código sem o conhecimento dos usuários. A seguir, você verá
uma abordagem baseada em cabeçalho que pode ser usada em conjunto com o script anterior e uma
prova de conceito chamada Waldo, que tira vantagem desse vetor de ataque.
Usar as técnicas mencionadas aqui tornará o código Waldo discutível.

X-Frame-Options para Framebusting O método

mais seguro de contornar o clickjacking foi introduzido pela Microsoft com o Internet Explorer 8 e
envolve uma opção de cabeçalho HTTP chamada X-Frame-Options.
A solução pegou e se tornou popular entre todos os principais navegadores, incluindo Safari, Firefox,
Chrome e Opera, e foi oficialmente padronizada como RFC 7034. Continua sendo a maneira mais
eficaz de sair dos frames. A Tabela 6-1 mostra os valores aceitáveis que podem ser passados pelo
servidor para garantir que apenas políticas aceitáveis sejam usadas para enquadrar o site.

Tabela 6-1. Valores aceitáveis das opções X-Frame


Valor do cabeçalho Descrição do comportamento

NEGAR Impede o enquadramento do código

MESMA ORIGEM Impede enquadramentos por sites externos

ALLOW-FROM origin Permite enquadramento apenas pelo site especificado

Por que tudo isso é importante em relação à comunicação WebSocket? Uma prova de conceito
chamada Waldo mostra como pode ser simples para um pedaço de JavaScript comprometido
controlar e reportar dados para um servidor WebSocket. Estas são algumas das coisas que Waldo é
capaz de alcançar:

• Enviar de volta cookies ou DOM

• Instalar e recuperar resultados do keylogger •

Executar JavaScript personalizado


• Uso em um ataque de negação de serviço

Todos os navegadores modernos suportam WebSocket, e a única defesa real contra esse vetor de
ataque são contramedidas anti-framing, como X-Frame-Options e, em menor grau, o outro código
frame-buster baseado em JavaScript revisado anteriormente.

Se quiser testar o Waldo, você pode encontrar instruções de instalação no site. Esteja ciente de que
as versões das bibliotecas de suporte usadas pelo Waldo foram avançadas.

Aqui estão as versões suportadas para compilar o Waldo:

86 | Capítulo 6: Segurança WebSocket


Machine Translated by Google

• websocketpp Biblioteca WebSocket (versão 0.2.x) • Boost

com versão 1.47.0 (pode usar gerenciador de pacotes)

Depois de instalar o websocketpp e baixar o waldo.zip, modifique o arquivo common.mk com os caminhos
corretos para boost e websocketpp e build. Crie uma página HTML simples que inclua o JavaScript comprometido
em um quadro oculto e carregue o site normal em outro quadro. Certifique-se de ter o aplicativo Waldo C++ em
execução e controle à vontade. Waldo é totalmente relevante, pois foi lançado com base na RFC 6455 e ainda
funciona bem nos navegadores mais recentes.

Para ferramentas mais detalhadas que permitem usar o navegador como vetor de ataque, incluindo o uso do
WebSocket para realizar esses testes, confira BeEF, que vem com uma interface RESTful e GUI, e XSSChef,
que é instalado como uma extensão comprometida do Google Chrome.

Negação de serviço

O WebSocket, por sua própria natureza, abre conexões e as mantém abertas. Um vetor de ataque que tem
sido comumente usado com servidores web baseados em HTTP é abrir centenas de conexões e mantê-las
abertas indefinidamente, enviando lentamente dados válidos de volta ao servidor web para evitar que ocorra
um tempo limite e esgote os threads disponíveis usados no servidor web. o servidor. O termo dado ao ataque é
Slowloris, e enquanto servidores mais assíncronos, como o nginx pode mitigar o efeito, nem sempre é
totalmente eficaz. Algumas práticas recomendadas a serem observadas para diminuir esse ataque incluem o
seguinte:

• Adicione limitação baseada em IP para garantir que as conexões provenientes de uma única fonte não
sobrecarreguem o número de conexões disponíveis. • Garantir que todas as

ações solicitadas por um usuário sejam geradas de forma assíncrona


na extremidade do servidor para diminuir o impacto dos clientes conectados.

Máscara de quadro
O protocolo WebSocket (RFC 6455), discutido com mais detalhes no Capítulo 8, define uma chave de
mascaramento de 32 bits que é definida usando o bit MASK no quadro WebSocket.
A máscara é uma chave aleatória escolhida pelo cliente e é uma prática recomendada que todos os clientes
definam o bit MASK junto com a passagem da chave de mascaramento obrigatória. Cada quadro deve incluir
uma chave de mascaramento aleatória do lado do cliente para ser considerado válido. A chave de mascaramento
é então usada para XOR dos dados da carga útil antes de enviá-los ao servidor, e o comprimento dos dados da
carga útil permanecerá inalterado.

Você pode estar dizendo a si mesmo: “Isso é ótimo, mas por que deveria me preocupar com isso quando
falamos de segurança?” Duas palavras: envenenamento de cache. A realidade de um aplicativo em

Negação de serviço | 87
Machine Translated by Google

o problema é que você não pode controlar servidores proxy que se comportam mal, e a relativa novidade
do WebSocket, infelizmente, significa que ele pode ser um vetor de ataque para os mal-intencionados.

Um artigo de 2011 intitulado “Conversando consigo mesmo para diversão e lucro” descreveu vários métodos
para enganar um proxy para que ele forneça o arquivo JavaScript do invasor. Na verdade, o mascaramento
introduz um pouco de variabilidade que é injetada em cada mensagem do cliente, que não pode ser
explorada pelo código JavaScript malicioso de um invasor. O mascaramento de dados garante que o
envenenamento do cache seja menos provável de acontecer devido à variabilidade nos pacotes de dados.

A desvantagem do mascaramento é que ele também impede que as ferramentas de segurança identifiquem
padrões no tráfego. Infelizmente, como o WebSocket ainda é um protocolo bastante novo, um bom número
de proxies, firewalls e softwares DLP (prevenção contra perda de dados) de rede e endpoint não conseguem
inspecionar adequadamente os pacotes enviados pela rede.
Além disso, como muitas ferramentas não sabem como inspecionar adequadamente os frames WebSocket,
dData pode ser ocultado em sinalizadores reservados, buffer overflows ou underflows são possíveis e
código JavaScript malicioso também pode ser ocultado no frame da máscara.

Não acredite em ninguém.

Validando Clientes
Há muitas maneiras de validar clientes que tentam se conectar ao servidor WebSocket. Devido a restrições
no navegador para conexão via WebSocket, não há capacidade de passar nenhum cabeçalho HTTP
personalizado durante o handshake. Portanto, os dois métodos mais comuns de implementação de
autenticação são usar o cabeçalho Basic e usar autenticação baseada em formulário com um cookie
definido. Este exemplo emprega o último método e usa um formulário simples de nome de usuário/senha,
configurando e lendo o cookie em seu WebSocket
servidor.

Configurando Dependências e Inits Como a

solução usa chaves compartilhadas por meio de um cookie, você precisará implementar algumas
dependências. As bibliotecas que você usará estão listadas aqui com os comandos npm :

% npm install ws
% npm install express %
npm install body-parser %
npm install cookie

Você provavelmente tem a biblioteca ws instalada em seu ambiente dos exemplos anteriores.
Você usará express e plug-ins para analisar dados de formulários e cookies: body-parser e cookie. A
dependência restante é fs, que você usará para ler seus arquivos de certificado TLS.

De volta ao código do seu servidor – a primeira coisa a fazer é configurar suas importações com require:

88 | Capítulo 6: Segurança WebSocket


Machine Translated by Google

var fs = require('fs'); var https


= requer('https'); var cookie =
require('cookie'); var bodyParser =
require('body-parser'); var expresso = require('expresso');
var WebSocket = require('ws');

Agora que você tem todas as dependências necessárias instaladas, configure o certificado
autoassinado e inicialize o Express e o servidor HTTPS que o suporta. Você pode usar o mesmo
certificado autoassinado configurado anteriormente neste capítulo:

var WebSocketServer = WebSocket.Server;

var credenciais = { chave:


fs.readFileSync('server.key', 'utf8'), certificado:
fs.readFileSync('server.crt', 'utf8')};

var aplicativo = expresso();

app.use(bodyParser.json()); // para analisar application/ json


app.use(bodyParser.urlencoded({ estendido: true }));

var httpsServer = https.createServer(credenciais, aplicativo);


httpsServer.listen(8443);

Ouvindo solicitações da Web Para

que sua autenticação baseada em formulário funcione, você estará servindo uma página de login
e uma página segura, o que exigirá que um cookie chamado credenciais seja encontrado e
contenha a chave adequada. Para resumir, você usará a combinação de nome de usuário/senha
de teste/teste e usará uma chave predefinida que nunca muda. No seu próprio código, entretanto,
esses dados devem ser salvos em uma fonte de dados de sua escolha, que também pode ser
recuperada pelo servidor WebSocket. Os métodos stub serão usados para mostrar onde você
inseriria o código de recuperação e armazenamento em qualquer fonte de dados que você decidir
usar em seu próprio aplicativo.

A seguir está o HTML do exemplo de login, que você servirá em seu servidor expresso . Salve
isso no diretório do seu projeto e nomeie-o como login.html:
<html>
<head>
<title>Login</title> </head>
<body>

<h1>Login</h1> <form
method="POST" action="/login" name="login">
Nome de usuário: <input type="text" name="nome de usuário" />
Senha: <input type="password" name="password" /> <input
type="submit" value="Login" /> </form>

Validando Clientes | 89
Machine Translated by Google

</body>
</html>

Você ouvirá solicitações GET e POST na URL /login e uma solicitação GET para /
secured, que verifica o cookie para garantir a existência e redireciona se não for
encontrado:

app.get('/login', function (req, res) { fs.readFile('./


login.html', function(err, html) { if(err) { throw err;

} res.writeHeader(200, {"Content-Type": "text/html"});


res.write(html);
reenviar();
});
});

app.post("/login", function(req, res) { if(req.body !


== 'indefinido') {
chave = validarLogin(req.body['nomedeusuário'], req.body['senha']); if(chave)

{ res.cookie('credenciais', chave);
res.redirect('/seguro'); retornar;

} res.sendStatus(401);
});

var validarLogin = function(nome de usuário, senha) { if(nome


de usuário == 'teste' && senha == 'teste') {
retornar '591a86e4-5d9d-4bc6-8b3e-6447cd671190'; }
else
{ retornar nulo;
}
}

app.get('/seguro', function(req, res) {


cookies = cookie.parse(req.headers['cookie']); if(!
cookies.hasOwnProperty('credenciais') &&
cookies['credenciais'] !== '591a86e4-5d9d-4bc6-8b3e-6447cd671190') { res.redirect('/
login'); } else { fs.readFile('./

secured.html', function(err, html) { if(err) { throw err;

} res.writeHeader(200, {"Content-Type": "text/html"});


res.write(html);

90 | Capítulo 6: Segurança WebSocket


Machine Translated by Google

reenviar();
});
}
});

Como você pode ver, você eliminou um método activateLogin , que nesta implementação simples
apenas verifica se o nome de usuário e a senha são de teste.
Após uma validação bem-sucedida, ele devolve a chave. Em uma implementação de produção, eu
optaria por armazenar essa chave em um armazenamento de dados, que pode então ser recuperado
e validado com o servidor WebSocket. Estamos trapaceando um pouco para não adicionar nenhuma
dependência desnecessária a este exemplo. O HTML fornecido pelo endpoint /secured da seguinte
forma:

<html>
<head>
<title>Exemplo de autenticação WebSocket </
title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> < tipo de script
= "texto/javascript">

$(função() { var
ws = new WebSocket("wss://localhost:8443");

ws.onopen = function(e)
{ console.log('Conexão com o servidor aberta');

} });
</script>
</head>
<body>
Olá, WebSocket. </
body> </
html>

Agora você tem todos os endpoints da web sendo servidos ao usuário e um cliente WebSocket que
está se conectando com segurança por TLS à porta 8443.

Servidor WebSocket
Você administrou o espectro da web e tem um cliente WebSocket totalmente carregado e pronto
para enviar mensagens para seu endpoint WebSocket. No mesmo arquivo de origem, você incluirá
código para ativar um servidor WebSocket seguro e usar verifyClient para verificar seu cookie de
credenciais . A primeira coisa que você faz é garantir que está falando em um canal seguro e, caso
contrário, retornar falso para o retorno de chamada que falha na conexão. Em seguida, verifique o
cabeçalho do cookie e chame a função checkAuth , que no código de produção procuraria a chave
em uma fonte de dados e validaria se o cliente realmente pode acessar este serviço. Se tudo correr
bem, retorne true ao retorno de chamada e permita que a conexão continue:

Validando Clientes | 91
Machine Translated by Google

var checkAuth = function(key) { return


key === '591a86e4-5d9d-4bc6-8b3e-6447cd671190';
}

var wss = new


WebSocketServer({ servidor:
httpsServer, verifyClient: function(info, callback)
{ if(info.secure !== true)
{ callback(false);
return;

} var parsed_cookie = cookie.parse(info.req.headers['cookie']);

if('credenciais' em parsed_cookie)
{ if(checkAuth(parsed_cookie['credenciais']))
{ callback(true);
retornar;
}
}
retorno de chamada(falso);
}

});
wss.on('conexão', function( wsConnect ) {
wsConnect.on('mensagem', function(mensagem) {
console.log(mensagem);

}); });

Como você pode ver, esta é uma solução ponta a ponta para validar se uma conexão WebSocket
está autorizada a continuar. Você pode adicionar outras verificações conforme necessário,
dependendo do aplicativo que está sendo criado. Apenas lembre-se de que o navegador do cliente
não pode definir nenhum cabeçalho, portanto, os cookies e o cabeçalho Básico são tudo o que
você tem. Isso deve fornecer a estrutura para que você possa incorporar isso em seus próprios
aplicativos de maneira segura, longe de olhares indiscretos.

Resumo
Este capítulo analisou vários vetores de ataque e formas de proteger seu aplicativo WebSocket. A
principal conclusão deve ser presumir que o cliente não é um navegador e, portanto, não confie
nele.

92 | Capítulo 6: Segurança WebSocket


Machine Translated by Google

Três coisas para lembrar:

• Sempre use TLS. •

O código do servidor deve sempre verificar o cabeçalho

Origin . • Verifique a solicitação usando um token aleatório semelhante a um token CSRF para Ajax
solicitações de.

É ainda mais importante usar os itens discutidos neste capítulo para que a possibilidade de alguém
sequestrar a conexão WebSocket para outros fins nefastos seja fortemente minimizada. No próximo
capítulo, revisaremos diversas maneiras de depurar o WebSocket e medir ativamente os benefícios
de desempenho em relação a uma solicitação regular baseada em Ajax.

Resumo | 93
Machine Translated by Google
Machine Translated by Google

CAPÍTULO 7

Depuração e ferramentas

Os capítulos anteriores se aprofundaram na construção de soluções para usar o Web-Socket


em seus aplicativos. Durante o processo de integração de qualquer tecnologia em um projeto
novo ou existente, talvez a ferramenta mais vital seja aprender como depurar quando as coisas
não saem como planejado originalmente.

Neste capítulo, você explorará diversas áreas do ciclo de vida do WebSocket e revisará
ferramentas que podem ajudar em sua jornada pelo cenário do WebSocket. Vamos dar uma
olhada em um dos exemplos anteriores e dar uma olhada no que está sendo divulgado e como
você pode usar as ferramentas para ver o que está acontecendo nos bastidores.

Um ciclo de vida típico do WebSocket consiste em três áreas principais: o handshake de


abertura, o envio e recebimento de frames e o handshake de fechamento. Cada um pode
apresentar seus próprios desafios. Descrever todos eles aqui seria impossível, mas mostrarei
alguns métodos de investigação caso surjam desafios durante a depuração.

O aperto de mão
Os dados esperados que o servidor recebe de um cliente válido devem incluir vários
cabeçalhos HTTP como Host, Connection, Upgrade, Sec-WebSocket-Key, Sec-WebSocket-
Version e outros que são opcionais para WebSocket. Proxies e ferramentas de segurança
em algumas redes corporativas podem modificar os cabeçalhos antes de serem transmitidos
ao servidor e provavelmente causar falha no handshake. Para fins de teste você pode usar
o OWASP ZAP. O ZAP foi projetado para ajudar os testadores de penetração a encontrar
vulnerabilidades em aplicações web, e você pode usá-lo para interceptar o handshake
usando sua funcionalidade break e remover alguns dos cabeçalhos importantes antes que
o servidor os veja.

Ao longo deste capítulo você usará o exemplo de código de identidade do Capítulo 3. O exemplo
completo para servidor e cliente é reproduzido nas seções a seguir.

95
Machine Translated by Google

O servidor
Aqui está o código completo para a parte do servidor do aplicativo de bate-papo de identidade:

var WebSocket = require('ws'); var


WebSocketServer = WebSocket.Server,
wss = novo WebSocketServer({porta: 8181});
var uuid = require('node-uuid');

var clientes = [];

function wsSend(tipo, client_uuid, apelido, mensagem) {


for(var i=0; i<clientes.length; i++) { var
clientSocket = clientes[i].ws;
if(clientSocket.readyState === WebSocket.OPEN)
{ clientSocket.send(JSON.stringify({ "type":
type, "id":
client_uuid,
"nickname": apelido,
"message":
mensagem }));
}
}
}

var índicecliente = 1;

wss.on('erro', function(e)
{ console.log(" hora do erro"); });

wss.on('conexão', function(ws) { var


client_uuid = uuid.v4(); var apelido
= "AnonymousUser"+clientIndex; clientIndex+=1;
clientes.push({"id":
client_uuid, "ws": ws, "apelido": apelido}); console.log('cliente [%s] conectado',
client_uuid);

var connect_message = apelido + "conectou";


wsSend("notificação", client_uuid, apelido, connect_message);

ws.on('mensagem', function(mensagem) {
if(message.indexOf('/nick') === 0) {
var apelido_array = mensagem.split(' ');
if(nickname_array.length >= 2) { var
old_nickname = apelido; apelido =
apelido_array[1]; var
"
apelido_message = "Cliente" + apelido; + apelido_antigo + " mudou para

wsSend("nick_update", client_uuid, apelido, apelido_mensagem);

} } senão {

96 | Capítulo 7: Depuração e Ferramentas


Machine Translated by Google

wsSend("mensagem", client_uuid, apelido, mensagem);

} });

ws.on('erro', função(e) {
console.log("erro acontece"); });

var closeSocket = function(customMessage) { for(var


i=0; i<clients.length; i++) { if(clientes[i].id ==
client_uuid) {
var mensagem_desconectada;
if(customMessage)
{ desconectar_message = customMessage; }
else
{ mensagem_desconectada = apelido + " desconectou";
}
wsSend("notificação", client_uuid, apelido, mensagem de desconexão);
clientes.splice(i, 1);
}
}

} ws.on('close', function()
{ console.log("fechando soquete ");
closeSocket();
});

process.on('SIGINT', function()
{ console.log("Fechando coisas");
closeSocket('Servidor desconectado');
process.exit();

}); });

O cliente
Aqui está o código completo para a parte do cliente do aplicativo de chat de identidade:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demonstração de bate-papo WebSocket bidirecional </
title> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta
name="viewport " content="largura=largura do dispositivo, escala inicial=1"> <link
rel="stylesheet" href="http://bit.ly/cdn-bootstrap-css"> <link rel="stylesheet"
href ="http://bit.ly/cdn-bootstrap-theme"> <script src="http://bit.ly/cdn-bootstrap-
jq"> </script>

<script>
var ws = new WebSocket("ws://localhost:8181");

O aperto de mão | 97
Machine Translated by Google

var apelido = "";


ws.onopen = function(e)
{ console.log('Conexão com o servidor aberta');

} functionappendLog (tipo, apelido, mensagem) {


var mensagens = document.getElementById('mensagens'); var
mensagemElem = document.createElement("li"); var
prefácio_label;
if(type==='notificação')
{ preface_label = "<span class=\"label label-info\">*"; } else
if(type==='nick_update') {
preface_label = "<span class=\"label label-warning\">*"; } else { preface_label
= "<span
class=\"label label-success\">" + apelido + "";

} var message_text = "<h2>" + preface_label + "&nbsp;&nbsp;" + mensagem + "</


h2>";
mensagemElem.innerHTML = mensagem_texto;
mensagens.appendChild(messageElem);
}

ws.onmessage = função (e) {


var dados = JSON.parse(e.data);
apelido = dados.apelido;
anexarLog(dados.tipo, dados.apelido, dados.mensagem);
console.log("ID: [%s] = %s", dados.id, dados.message); }

ws.onclose = function(e)
{ appendLog("Conexão fechada");
console.log("Conexão fechada");

} ws.onerror = function(e)
{ appendLog("Erro");
console.log(" Erro de conexão");

} function enviarMensagem() {
var mensagemField = document.getElementById('mensagem');
if(ws.readyState === WebSocket.OPEN)
{ ws.send(messageField.value);

} mensagemField.value = '';
mensagemField.focus();

} função desconectar() {
close();

} </script>

</head>
<body lang="en">
<div class="vertical-center">

98 | Capítulo 7: Depuração e Ferramentas


Machine Translated by Google

<div class="container"> <ul


id="messages" class="list-unstyled">

</ul>
<hr />
<form role="form" id="chat_form" onsubmit="sendMessage(); return false;">
<div class="form-group">
<input class="form-control" type="text" id="message" name="message"
placeholder="Digite o texto para ecoar aqui" value="" autofocus/ > </div>
<button
type="button" id="send" class="btn btn-primary"
onclick="sendMessage();">Enviar mensagem</button>
</form>
</div>
</ div>
<script src="http://bit.ly/cdn-bootstrap-minjs"></script> </body> </
html>

Baixe e configure o ZAP A melhor maneira

de seguir com o teste de “proxies ruins” é baixar o ZAP e execute-o para sua plataforma específica. O
ZAP pode atuar como um proxy enquanto você navega pelo seu aplicativo, então você precisará modificar
as configurações de rede do seu navegador. Com tantas iterações possíveis, é melhor apenas acessar a
documentação do ZAP, que fala sobre vários navegadores e como configurar o proxy. O proxy por padrão
é executado na porta localhost 8080 e pode ser alterado acessando Opções em Ferramentas ÿ Opções
ÿ Proxy local.

No cliente ZAP, selecione “Alternar pausa em todas as solicitações” para poder aprovar cada solicitação
antes que ela seja enviada. É aqui que você modificará seu handshake e removerá alguns cabeçalhos
vitais e necessários. Ao visitar o cliente local abrindo o arquivo client.html, ele tentará fazer diversas
conexões HTTP. Algumas delas serão para dependências externas no bootstrap da UI, e haverá uma
que irá para http:// localhost:8181 solicitando uma conexão de atualização para WebSocket.

Haverá botões Próximo no cabeçalho da IU, permitindo que você percorra cada solicitação. Quando você
chegar à solicitação WebSocket, pare e vamos fazer algumas alterações.

Aqui está uma solicitação semelhante ao que você deve ver no ZAP:

OBTER http://localhost:8181/ HTTP/1.1


Host: localhost:8181
Conexão: Pragma de
atualização: sem
cache Controle de cache:
sem cache Atualização:
websocket
Origem: null Sec-WebSocket-
Version: 13 User-Agent: Mozilla/5.0 (Macintosh ; ...

O aperto de mão | 99
Machine Translated by Google

Accept-Encoding: sdch
Accept-Language: en-US,en;q=0.8,de;q=0.6 Sec-
WebSocket-Key: BRUZ6wGtxKWln5gToX4MSg== Sec-
WebSocket-Extensions: permessage-deflate; client_max_window_bits

Na área de texto, remova todos os cabeçalhos específicos do WebSocket, como um proxy ou IDS inválido
poderia fazer:

GET http://localhost:8181/ HTTP/1.1 Host:


localhost:8181 Conexão:
Upgrade Pragma: no-
cache Cache-Control:
no-cache Origin: null User-
Agent: Mozilla/
5.0 (Macintosh; Intel Mac OS X 10_10_3 ) AppleWebKit/537.36...
Codificação de aceitação:
sdch Linguagem de aceitação: en-US,en;q=0,8,de;q=0,6

Após permitir que a solicitação continue sem os cabeçalhos adequados, você verá na resposta do ZAP um
código de erro HTTP 426. Este código HTTP indica que uma atualização é necessária e não foi fornecida.
Isso pode ser uma ocorrência comum ao interagir com proxies ruins, que discutiremos como resolver na
seção “WebSocket seguro para o resgate” na página 102.

A Figura 7-1 mostra o handshake do WebSocket dentro do aplicativo OWASP.

Figura 7-1. Proxy de ataque OWASP Zed quebrando o handshake do WebSocket

100 | Capítulo 7: Depuração e Ferramentas


Machine Translated by Google

Vejamos as ferramentas para desenvolvedores do Chrome; eles deveriam nos contar uma história semelhante. Talvez
seja necessário atualizar a solicitação e prosseguir novamente com o ZAP depois de navegar até a guia Rede na
seção Ferramentas para desenvolvedores do Chrome. Pode ser necessário clicar no botão Filtrar e especificar
WebSockets. Após o reenvio, você também verá o código de resposta HTTP 426 sendo transmitido de volta ao
navegador.

A Figura 7-2 mostra o resultado da falta de alguns cabeçalhos nas Ferramentas do desenvolvedor do Chrome.

Figura 7-2. Aperto de mão nas ferramentas do desenvolvedor do Chrome

O que aconteceria se você não removesse todos os cabeçalhos, apenas algo que pode ser importante, como o Sec-
WebSocket-Version? Quando a solicitação chegar, remova o cabeçalho que você verá para Sec-WebSocket-Version:

OBTER http://localhost:8181/ HTTP/1.1


Host: localhost:8181
Conexão: Pragma de
atualização: sem
cache Controle de cache:
sem cache Atualização:
websocket
Origem: null User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36...
Accept-Encoding: sdch
Accept-Language: en-US,en;q=0.8,de;q=0.6 Sec-
WebSocket-Key: BRUZ6wGtxKWln5gToX4MSg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

O aperto de mão | 101


Machine Translated by Google

Depois de enviar esta solicitação de volta ao servidor, o que provavelmente retornará será uma
solicitação HTTP 400 incorreta. Estão faltando algumas informações vitais (Sec-WebSocket-
Version) e isso não permitirá que você continue. Como você pode garantir que há mais chances
de que suas mensagens sejam recebidas e enviadas corretamente?

WebSocket seguro para o resgate


Graças a algumas ferramentas interessantes, você pode ver o que está acontecendo com sua
conexão e por que as coisas estão um pouco instáveis. Como você contorna coisas como proxies
ou ferramentas IDS que atrapalham seus preciosos cabeçalhos? WebSocket Seguro é a resposta.
Conforme discutimos no Capítulo 6, a melhor maneira de garantir que sua comunicação chegará
ao destino pretendido é sempre usar wss://. Se precisar de ajuda para configurá-lo, consulte o
Capítulo 6 para obter instruções. Em geral, usar o canal WebSocket seguro pode aliviar os
problemas descritos na seção anterior.

Validando o aperto de mão


A maioria das bibliotecas e todos os navegadores com suporte WebSocket RFC 6455
implementarão o processo simples de handshake sem falhas. Como discutiremos no Capítulo
8, o Sec-WebSocket-Key é um nonce aleatório codificado em base64 e enviado no handshake
inicial do cliente. Para validar se seu servidor está enviando de volta o valor correto ao cliente,
você pode pegar o código de exemplo do Capítulo 8 e escrever um script simples que aceite uma
Sec-WebSocket-Key e emita uma resposta adequada:

var criptografia = require('cripto');

var SPEC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

var webSocketAccept = function(secWebsocketKey) { var


sha1 = crypto.createHash("sha1");
sha1.update(secWebsocketKey + SPEC_GUID, "ascii");
retornar sha1.digest("base64");
}

if(process.argv.length <= 2) {
console.log("Você deve fornecer um Sec-WebSocket-Key como único parâmetro");
processo.exit(1);
}

var webSocketKey = process.argv[2];

webSocketAccept = webSocketAccept(webSocketKey);
console.log("Sec-WebSocket-Accept:", webSocketAccept);

102 | Capítulo 7: Depuração e Ferramentas


Machine Translated by Google

Se você estiver vendo outros valores ao usar o Wireshark ou as ferramentas de desenvolvedor do


Chrome (valores que obviamente seriam rejeitados pelo cliente), execute este script primeiro na chave e
depois veja como corrigir o que pode estar errado com seu servidor. Isso poderia indicar que algo ao
longo do caminho da comunicação está se inserindo na comunicação e que o protocolo está fazendo a
coisa certa ao rejeitá-lo.

Inspecionando Quadros
Você terá, em mais de uma ocasião, a tarefa de descobrir por que um cliente está recebendo dados
inesperados vindos de seu servidor. A primeira reação a isso pode ser adicionar algum registro de
depuração ao seu aplicativo e pedir ao cliente para fazer outra tentativa. No entanto, se o seu código
estiver em produção, isso seria desaconselhável porque poderia afetar outros usuários e afetar o
desempenho ou a disponibilidade. Outra opção é usar um sniffer de rede para observar a comunicação
do servidor com o cliente afetado. Vamos usar nosso exemplo de chat existente para ver o que está
acontecendo.

Cargas mascaradas A

melhor maneira de ver cada quadro passando pelo fio é usar nossa confiável ferramenta Wire- shark. É
isso mesmo, crianças, o Wireshark não serve apenas para farejar conexões de rede em um café! A
versátil ferramenta de rede funciona bem em todas as plataformas e permite filtrar e inspecionar o
handshake junto com cada quadro individual enviado pela rede. A partir da versão 1.9 ele roda sem
precisar da dependência do X11 também, o que é definitivamente um bônus.

Começar a usar o Wireshark é bastante simples. Depois de baixar e instalar com sucesso a ferramenta
para sua plataforma específica, você será saudado com a tela principal, que lista todas as interfaces de
rede que o Wireshark está pronto para ouvir.

A Figura 7-3 mostra a tela inicial da ferramenta Wireshark.

Clique duas vezes na interface que você navegará para esses testes e o Wireshark começará a mostrar
os pacotes capturados na próxima tela (veja a Figura 7-4).
Ele mostra cada pacote capturado em uma tabela com colunas classificáveis e uma barra de filtro na
parte superior para facilitar a visualização exata do que você procura no enorme fluxo de dados que flui
para frente e para trás.

Você pode optar por seguir um fluxo TCP, UDP ou SSL para ver a comunicação bidirecional sendo
enviada ao longo da rede. Você observará o handshake na Figura 7.5 e verá uma solicitação do cliente
ao servidor com a carga após o cabeçalho HTTP.

Inspecionando Quadros | 103


Machine Translated by Google

Figura 7-3. Tela principal do Wireshark

Figura 7-4. Tela de captura do Wireshark

104 | Capítulo 7: Depuração e Ferramentas


Machine Translated by Google

Figura 7-5. Cliente de quadro Wireshark para servidor

Discutimos brevemente o mascaramento de quadros no Capítulo 6, e se você estiver olhando para a carga
pensando que ela não se parece com o JSON que você esperava, você está correto. Para serem considerados
válidos, todos os clientes devem mascarar a carga útil com a chave de mascaramento de 32 bits passada dentro
do quadro antes de enviar qualquer mensagem a um servidor. Você pode estar dizendo a si mesmo: “Isso é uma
merda; como devo depurar efetivamente o que está acontecendo com esse cliente com essas visões antiquadas
em meus dados?” Não tenha medo, as versões mais recentes do Wireshark suportam o desmascaramento
automático de solicitações WebSocket para o servidor.

A Figura 7-6 mostra a chave de mascaramento capturada com o Wireshark.

Veja como visualizá-lo no Wireshark:

1. Encontre um quadro para selecionar que tenha [MASK] na coluna Informações.

2. Certifique-se de que os Detalhes do pacote estejam ativados e visíveis.

3. Expanda a seção inferior denominada “WebSocket”.

4. Expanda a última seção chamada “Unmask Payload” e observe sua carga sem mascarar.

Inspecionando Quadros | 105


Machine Translated by Google

Figura 7-6. Quadro mascarado WebSocket mostrado no Wireshark

A Figura 7-7 mostra um exemplo da UI do Wireshark e um exemplo de carga útil desmascarada junto
com a carga mascarada logo acima dela, caso você precise. Isso é incrivelmente poderoso para
depurar as interações com vários clientes e o código do servidor.

Agora você pode ver a aparência da carga útil desmascarada para uma mensagem específica passada
do cliente para o servidor sem recorrer à depuração para stderr/stdout ou prejudicar o desempenho
ou a disponibilidade de seu aplicativo. Ao analisar as opções, as Ferramentas para desenvolvedores
do Chrome também são uma companhia valiosa para nossos esforços, embora exija que você seja o
cliente e possa replicar os erros do seu ambiente.
Uma das razões pelas quais considero o Wireshark tão poderoso nesse aspecto é a capacidade de
assistir ao stream sem modificar ou interromper outros clientes no processo.

106 | Capítulo 7: Depuração e Ferramentas


Machine Translated by Google

Figura 7-7. Carga útil desmascarada do WebSocket mostrada no Wireshark

Embora seja interessante visualizar cargas específicas desmascaradas, outras vezes é mais apropriado
ver toda a conversa de um cliente específico. Para isso o Wireshark também é indispensável. Enquanto
ainda captura sua comunicação WebSocket, você pode clicar com o botão direito do mouse ou clicar com
a tecla Command pressionada em qualquer WebSocket ou no handshake HTTP inicial na comunicação e
escolher Seguir ÿ Seguir fluxo TCP.
No momento da captura, ele mostrará toda a conversa daquele cliente específico.

A Figura 7-8 mostra o menu contextual necessário para seguir o fluxo TCP.

Ao examinar a conversa mostrada na janela Follow TCP Stream, navegando de volta para a tela principal
de captura, você deverá notar que a captura aparece filtrada. Sempre que você seguir um stream, ele
selecionará esse stream e filtrará qualquer outro para que você possa se concentrar. A próxima seção
aborda como depurar e observar quadros próximos usando o Wireshark.

Inspecionando Quadros | 107


Machine Translated by Google

Figura 7-8. Wireshark segue fluxo TCP

Fechando conexão
A última coisa que você verá em uma conversa WebSocket é o quadro de fechamento. Digamos
que você adicione um botão à sua UI, permitindo que o cliente envie um quadro fechado ao servidor
da seguinte forma:

<button type="button" onclick="disconnect();">Desconectar</button>

Você poderia então usar algumas das coisas que aprendeu sobre observar quadros no Wire-Shark.
Adequado à nossa tarefa seria ver o quadro mascarado sendo enviado pelo cliente e observar a
mensagem muito pequena com a carga útil vazia para uma solicitação de fechamento. Se você
visualizar o quadro e abrir a seção WebSocket no Wireshark, deverá ver algo semelhante ao seguinte:

WebSocket
1... .... = Fin:
.000 .... Verdadeiro =
Reservado: 0x00 .... 1000 = Opcode: Conexão
Fechada (8) =1... ....
Máscara: True
.000 0000 = Comprimento da carga útil:
0 Chave de máscara: 4021df19

Os códigos de status registrados para um fechamento por motivos específicos de RFC são definidos
no Capítulo 8. O cabeçalho anterior indica que ocorreu um fechamento normal e nenhuma mensagem
foi passada. Se você quiser passar seu próprio código de status no fechamento e/ou mensagem

108 | Capítulo 7: Depuração e Ferramentas


Machine Translated by Google

sábio, você pode fazer isso usando a API JavaScript conforme discutimos no Capítulo 2 com o seguinte:

ws.close(1000, 'Terminou normalmente');

A carga útil seria idêntica à forma como as mensagens são recebidas normalmente, incluindo o mascaramento e
o envio junto com os cabeçalhos no quadro. O opcode adornaria o código passado na chamada JavaScript, a
máscara seria definida e a chave de mascaramento seria usada para mascarar a mensagem enviada ao cliente.
Essa é a comunicação final que você receberá com uma conversa WebSocket, e você aprendeu como observar
tudo e modificar as coisas conforme necessário para testar diferentes cenários.

Resumo
Este capítulo abordou o handshake de abertura, os frames e o handshake de fechamento e apresentou
ferramentas que permitem observar a interação entre cliente e servidor. A seguir está uma recapitulação de
alguns dos problemas que você pode identificar usando essas ferramentas.

Se você receber um código diferente de HTTP 200 durante o handshake de abertura:

• Um proxy ou IDS inválido provavelmente está envolvido e removendo cabeçalhos. O código pode indicar
que você não usou o WebSocket Secure, que é preferível ao uso da conexão normal não TLS.

• O código também pode indicar que algo deu errado com o Sec-WebSocket-Key ou o Sec-WebSocket-
Accept enviado em resposta. Você pode observar a solicitação e a resposta usando OWASP, Chrome
Developer Tools ou Wireshark e executar os valores no script que você escreveu em “Validando o
handshake” na página 102.

Se um cliente estiver reclamando de erros, mas você não tiver visibilidade devido ao mascaramento:

• Use o Wireshark e siga as instruções que detalham como ver as respostas do servidor e as solicitações

feitas pelo cliente para que você possa entender completamente onde as coisas estão dando errado.

E finalmente, se estiver ocorrendo um fechamento:

• Use qualquer uma das ferramentas listadas para ver o que está sendo enviado pelo cliente ou respondido
pelo servidor, ver o código e/ou mensagem e tratar adequadamente.

Conseguimos identificar algumas possíveis armadilhas ao longo do caminho e como elas poderiam ser
identificadas usando ferramentas para que não acabem em uma busca de dias durante o processo de depuração.
As ferramentas devem servir como companheiras valiosas durante o desenvolvimento

Resumo | 109
Machine Translated by Google

processo e durante a depuração, quando o aplicativo entra em produção e você precisa de uma visão
melhor do que está acontecendo.

Você pode notar que o método de depuração via navegador se concentra exclusivamente nas
Ferramentas do desenvolvedor do Chrome. O Safari não oferece nenhuma maneira discernível de
depurar frames WebSocket. O Firefox mostrará o handshake de abertura e todos os cabeçalhos
associados à conexão, mas nenhuma inspeção do quadro estará disponível. De acordo com o bug
885508 da Mozilla, ele ainda está aberto e parece que nenhuma implementação está disponível nas
maravilhosas ferramentas de desenvolvedor. Você tem muitas ferramentas à sua disposição; O Chrome
junto com o Wireshark e o OWASP ZAP podem fornecer a introspecção necessária para descobrir o
que está acontecendo quando as coisas vão mal.

O capítulo final apresenta uma visão mais aprofundada do próprio protocolo WebSocket.

110 | Capítulo 7: Depuração e Ferramentas


Machine Translated by Google

CAPÍTULO 8

Protocolo WebSocket

Nenhuma discussão sobre protocolos, especialmente aqueles que são iniciados através de
uma chamada HTTP, estaria completa sem falar um pouco sobre a história do HTTP. O
início do WebSocket surgiu devido à enorme popularidade do Ajax e das atualizações em
tempo real. Com HTTP, um protocolo em que um cliente solicita um recurso e o servidor
responde com o recurso ou possivelmente com um código de erro se algo der errado. Essa
natureza unidirecional tem sido contornada usando tecnologias como Comet e pesquisas
longas, mas tem um custo de recursos de computação no lado do servidor. O Web-Socket
pretende ser uma das técnicas que resolve este problema e permite aos desenvolvedores
web implementar comunicação bidirecional sobre a mesma solicitação HTTP.

HTTP 0.9 – Nasce a Web


O nascimento da World Wide Web deu origem às primeiras versões do Hypertext Transfer
Protocol (HTTP). A primeira versão do HTTP foi criada por Tim Berners-Lee em conjunto
com a Hypertext Markup Language (HTML). HTTP 0.9 era incrivelmente simples. Um cliente
solicita conteúdo através do método GET :

OBTER /index.html

A simplicidade do HTTP 0.9 significava que você só poderia solicitar duas coisas: texto
simples ou HTML. Esta versão inicial do HTTP não tinha cabeçalhos, portanto não havia
capacidade de servir nenhuma mídia. Em essência, como cliente, você solicitou um recurso
do servidor usando TCP e, após o servidor terminar de enviá-lo, a conexão foi encerrada.

HTTP 1.0 e 1.1


A simplicidade do 0.9 não duraria muito. Com a próxima versão do HTTP, a complexidade
envolvida em um par solicitação/resposta HTTP cresceu. As versões posteriores do HTTP
adicionaram a capacidade de enviar cabeçalhos HTTP a cada solicitação. Com isso crescendo

111
Machine Translated by Google

número de cabeçalhos para suporte, itens como solicitações POST (formulário), tipos de mídia,
cache e autenticação foram adicionados no HTTP 1.0. Na versão mais recente, servidores
multihomed com cabeçalho Host, negociação de conteúdo, conexões persistentes e respostas
fragmentadas foram adicionados e são usados atualmente em servidores de produção. A questão
de tudo isso é que, à medida que o HTTP cresceu em complexidade, o tamanho dos cabeçalhos também cresceu.

De acordo com um white paper do Google falando sobre SPDY, o cabeçalho HTTP médio agora
tem 800 bytes e geralmente chega a 2 KB. A compressão e outras técnicas estão prontamente
disponíveis para simplificar esta situação. O seguinte mostra um cabeçalho HTTP típico do popular
mecanismo de pesquisa Google:

% curl -I http:// www.google.com HTTP/


1.1 200 OK Data:
Quarta, 20 de maio de 2015 22:50:00 GMT
Expira: -1
Controle de cache: privado, idade
máxima = 0 Tipo de conteúdo: texto/html;
charset=ISO-8859-1 Set-Cookie: PREF=ID=68769f4bb498a69f:FF=0:T...
Set-Cookie: NID=67=D26hM_BKWVnngC-7_1-XGmBR...
P3P: CP="Esta não é uma política P3P! Consulte http..."
Servidor: gws
X-XSS-Protection: 1; mode=block X-
Frame-Options: SAMEORIGIN
Protocolo alternativo: 80:quic,p=0
Transfer-Encoding: chunked
Accept-Ranges: nenhum
Vary: Accept-Encoding

Tomei a liberdade de remover o conteúdo do cabeçalho do cookie e deixei quantos caracteres ele
ocupava no cabeçalho. Ao todo, o cabeçalho tinha 850 caracteres, ou pouco menos de 1 KB.
Quando você deseja enviar dados entre servidor e cliente e vice-versa, ter que enviar um cabeçalho
de 1 KB além disso é desnecessário e um desperdício. Como você verá, após o handshake inicial,
um cabeçalho de quadro WebSocket é minúsculo em comparação e semelhante à abertura de uma
conexão TCP sobre HTTP.

As seções a seguir contêm exemplos de código que mostram como criar partes do protocolo do
servidor. Juntos, você pode construir sua própria implementação de um servidor WebSocket
compatível com RFC.

Aperto de mão aberto do WebSocket

Um dos muitos benefícios do protocolo WebSocket é que ele inicia sua conexão com o servidor
como uma simples solicitação HTTP. Navegadores e clientes que suportam WebSocket enviam ao
servidor uma solicitação com cabeçalhos específicos que solicitam uma Conexão: Atualize para
usar WebSocket. O cabeçalho Connection: Upgrade foi introduzido no HTTP/1.1 para permitir que
o cliente notifique o servidor sobre meios alternativos de comunicação. É priÿ

112 | Capítulo 8: Protocolo WebSocket


Machine Translated by Google

amplamente usado neste ponto como um meio de atualizar o HTTP para usar o WebSocket e pode ser
usado para atualizar para o HTTP/2.

De acordo com as especificações do WebSocket, a única indicação de que uma conexão com o servidor
Web-Socket foi aceita é o campo de cabeçalho Sec-WebSocket-Accept. O valor é um hash de um GUID
predefinido e do cabeçalho HTTP do cliente Sec-WebSocket-Key.

Da RFC 6455

O campo de cabeçalho Sec-WebSocket-Accept indica se o servidor está


disposto a aceitar a conexão. Se presente, este campo de cabeçalho deve
incluir um hash do nonce do cliente enviado em Sec-WebSocket-Key junto
com um GUID predefinido. Qualquer outro valor não deve ser interpretado
como uma aceitação da conexão por parte do
servidor.

Sec-WebSocket-Key e Sec-WebSocket-Accept A primeira coisa que

a especificação pede no lado do cliente para gerar o Sec-WebSocket-Key é um valor nonce ou aleatório
único. Se você estiver usando um navegador compatível com WebSocket, a geração do Sec-WebSocket-
Key será feita automaticamente usando a API JavaScript. Uma das restrições de segurança é que um
XMLHttpRequest não terá permissão para modificar esse cabeçalho. Como discutimos no Capítulo 6,
isso garante que, mesmo que o site esteja comprometido, você pode confiar que o navegador não
permitirá a modificação de nenhum cabeçalho.

Gerando a Sec-WebSocket-Key O

código a seguir assumirá a execução em Node.js e possivelmente o uso do WebSocket para se comunicar
com outro serviço que atua como o servidor WebSocket. Você usará um GUID gerado usando o módulo
node-uuid , que deve ser aleatório o suficiente para suas necessidades.

A única coisa que você precisa fazer neste momento é base64 seu nonce e incluí-lo nos cabeçalhos
HTTP da sua solicitação de conexão WebSocket. Você usará o módulo node-uuid necessário
anteriormente para criar sua string aleatória:

var uuid = require('node-uuid');

var webSocketKey = function() { var


wsUUID = uuid.v1();
retornar novo Buffer(wsUUID).toString('base64');
}

Aperto de mão aberto do WebSocket | 113


Machine Translated by Google

Respondendo com o Sec-WebSocket-Accept

No lado do servidor, a primeira coisa que você fará é incluir o módulo criptográfico para poder
enviar de volta seu hash SHA1 do valor combinado:
var criptografia = require('cripto');

RFC 6455 define um GUID predefinido, que você definirá como uma constante em seu código:

var SPEC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

Sua próxima tarefa é definir uma função em seu código JavaScript que aceite a Sec-
WebSocket-Key como parâmetro e cria um objeto hash criptográfico SHA1:
var webSocketAccept = function(secWebsocketKey) { var
sha1 = crypto.createHash("sha1");

Por fim, você anexará o Sec-WebSocket-Key junto com o GUID predefinido, passando-o para o seu
objeto hash SHA1. A função de atualização atualizará o conteúdo do hash com seus dados
combinados. Você passa em ascii para identificar a codificação de entrada para a atualização SHA1:

sha1.update(secWebsocketKey + SPEC_GUID, "ascii");


retornar sha1.digest("base64");

A geração do cabeçalho Sec-WebSocket-Accept geralmente é tarefa de uma biblioteca de servidor.


É uma boa ideia entender o funcionamento interno e ter uma maneira de testar se algo der errado.

Cabeçalhos HTTP WebSocket

A conexão WebSocket deve ser uma solicitação HTTP/1.1 GET e incluir os seguintes cabeçalhos:

• Anfitrião

• Atualização: websocket

• Conexão: Atualização

• Chave Sec-WebSocket
• Versão Sec-WebSocket

Se algum deles não estiver incluído nos cabeçalhos HTTP, o servidor deverá responder com um
código de erro HTTP 400 Bad Request. Aqui está um exemplo de uma solicitação HTTP simples
para atualização para WebSocket. A disposição dos cabeçalhos não é tão importante quanto a sua
existência:

114 | Capítulo 8: Protocolo WebSocket


Machine Translated by Google

OBTER ws:// localhost:8181/ HTTP/ 1.1


Origem: http:// localhost:8181
Anfitrião: localhost:8181
Chave Sec-WebSocket: zy6Dy9mSAIM7GJZNf9rI1A==
Atualização: websocket
Conexão: Atualização
Versão Sec-WebSocket: 13

A Tabela 8-1 mostra os possíveis cabeçalhos no handshake de abertura.

Tabela 8-1. Abrindo cabeçalhos de handshake

Cabeçalho Valor obrigatório

Hospedar Sim Campo de cabeçalho contendo a autoridade do servidor.

Sim websocket
Atualizar

Conexão Sim Atualizar

Sec- Sim Campo de cabeçalho com um valor codificado em base64 que, quando decodificado, tem 16 bytes de comprimento.

Chave WebSocket

Sec- Sim 13

WebSocket-
Versão

Não
Origem Opcionalmente, um campo de cabeçalho Origin . Este campo de cabeçalho é enviado por todos os clientes do navegador. A

tentativa de conexão sem este campo de cabeçalho não deve ser interpretada como proveniente de um

cliente do navegador. O envio do domínio de origem na atualização é para que as conexões possam ser

restrito para evitar ataques CSRF semelhantes ao CORS para XMLHttpRequest.

Sec- Sim (servidor) O servidor envia de volta uma confirmação descrita após a tabela e deve ser

WebSocket- presente para que a conexão seja válida.

Aceitar

Sec- Não
Opcionalmente, um campo de cabeçalho Sec-WebSocket-Protocol , com uma lista de valores

WebSocket- indicando quais protocolos o cliente gostaria de falar, ordenados por preferência.

Protocolo

Sec- Não
Opcionalmente, um campo de cabeçalho Sec-WebSocket-Extensions , com uma lista de valores

WebSocket- indicando quais ramais o cliente gostaria de falar. A interpretação disto

Extensões O campo de cabeçalho é discutido na Seção 9.1 da RFC 6455.

Ao receber uma solicitação de atualização válida com todos os campos obrigatórios, o servidor decidirá
no protocolo aceito e quaisquer extensões, e enviar de volta uma resposta HTTP com
código de status 101 junto com o reconhecimento de handshake Sec-WebSocket-Accept .

Aperto de mão aberto do WebSocket | 115


Machine Translated by Google

O código a seguir mostra uma resposta simples do servidor aceitando o Web-


Solicitação de soquete e abertura do canal para comunicação usando WebSocket:

Conexão de protocolos de comutação


HTTP/1.1 101 :
Atualização Sec-WebSocket-Accept:
EDJa7WCAQQzMCYNJM42Syuo9SqQ= Atualização: websocket

A seguir, examinaremos detalhadamente o cabeçalho do quadro WebSocket, no nível de bits, porque o


protocolo é binário e não texto.

Quadro WebSocket
Uma mensagem WebSocket é composta por um ou mais frames. O quadro é uma sintaxe binária que contém
as seguintes informações, cada uma das quais descreverei com mais detalhes. Como você deve se lembrar do
Capítulo 2, as especificidades do quadro, da fragmentação e do mascaramento são todas protegidas e mantidas
nos detalhes de implementação de baixo nível do lado do servidor e do cliente. É definitivamente bom entender,
porque depurar o WebSocket com essas informações torna as coisas muito mais poderosas do que sem elas.

Fin bit
Este é o quadro final ou há uma continuação?

Opcode
Este é um quadro de comando ou quadro de dados?

Comprimento Qual é o comprimento da carga útil?

Comprimento
estendido Se a carga útil for maior que 125, usaremos os próximos 2 a 8 bytes.

Máscara
Este quadro está mascarado?

Chave de

mascaramento 4 bytes para a chave de mascaramento.

Dados de carga
útil Os dados a serem enviados, sejam strings binárias ou UTF-8, podem ser uma combinação de dados
de extensão + dados de carga útil.

Uma mensagem WebSocket pode consistir em vários quadros, dependendo de como o servidor e o cliente
decidem enviar e receber dados. E como a comunicação entre cliente e servidor é bidirecional, a qualquer

momento que qualquer um dos lados decidir, os dados podem ser enviados de um lado para outro, desde que
nenhum quadro próximo tenha sido enviado anteriormente por nenhum dos lados. A seguir está uma
representação de texto de um quadro WebSocket:

116 | Capítulo 8: Protocolo WebSocket


Machine Translated by Google

0 1 2 3
01234567890123456789012345678901
+-+-+-+-+-------+-+------------+----------------- --------------+
|F|R|R|R| código de operação|M| Capacidade de carga útil | Comprimento de carga útil estendido |
|EU|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (se carga útil len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Comprimento estendido da carga útil continuado, se carga útil len == 127 |
+ - - - - - - - - - - - - - - - +------------------------------------------+
| |Chave de mascaramento, se MASK estiver definida como 1 |
+------------------------------------------+----------------- --------------+
| Chave de máscara (continuação) | Dados de carga útil |
+-------------------------------- - - - - - - - - - - - - - - - +
: Dados de carga útil, continuação ... :
+------------------------------+
| Dados de carga útil, continuação ... |
+---------------------------------------------------------------- --------------+

Vamos falar sobre cada um dos elementos do cabeçalho com mais detalhes.

Barbatana

O primeiro bit do cabeçalho WebSocket é o bit Fin. Se o bit estiver definido, este fragmento é
a parte final de uma mensagem. Se o bit estiver limpo, a mensagem não estará completa com o seguinte
fragmento de descida. Como você verá na próxima seção, o opcode a ser passado é 0x00.

Códigos de operação de quadro

Cada quadro possui um opcode que identifica o que o quadro representa. Esses códigos de operação
são definidos na RFC 6455. Os valores iniciais são definidos pela IANA no site da Web.
Registro de soquete e estão atualmente em uso; adições a isso são possíveis com WebSocket
Extensões. O opcode é colocado nos segundos 4 bits do primeiro byte do
cabeçalho do quadro. A Tabela 8-2 lista as definições de opcode.

Tabela 8-2. Definição de código de operação

Valor do código de operação Descrição

0x00 Quadro de continuação; este quadro continua a carga do anterior.

0x01 Quadro de texto; este quadro inclui dados de texto UTF-8.

0x02 Quadro binário; este quadro inclui dados binários.

0x08 Quadro fechado de conexão; este quadro encerra a conexão.

0x09 Quadro de ping; este quadro é um ping.

Quadro WebSocket | 117


Machine Translated by Google

Valor do código de operação Descrição

0x0a Moldura Pong; esse quadro é um pong.

0x0b-0x0f Reservado para quadros de controle futuros.

Mascaramento Por padrão, todos os frames WebSocket devem ser mascarados do lado do cliente,
e o servidor deve fechar a conexão se receber um frame indicando o contrário. Como você descobriu
em “Mascaramento de quadro” na página 87, o mascaramento introduz variação no quadro para
evitar envenenamento de cache. O segundo byte do quadro é ocupado pelo comprimento dos
últimos 7 bits, e o primeiro bit indica se o quadro está mascarado. A máscara a ser aplicada serão
os 4 bytes seguintes ao comprimento estendido do cabeçalho do quadro WebSocket. Todas as
mensagens recebidas por um servidor WebSocket devem ser desmascaradas antes do
processamento posterior:

var desmascarar = function(máscara, buffer) {


var carga útil = novo Buffer(buffer.length); for (var
i=0; i<buffer.length; i++) {
carga útil[i] = máscara[i % 4] ^ buffer[i];

} retornar carga útil;


}

Após o desmascaramento, o servidor pode decodificar UTF-8 para mensagens baseadas em texto
(opcode 0x01) e entregar inalterado para mensagens binárias (opcode 0x02).

Comprimento O comprimento da carga útil é definido pelos últimos 7 bits do segundo byte do cabeçalho do quadro.
O primeiro byte é o opcode definido anteriormente. Dependendo do tamanho da carga útil, ela pode
ou não usar os bytes de comprimento estendido que seguem os primeiros 2 bytes do cabeçalho:

• Para mensagens com menos de 126 bytes (0–125), o comprimento é compactado nos últimos 7
bits do segundo byte do cabeçalho do

quadro. • Para mensagens entre 126 e 216, dois bytes adicionais são usados no comprimento
estendido após o comprimento inicial. Um valor de 126 será colocado nos primeiros 7 bits da
seção de comprimento para indicar o uso dos 2 bytes seguintes para comprimento. • Para

mensagens maiores que 216, acabará usando todos os 8 bytes após o comprimento. Um valor de
127 será colocado nos primeiros 7 bits da seção de comprimento para indicar o uso dos 8 bytes
seguintes de comprimento.

118 | Capítulo 8: Protocolo WebSocket


Machine Translated by Google

Fragmentação
Em dois casos, dividir uma mensagem em vários quadros pode fazer sentido.

Um caso é que, sem a capacidade de fragmentar mensagens, o terminal teria que armazenar toda a
mensagem em buffer antes de enviá-la, para que pudesse enviar de volta uma contagem precisa.
Com a capacidade de fragmentar, o endpoint pode escolher um buffer de tamanho razoável e,
quando estiver cheio, enviar outro quadro como continuação até que tudo esteja completo.

O segundo caso é a multiplexação, na qual não é desejável preencher o canal com dados que estão
sendo compartilhados e, em vez disso, dividi-los em vários pedaços antes de enviar. A multiplexação
não é suportada diretamente no protocolo WebSocket, mas a extensão x-google-mux pode oferecer
suporte. Para saber mais sobre extensões e como elas se relacionam com o protocolo WebSocket,
consulte “Extensões WebSocket” na página 122.

Se um quadro não estiver fragmentado, o bit Fin será definido e conterá um opcode diferente de
0x00. Se fragmentado, o mesmo opcode deve ser usado no envio de cada quadro até que a
mensagem seja concluída. Além disso, o bit Fin seria 0x00 até o quadro final, que estaria vazio,
exceto o bit Fin definido e um opcode de 0x00 usado.

Se estiver enviando uma mensagem fragmentada, deve haver a capacidade de intercalar quadros
de controle quando qualquer um dos lados estiver aceitando a comunicação (se uma mensagem
grande for enviada e um quadro de controle não puder ser enviado até o final, seria bastante
ineficiente). A última coisa necessária a lembrar é que a mensagem fragmentada deve ser toda do
mesmo tipo – sem mistura e correspondência de dados binários e de string UTF-8 em uma única
mensagem.

Aperto de mão próximo do WebSocket

O handshake de fechamento para uma conexão WebSocket requer que um quadro seja enviado com
o opcode 0x08. Se o cliente enviar o quadro de fechamento, ele deverá ser mascarado como é feito
em todos os outros casos do cliente, e não mascarado voltando do servidor. Além do opcode, o
quadro de fechamento pode conter um corpo que indica o motivo do fechamento, na forma de um
código e uma mensagem. O código de status é passado no corpo da mensagem e é um número
inteiro não assinado de 2 bytes. A string de razão restante seguiria e, como acontece com as
mensagens WebSocket, seria uma string codificada em UTF-8.

A Tabela 8-3 mostra os códigos de status disponíveis para um evento de fechamento do WebSocket .
Cada um dos códigos de status registrados na RFC é identificado e descrito na próxima seção.

Aperto de mão próximo do WebSocket | 119


Machine Translated by Google

Tabela 8-3. Códigos de status registrados do WebSocket

Status Significado Descrição


código

1000 Fechamento normal Envie este código quando seu aplicativo for concluído com sucesso.

1001 Indo embora Envie este código quando o servidor ou o aplicativo cliente estiver desligando ou fechando

sem expectativa de continuar.

1002 Erro de protocolo Envie este código quando a conexão estiver fechando com um erro de protocolo.

1003 Dados não suportados Envie este código quando seu aplicativo receber uma mensagem de tipo inesperado que
não consegue lidar.

1004 Reservado Não use; isso é reservado de acordo com RFC 6455.

1005 Nenhum status recebido Não use; a API usará isso para indicar quando nenhum código válido foi recebido.

1006 Fechamento anormal Não use; a API usará isso para indicar que a conexão foi fechada de forma anormal.

1007 Quadro inválido Envie este código se os dados da mensagem recebida não forem consistentes com o tipo do

dados de carga útil mensagem (por exemplo, não UTF-8).

1008 Violação da política Envie este código quando a mensagem recebida violar uma política. Este é um status genérico
código que pode ser retornado quando não houver mais códigos de status adequados.

1009 Mensagem muito grande Envie este código quando a mensagem recebida for muito grande para ser processada.

1010 Ext. obrigatório Envie este código se você espera uma extensão do servidor, mas ela não foi retornada em
o aperto de mão WebSocket.

1011 Erro interno Envie este código quando a conexão for encerrada devido a uma condição inesperada.

1012 Reinicialização do serviço Envie este código indicando que o serviço foi reiniciado e um cliente que se reconectar deverá

faça isso com um atraso aleatório de 5–30s.

1013 Tente mais tarde Envie este código quando o servidor estiver sobrecarregado e o cliente precisar se conectar a um

IP diferente (dados vários alvos) ou reconecte-se ao mesmo IP quando o usuário tiver executado
uma ação.

1014 Não atribuído Não use; isso não foi atribuído, mas pode ser alterado em revisões futuras.

1015 Aperto de mão TLS Não use; isso é enviado quando o handshake TLS falha.

Ao contrário do TCP, onde as conexões podem ser fechadas a qualquer momento sem aviso prévio, a Web
O fechamento do soquete é um aperto de mão de ambos os lados. A RFC também identifica os intervalos e

120 | Capítulo 8: Protocolo WebSocket


Machine Translated by Google

o que eles significam categoricamente para sua aplicação. Em geral, você usará o intervalo definido para a versão
atual (1000 a 1013) e, considerando quaisquer códigos personalizados necessários em seu aplicativo, o intervalo
não registrado de 4000 a 4999 estará disponível.

Se um endpoint receber um quadro Close sem enviá-lo, ele deverá enviar um quadro Close como resposta
(ecoando o código de status recebido). Além disso, nenhum outro dado pode passar por uma conexão WebSocket
que tenha recebido um quadro Close anteriormente.
Certamente há casos em que um terminal atrasa o envio de um quadro Close até que toda a sua mensagem atual
seja enviada (no caso de mensagens fragmentadas), mas a probabilidade de a outra extremidade processar essa
mensagem não é garantida.

Quando um endpoint (cliente ou servidor) envia e recebe um quadro Close, a conexão Web-Socket é fechada e a
conexão TCP deve ser fechada. Um servidor sempre fechará a conexão após receber e enviar imediatamente,

enquanto o cliente deverá aguardar o fechamento de um servidor ou definir um tempo limite para fechar a conexão
TCP subjacente em um período de tempo razoável após um quadro Fechar.

A IANA possui um registro dos códigos de status do WebSocket para usar durante o handshake de fechamento.

A Tabela 8-4 mostra a gama completa de códigos de status para um evento de fechamento do WebSocket .

Tabela 8-4. Intervalos de código próximos do WebSocket

Faixa de status Descrição

0–999 Este intervalo não é usado para códigos de status.

1000–2999 Os códigos de status nesta faixa são definidos pela RFC 6455 ou estarão em revisões futuras.

3000–3999 Este intervalo é reservado para bibliotecas, estruturas e aplicativos.

4000–4999 Este intervalo é reservado para uso privado e não está registrado na IANA. Sinta-se à vontade para usar esses valores em
seu código entre cliente e servidor mediante acordo prévio.

Subprotocolos WebSocket
A RFC para WebSocket define subprotocolos e negociação de protocolo entre cliente e servidor. No Capítulo 2,
você viu como passar um ou mais protocolos por meio da API JavaScript WebSocket. Agora que estamos no
capítulo dedicado às entranhas do WebSocket, você pode ver como essa negociação realmente acontece ou não.
No nível mais baixo, a negociação de qual protocolo usar para uma conexão WebSocket acontece através do
cabeçalho HTTP Sec-WebSocket-Protocol. Este cabeçalho é passado com a solicitação de atualização inicial
enviada pelo cliente:

Protocolo Sec-WebSocket: com.acme.chat, com.acme.anotherchat

Subprotocolos WebSocket | 121


Machine Translated by Google

Nesse caso, o cliente está informando ao servidor que os dois protocolos com os quais ele gostaria de falar
são chat ou outro chat. Neste ponto, cabe ao servidor decidir qual protocolo irá escolher. Se o servidor não
concordar com nenhum dos protocolos, ele retornará nulo ou não retornará esse cabeçalho. Se o servidor
concordar com um subprotocolo, ele responderá com um cabeçalho como este:

Protocolo Sec-WebSocket: com.acme.anotherchat

Como você deve se lembrar do Capítulo 2, seu objeto JavaScript WebSocket terá a propriedade protocol
preenchida com o valor escolhido pelo servidor, ou none se nada tiver sido escolhido. Neste caso, a API terá
o valor com.acme.another-chat porque a resposta do handshake do servidor indica este como um protocolo
aceitável para comunicação. Um subprotocolo não altera o protocolo Web-Socket subjacente, mas apenas
coloca camadas sobre ele, fornecendo um canal de comunicação de nível superior sobre o protocolo
existente. A capacidade de alterar a definição de um quadro WebSocket está disponível para você, no
entanto, na forma de “Extensões WebSocket” na página 122.

Lembre-se do Capítulo 2 que três tipos de subprotocolos podem ser usados com o handshake de
subprotocolo. Os primeiros são os protocolos registrados, identificados no WebSocket RFC 6455, seção
11.5. Define um registro junto à IANA. Os segundos são protocolos abertos como XMPP ou STOMP, embora
você também possa ver protocolos registrados para estes. E o terceiro, que você provavelmente usará em
sua aplicação, são os protocolos personalizados, que geralmente assumem a forma do nome de domínio
com um identificador para o nome do subprotocolo.

Extensões WebSocket
O WebSocket RFC define Sec-WebSocket-Extensions como um cabeçalho HTTP opcional a ser enviado
pelo cliente conectado perguntando se o servidor pode suportar qualquer uma das extensões listadas. O
cliente passará uma ou mais extensões com possíveis parâmetros através do cabeçalho HTTP, e o servidor
responderá com uma ou mais extensões aceitas. O servidor pode escolher apenas na lista passada pelo
cliente.

As extensões têm controle para adicionar novos opcodes e campos de dados ao formato de enquadramento.
Em essência, você pode alterar completamente todo o formato de um quadro WebSocket com uma extensão
WebSocket. Uma das especificações anteriores, draft-ietf-hybi-thewebsocketprotocol-10, até mencionou
uma extensão deflate-stream , que compactaria todo o fluxo do WebSocket. A eficácia disso é provavelmente
a razão pela qual não aparece mais em especificações posteriores, porque o WebSocket possui mascaramento
de quadro cliente-servidor, por meio do qual a máscara muda por quadro e, com isso, a deflação seria
totalmente ineficaz.

122 | Capítulo 8: Protocolo WebSocket


Machine Translated by Google

Aqui estão dois exemplos de extensões que estão disponíveis nos clientes hoje:

• quadro desinflado, um método melhor de deflate (disponível no Chrome, que usa x-webkit-
deflate-frame como nome), onde os frames são compactados na origem e extraídos no
destino

• x-google-mux, uma extensão em estágio inicial que suporta multiplexação

A única ressalva, e que tem sido um problema com a adoção de qualquer nova tecnologia
associada a navegadores como clientes, é que o suporte deve ser incorporado aos navegadores
usados por seus clientes. O servidor analisará as extensões passadas pelo cliente e retornará a
lista que suportará. A ordem das extensões repassadas deve coincidir com o que foi repassado
pelo cliente. Ele deve devolver apenas extensões que o cliente indicou que também suporta.

Implementações de servidores alternativos


Neste livro, optei por focar exclusivamente no uso do Node.js no lado do servidor.
As implementações do protocolo WebSocket no lado do servidor são difundidas e abordadas em
quase todas as linguagens imagináveis. A cobertura de qualquer uma dessas outras opções do
lado do servidor certamente está fora do escopo deste livro. A seguir está uma lista não exaustiva
de algumas das implementações de WebSocket compatíveis com RFC disponíveis atualmente
para algumas das linguagens mais populares:

• API Java para WebSocket (JSR-356), que está incluída em qualquer servidor compatível com
Java EE 7, como Glassfish ou Jetty. •

Python tem diversas opções, duas das quais estão disponíveis em pywebsocket e em
ws4py.
• PHP possui uma implementação compatível com Ratchet •

Ruby possui uma implementação baseada em EventMachine, em-websocket.

Estas são apenas algumas das implementações mais populares em cada linguagem. Como
acontece com qualquer decisão técnica sobre back-end, avalie as opções para a plataforma
escolhida e use-as e as informações contidas neste livro como um guia ao longo do caminho.

Implementações de servidores alternativos | 123


Machine Translated by Google

Resumo
Este capítulo entrou em muitos detalhes sobre o protocolo WebSocket — esperançosamente,
o suficiente para você usá-lo como está ou estendê-lo na forma de subprotocolos dispostos
em camadas sobre o protocolo WebSocket subjacente. O protocolo WebSocket percorreu
um longo caminho para chegar onde está hoje e, embora possam ocorrer mudanças no
futuro, parece ser uma forma sólida de comunicação de maneira mais eficiente e poderosa.
É hora de acabar com os hacks historicamente necessários do passado e abraçar o poder
fornecido pelo protocolo WebSocket e sua API.

124 | Capítulo 8: Protocolo WebSocket


Machine Translated by Google

Índice

Atributo bufferedAmount, 19

Um método addEventListener(), 13, 36


Soquete Adobe Flash, 67
Protocolo avançado de enfileiramento de mensagens (AMQP), Envenenamento de cache
44, 56 C , 87 autoridade de certificação
implementações de servidor alternativo, 123 (CA), 80 solicitação de assinatura de certificado
Apache, 78 (CSR), 81 canais, Pusher.com, 71-72
API (Application Programming Interface), 9-21 atributos, 18-19 bate-papo (consulte bate-papo
eventos, 12-16 bidirecional)
inicialização, clientes de bate-papo
9-11 métodos, 16-18 Pusher.com,
Pusher (consulte 76-77
Pusher.com) exemplo de Socket.IO, 69 SockJS ,
estoque servidor, 19-21 UI de 66 WebSocket,
exemplo de estoque, 11-12 31-34 servidores de
testes para suporte, 21 bate-papo
Array.indexOf, 41 Pusher.com,
atributos 73-76 Socket.IO, 68
bufferedAmount, 19 SockJS, 63-66 WebSocket, 30-31 Chrome Developer
protocolo, 19 Tools, 6, 101-102, 106
readyState, 17, 18 Clickjacking, 85-86 clientes, validação (veja validação
de clientes) evento de

B fechamento, 15, 26

Cabeçalho básico, método de fechamento, 17-18


88 bate-papo bidirecional, 23-34 handshake de fechamento,

aplicativo de bate-papo básico, 24-27 119-121 atributo de código, 15, 16 compatibilidade, 61-78
código do cliente, 31-34, 97-99 Pusher.com, proxy

identidade do cliente, reverso 70-78 , 78


27-29 eventos e notificações, 29-30 Socket.IO, 66-70

código do servidor, 30-31, 96-97 SockJS, 62-66

Cliente WebSocket, 27 mensagens de conexão/desconexão, 29-30 Conexão:

Bootstrap (Twitter), 11 suporte cabeçalho de atualização, 112 função


connect_callback, 59 cabeçalho de
a navegador (ver compatibilidade) teste de
suporte a navegador, 21 comprimento de conteúdo, 37

125
Machine Translated by Google

Compartilhamento de recursos entre origens (CORS), 83-84 Sec-WebSocket-Key e Sec-Websocket-


Ataques de falsificação de solicitação entre sites (CSRF), 85 Aceitar, código do
solicitações entre domínios, 23 servidor 113-114 ,

Cross-Site WebSocket Hijacking (CSWSH), 85 protocolos validação 96-97 ,


personalizados, 10 102 cabeçalhos

Básico, 88
HTTP, 111, 114-116
Cabeçalhos HTTP, 111, 114-116
Mascaramento de dados D ,
Histórico HTTP, 111-112
depuração 87-88 , 95-110
(veja também
EU

ferramentas) fechamento de conexão,


108-109 validação de handshake, Bate-papo de retransmissão da Internet (IRC), 28

102 inspeção de quadros, 103-107


deflate-frame (veja extensões WebSocket) J.

Negação de serviço (DoS), 87


JavaScript, 1, 39 teste
de suporte do navegador, 21
E
framebusting, 85-86 jQuery,
Servidor de eco, 6 11
evento de erro, 15
eventos, 12-16 eu

fechamento, 15, Linux


26 erro, 15
instalando Node.js e npm, 2 instalando
mensagem, 14-15, 26
OpenSSL, 80 pesquisas longas,
abertura,
7-8, 23
13 PING/PONG, 15
Pusher.com, 72-73
Socket.IO, 67-68
SockJS, 63- 65 Mascaramento M ,
chave de mascaramento
Biblioteca Expressa, 64-66
87-88 , 87 eventos de mensagem,
14-15, 26
F
métodos de
Fin bit, 117, 119
fechamento,
autenticação baseada em formulário com cookie,
17-18 envio, 16-17 multiplexação, 119
88-92 fragmentação, 119
mascaramento de quadro, 87-88, 103-107, 118
N
framebusting, 85-87 quadros,
farejadores de rede (consulte Wireshark)
116 (veja
também quadro WebSocket) quadro nginx, 9, 78, 87
Node.js, 1-3
de fechamento, 108- 109
gerenciador de pacotes (npm), 2-3, 24, 44, 63, 88 cliente
inspecionando, 103-107
STOMP para, 57

O
Handshake H , código do
cliente 95-102 , navegadores mais antigos (ver compatibilidade)
no manipulador <nome do evento>, 13
fechamento 97-99 , 108-109, 119-121
Cabeçalhos HTTP, 114-116 opcodes, 117
eventos abertos, 13
abertos, 112-116
protocolos abertos, 10

126 | Índice
Machine Translated by Google

Instalações OpenSSL, 80 funções de envio (consulte STOMP)


Modelo de segurança baseado na origem, método de envio, 16-17 ID

clickjacking 83-87 , 85-86 da sessão (consulte STOMP)


X-Frame-Options para eliminação de quadros, 86-87 Slowloris, 87
OS X Socket.IO, 24, 66-70 Adobe

instalando Node.js e npm, 2 instalando Flash Socket, 67 transportes

OpenSSL, 80 OWASP ZAP, alternativos, 66 cliente de chat,


95, 99-101 69-70 servidor de chat,
68 conexões, 67-68

eventos, 67-68

Comprimento da carga útil


P , 118 eventos PING/PONG, 15 nomeando, 68-70
SockJS, cliente de
função process_frame (consulte STOMP)
bate-papo 62-66 ,
protocolAttribute, 19
servidor de bate-papo
protocolos, 10-11
66 , manipulação de eventos
(consulte também protocolo WebSocket)
63-66 ,
proxy_wstunnel, 78
biblioteca 63-65 ,
Pusher.com, 70-78
canais, 71-72 cliente bibliotecas de servidor 66 , transcrições suportadas 62 , 62

de bate-papo, 76 -77 STOMP (Protocolo de mensagens orientadas a texto simples),


aplicativo cliente
exemplo de chat, 77
servidor de chat, 73-76 35-59 , 50-56
Comandos CONNECT/DISCONNECT, 50,
eventos, 72-73
54-56

conectando o servidor ao RabbitMQ, 44-48 evento de


R conexão, 40-41 conexão via
RabbitMQ, 35
servidor, 39-42 cabeçalho de comprimento
conectando o servidor, 44-48 configurando,
de conteúdo, 37 função
42-44 daemon de error_callback, 59 conectando-se,
preço de ações, 47-48 com Web-
36-39 implementando, 36-42
Stomp, 56-59 (veja também
estrutura de objeto
Web-Stomp) atributo
JavaScript, 39 MENSAGEM comando,
readyState, 17, 18 atributo de razão, 55 arquivos necessários, 35
15, 16 protocolos registrados,
estrutura de objeto,
10 proxy reverso, 78
39 processamento de
solicitações STOMP, 49-50 função
RFC 6455, 1, 10, 61, 87, 113-114, 117
process_frame, 39 envio de quadros
RFC 7034, 86
compatíveis com STOMP, 37 função send_error, 39
função send_frame, 39 função
S send_message, 59 ID de sessão,

política de mesma origem (SOP), 83 37 configuração do Rabbit MQ ,


Sec-WebSocket-Aceitar, 114 42-44 cliente

Sec-WebSocket-Key, segurança STOMP para Web e Node.js, 57


113 , 79-93 biblioteca Stomp.js, 58 stomp_helper.js, 37 comandos
Negação de serviço (DoS), SUBSCRIBE/
mascaramento de 87 quadros, 87-88 UNSUBSCRIBE,
Modelo de segurança baseado na origem, 83-87
TLS (Transport Layer Security), 79-83 clientes de 50-54

validação, 88-92 Web-Stomp, 56-59

Índice | 127
Machine Translated by Google

subprotocolos, 10, 35, 121-122 (veja intervalos de código


também STOMP (Simple Text Oriented próximos, 121 parâmetros de
Protocolo de mensagens)) construtor,
Comandos SUBSCRIBE/UNSUBSCRIBE (veja 10 eventos, 10 Hello World!
PISO) exemplo, inicialização
3-7 , visão geral 4-5 , 1-3

T códigos de status registrados, 119


versus pesquisa longa, 7-8
TLS (Transport Layer Security), exemplo 79-83 ,
Cliente WebSocket, 27
82-83 gerando um
Extensões WebSocket, 122-123
certificado autoassinado, 79-82 erro de segurança de
Quadro WebSocket, 116-119
conteúdo misto, 83
Fin bit, 117, 119
Configuração do WebSocket concluída, 80-83
ferramentas fragmentação, 119
comprimento,
Ferramentas para desenvolvedores do Chrome, 6, 101-102, 106
OWASP ZAP, 95, 99-101 118
mascaramento, 118 opcodes, 117
Vagabundo, 42-44
Wireshark, 103-107 Aperto de mão de abertura/fechamento do WebSocket (veja aperto
de mão)
Bootstrap do Twitter, 11
Protocolo WebSocket
extensões, 122-123
EM
Histórico HTTP, subprotocolos
UUID (identificador universalmente exclusivo), 24-25, 50
de handshake aberto 111-112 (consulte
handshake), 121-122
EM Quadro Websocket, 116-119

Vagrant, 42-44 WebSocket Secure, 83, 102 (veja

validando clientes, 88-92 também TLS (segurança da camada de transporte))


ouvindo solicitações da Web, 89-91 Registro de nome de subprotocolo WebSocket, cliente
configurando dependências e inits, 88-89 36 web_socket.js, 71 Windows
Servidor WebSocket, 91-92
instalando Node.js e npm, 2 instalando

EM OpenSSL, 80 Wireshark,
103-107
Valdo, 86-87
Web-Stomp, cliente de
eco 56-59 para, 57-59 X

instalação, 57 Opções de quadro X, 86-87 x-


WebSocket google-mux, 123
API (consulte API (Programação de Aplicativos XHR (XMLHttpRequest), 23, 83
Interface))

128 | Índice
Machine Translated by Google

Sobre o autor
Andrew Lombardi é um empresário veterano e desenvolvedor de software. Seus pais o ensinaram a
programar – embora ele mal conseguisse ler – em um Apple II que ele ainda gostaria de ter. Ele dirige
a empresa de consultoria Mystic Coders há 15 anos, codificando, falando internacionalmente e
oferecendo orientação técnica para empresas grandes como o Walmart e empresas com problemas tão
interessantes quanto a simulação de helicópteros. Ele acredita firmemente que a melhor coisa que fez
até agora foi ser um ótimo pai.

Colofão
O animal da capa do WebSocket é uma anêmona do mar (ordem Actiniaria).

Mais de 1.000 espécies de anêmonas marinhas são encontradas nos oceanos do mundo.
Eles são particularmente abundantes em águas tropicais costeiras. Esses organismos tendem a
permanecer enraizados em um só lugar, ancorando-se em superfícies duras, como recifes de coral ou
rochas no fundo do mar.

Intimamente relacionadas aos corais e às águas-vivas, as anêmonas do mar são invertebrados com
corpos cilíndricos cercados por tentáculos. Eles variam em tamanho, variando de meia polegada a seis
pés de diâmetro, e podem possuir de uma dúzia a várias centenas de tentáculos. Eles também aparecem
em uma variedade de cores vivas, lembrando flores como sua homônima, a anêmona terrestre.

Apesar de sua beleza elegante, esses animais são bastante predadores. Seus tentáculos são pontilhados
de células urticantes que são usadas para imobilizar e consumir pequenos peixes e crustáceos.
Conseqüentemente, as anêmonas do mar têm poucos predadores próprios e muitas espécies vivem
mais de 50 anos.

As anêmonas do mar são frequentemente citadas por suas relações simbióticas com o peixe-palhaço,
que possui uma camada protetora que as torna imunes à picada letal da anêmona. O peixe-palhaço
está a salvo de seus inimigos entre os tentáculos da anêmona, enquanto a anêmona aproveita os restos
de comida das refeições do peixe-palhaço.

Muitos dos animais nas capas da O'Reilly estão ameaçados de extinção; todos eles são importantes
para o mundo. Para saber mais sobre como você pode ajudar, acesse pets.oreilly.com.

A imagem da capa é de placas soltas (fonte original desconhecida). As fontes da capa são URW
Typewriter e Guardian Sans. A fonte do texto é Adobe Minion Pro; a fonte do título é Adobe Myriad
Condensed; e a fonte do código é Ubuntu Mono da Dalton Maag.

Você também pode gostar