Guia do Node.js, parte 8: protocolos HTTP e WebSocket

O Node.js é uma plataforma de servidor. A principal tarefa do servidor é processar solicitações de clientes, em particular, de navegadores, da maneira mais rápida e eficiente possível. A oitava parte da tradução do tutorial Node.js. que publicamos hoje é sobre os protocolos HTTP e WebSocket.




O que acontece ao fazer solicitações HTTP?


Vamos falar sobre como os navegadores fazem solicitações para servidores usando o protocolo HTTP / 1.1.

Se você já teve uma entrevista no campo de TI, poderá ser perguntado o que acontece quando você digita algo na barra de endereços do seu navegador e pressiona Enter. Talvez essa seja uma das perguntas mais populares que ocorrem nessas entrevistas. Qualquer pessoa que faça essas perguntas quer saber se você pode explicar alguns conceitos bastante simples e descobrir se você entende os princípios da Internet.

Esta questão aborda muitas tecnologias, para entender os princípios gerais dos quais significa entender como é construído um dos sistemas mais complexos já construídos pela humanidade, que abrange o mundo inteiro.

▍ protocolo HTTP


Os navegadores modernos são capazes de distinguir os URLs reais inseridos na barra de endereços das consultas de pesquisa, para o processamento do qual o mecanismo de pesquisa padrão geralmente é usado. Vamos falar sobre URLs. Se você inserir um endereço de site, como flaviocopes.com , na linha do navegador, o navegador converterá esse endereço no formato http://flaviocopes.com , com base na suposição de que o protocolo HTTP será usado para trocar dados com o recurso especificado. Observe que no Windows, o que falaremos aqui pode parecer um pouco diferente do que no macOS e Linux.

Phase fase de pesquisa de DNS


Portanto, o navegador, iniciando o trabalho de download de dados do endereço solicitado pelos usuários, executa a operação de Pesquisa de DNS (Pesquisa de DNS) para descobrir o endereço IP do servidor correspondente. Os nomes simbólicos dos recursos inseridos na barra de endereços são convenientes para as pessoas, mas o dispositivo da Internet implica a possibilidade de trocar dados entre computadores usando endereços IP, que são conjuntos de números como 222.324.3.1 (para IPv4).

Primeiro, descobrindo o endereço IP do servidor, o navegador examina o cache DNS local para verificar se um procedimento semelhante foi executado recentemente. No navegador Chrome, por exemplo, existe uma maneira conveniente de examinar o cache DNS inserindo o seguinte endereço na barra de endereços: chrome://net-internals/#dns .

Se nada puder ser encontrado no cache, o navegador utilizará a chamada de sistema gethostbyname do POSIX para descobrir o endereço IP do servidor.

▍ função gethostbyname


A função gethostbyname primeiro verifica o hosts , que, no macOS ou Linux, pode ser encontrado em /etc/hosts para descobrir se as informações locais podem ser encontradas ao descobrir o endereço do servidor.

Se local significa resolver a solicitação e descobrir o endereço IP do servidor, o sistema executará uma solicitação ao servidor DNS. Os endereços desses servidores são armazenados nas configurações do sistema.

Aqui estão alguns servidores DNS populares:

  • 8.8.8.8: servidor DNS do Google.
  • 1.1.1.1: servidor DNS CloudFlare.

A maioria das pessoas usa os servidores DNS fornecidos por seus provedores. O navegador executa consultas DNS usando o protocolo UDP.

TCP e UDP são dois protocolos básicos usados ​​em redes de computadores. Eles estão localizados no mesmo nível conceitual, mas o TCP é um protocolo orientado a conexão e, para a troca de mensagens UDP, cujo processamento cria uma pequena carga adicional no sistema, não é necessário um procedimento de estabelecimento de conexão. Não falaremos exatamente sobre como os dados são trocados pelo UDP.

O endereço IP correspondente ao nome de domínio de seu interesse pode estar no cache do servidor DNS. Se não for esse o caso, ele entrará em contato com o servidor DNS raiz. O sistema do servidor DNS raiz consiste em 13 servidores, dos quais depende a operação de toda a Internet.

Deve-se notar que o servidor DNS raiz não conhece a correspondência entre todos os nomes de domínio e endereços IP existentes no mundo. Porém, servidores semelhantes conhecem os endereços dos servidores DNS de nível superior para domínios como .com, .it, .pizza e assim por diante.

Após o recebimento da solicitação, o servidor DNS raiz o redireciona para o servidor DNS do domínio de nível superior, para o chamado servidor TLD (do domínio de nível superior).

Suponha que o navegador esteja procurando o endereço IP do servidor flaviocopes.com . Voltando ao servidor DNS raiz, o navegador receberá o endereço do servidor TLD da zona .com. Agora, esse endereço será armazenado no cache; como resultado, se você precisar descobrir o endereço IP de outro URL da zona .com, não precisará acessar o servidor DNS raiz novamente.

Os servidores de TLD têm endereços IP de servidores de nomes (Name Server, NS), com a ajuda da qual você pode descobrir o endereço IP a partir do URL que temos. Onde o servidor NS obtém essas informações? O fato é que, se você compra um domínio, o registrador do domínio envia dados sobre ele para os servidores de nomes. Um procedimento semelhante é realizado, por exemplo, ao alterar a hospedagem.

Os servidores em questão geralmente são de propriedade de provedores de hospedagem. Como regra, para proteger contra falhas, vários desses servidores são criados. Por exemplo, eles podem ter estes endereços:

  • ns1.dreamhost.com
  • ns2.dreamhost.com
  • ns3.dreamhost.com

Para descobrir o endereço IP por URL, no final, eles recorrem a esses servidores. Eles armazenam os dados reais sobre endereços IP.

Agora, depois que conseguimos descobrir o endereço IP por trás da URL inserida na barra de endereços do navegador, passamos para a próxima etapa do nosso trabalho.

▍ Estabelecendo uma conexão TCP


Depois de aprender o endereço IP do servidor, o cliente pode iniciar uma conexão TCP com ele. No processo de estabelecer uma conexão TCP, o cliente e o servidor transmitem um ao outro alguns dados de serviço, após o qual eles podem trocar informações. Isso significa que depois que a conexão for estabelecida, o cliente poderá enviar uma solicitação ao servidor.

RequestEnviando pedido


Uma solicitação é um fragmento de texto estruturado de acordo com as regras do protocolo usado. Consiste em três partes:

  • String de consulta
  • Cabeçalho da solicitação.
  • Corpo da solicitação.

String de consulta


A cadeia de consulta é uma cadeia de texto única que contém as seguintes informações:

  • Método HTTP.
  • Endereço do recurso
  • Versão do protocolo.

Pode parecer, por exemplo, assim:

 GET / HTTP/1.1 

Cabeçalho da solicitação


O cabeçalho da solicitação é representado por um conjunto de : . Existem 2 campos de cabeçalho obrigatórios, um dos quais é Host e o segundo é Connection . Os demais campos são opcionais.

O título pode ficar assim:

 Host: flaviocopes.com Connection: close 

O campo Host indica o nome de domínio no qual o navegador está interessado. O campo Connection , definido para close , significa que a conexão entre o cliente e o servidor não precisa ser mantida aberta.

Outros cabeçalhos de solicitação comumente usados ​​incluem o seguinte:

  • Origin
  • Accept
  • Accept-Encoding
  • Cookie
  • Cache-Control
  • Dnt

De fato, existem muitos mais.

O cabeçalho da solicitação termina com uma sequência vazia.

Organismo de solicitação


O corpo da solicitação é opcional; não é usado nas solicitações GET. O corpo da solicitação é usado em solicitações POST, bem como em outras solicitações. Pode conter, por exemplo, dados no formato JSON.

Como agora estamos falando de uma solicitação GET, o corpo da solicitação estará vazio, não trabalharemos com ela.

▍ Resposta


Depois que o servidor recebe a solicitação enviada pelo cliente, ele a processa e envia uma resposta ao cliente.

A resposta começa com um código de status e uma mensagem correspondente. Se a solicitação for bem-sucedida, o início da resposta terá a seguinte aparência:

 200 OK 

Se algo der errado, pode haver outros códigos. Por exemplo, o seguinte:

  • 404 Not Found
  • 403 Forbidden
  • 301 Moved Permanently
  • 500 Internal Server Error
  • 304 Not Modified
  • 401 Unauthorized

Além disso, a resposta contém uma lista de cabeçalhos HTTP e o corpo da resposta (que, como a solicitação é executada pelo navegador, será código HTML).

Análise de HTML


Depois que o navegador recebe a resposta do servidor, cujo corpo contém código HTML, ele começa a analisá-lo, repetindo o processo acima para cada recurso necessário para formar a página. Esses recursos incluem, por exemplo, o seguinte:

  • Arquivos CSS.
  • Imagens
  • Ícone de página da Web (favicon).
  • Arquivos JavaScript.

Como exatamente o navegador exibe a página não se aplica à nossa conversa. O principal que nos interessa aqui é que o processo acima de solicitar e receber dados é usado não apenas para o código HTML, mas também para outros objetos transferidos do servidor para o navegador usando o protocolo HTTP.

Sobre a criação de um servidor simples usando o Node.js


Agora, depois de examinarmos o processo de interação entre o navegador e o servidor, você pode dar uma nova olhada na seção do aplicativo First Node.js da primeira parte desta série de materiais, na qual descrevemos o código para um servidor simples.

Fazendo solicitações HTTP com Node.js


Para executar solicitações HTTP usando o Node.js, o módulo apropriado é usado . Os exemplos abaixo usam o módulo https . O fato é que, em condições modernas, sempre que possível, é necessário usar o protocolo HTTPS.

▍ Executando solicitações GET


Aqui está um exemplo de execução de uma solicitação GET usando o Node.js:

 const https = require('https') const options = { hostname: 'flaviocopes.com', port: 443, path: '/todos', method: 'GET' } const req = https.request(options, (res) => { console.log(`statusCode: ${res.statusCode}`) res.on('data', (d) => {   process.stdout.write(d) }) }) req.on('error', (error) => { console.error(error) }) req.end() 

Execução de solicitação POST


Veja como fazer uma solicitação POST no Node.js:

 const https = require('https') const data = JSON.stringify({ todo: 'Buy the milk' }) const options = { hostname: 'flaviocopes.com', port: 443, path: '/todos', method: 'POST', headers: {   'Content-Type': 'application/json',   'Content-Length': data.length } } const req = https.request(options, (res) => { console.log(`statusCode: ${res.statusCode}`) res.on('data', (d) => {   process.stdout.write(d) }) }) req.on('error', (error) => { console.error(error) }) req.write(data) req.end() 

▍PUT e DELETE consultas


A execução de tais solicitações é igual à execução de solicitações POST. A principal diferença, além do conteúdo semântico de tais operações, é o valor da propriedade method do objeto options .

▍ Executando solicitações HTTP no Node.js usando a biblioteca Axios


O Axios é uma biblioteca JavaScript muito popular que funciona tanto no navegador (que inclui todos os navegadores modernos e no IE, começando no IE8), quanto no ambiente Node.js., que pode ser usado para executar solicitações HTTP.

Essa biblioteca é baseada em promessas, possui algumas vantagens sobre os mecanismos padrão, em particular sobre a API Fetch. Entre suas vantagens estão as seguintes:

  • Suporte para navegadores mais antigos (você precisa de um polyfill para usar o Fetch).
  • Capacidade de interromper solicitações.
  • Suporte para definir tempos limite para solicitações.
  • Proteção integrada contra ataques CSRF.
  • Suporte para upload de dados com o fornecimento de informações sobre o andamento desse processo.
  • Suporte para conversão de dados JSON.
  • Empregos em Node.js

Instalação


Você pode usar o npm para instalar o Axios:

 npm install axios 

O mesmo efeito pode ser alcançado com o fio:

 yarn add axios 

Você pode conectar a biblioteca à página usando unpkg.com :

 <script src="https://unpkg.com/axios/dist/axios.min.js"></script> 

API do Axios


Você pode fazer uma solicitação HTTP usando o objeto axios :

 axios({ url: 'https://dog.ceo/api/breeds/list/all', method: 'get', data: {   foo: 'bar' } }) 

Mas geralmente é mais conveniente usar métodos especiais:

  • axios.get()
  • axios.post()

Isso é semelhante a como o jQuery usa $.get() e $.post() vez de $.ajax() $.post() .

O Axios oferece métodos separados para executar outros tipos de solicitações HTTP, que não são tão populares quanto GET e POST, mas ainda são usados:

  • axios.delete()
  • axios.put()
  • axios.patch()
  • axios.options()

A biblioteca possui um método para executar uma solicitação projetada para receber apenas cabeçalhos HTTP, sem o corpo da resposta:

  • axios.head()

Solicitações GET


O Axios é conveniente de usar usando a sintaxe assíncrona / aguardada moderna. O exemplo de código a seguir, desenvolvido para o Node.js, usa a biblioteca para carregar uma lista de raças de cães da API Dog . Aqui o método axios.get() é aplicado e as rochas são contadas:

 const axios = require('axios') const getBreeds = async () => { try {   return await axios.get('https://dog.ceo/api/breeds/list/all') } catch (error) {   console.error(error) } } const countBreeds = async () => { const breeds = await getBreeds() if (breeds.data.message) {   console.log(`Got ${Object.entries(breeds.data.message).length} breeds`) } } countBreeds() 

O mesmo pode ser reescrito sem usar async / waitit, aplicando promessas:

 const axios = require('axios') const getBreeds = () => { try {   return axios.get('https://dog.ceo/api/breeds/list/all') } catch (error) {   console.error(error) } } const countBreeds = async () => { const breeds = getBreeds()   .then(response => {     if (response.data.message) {       console.log(         `Got ${Object.entries(response.data.message).length} breeds`       )     }   })   .catch(error => {     console.log(error)   }) } countBreeds() 

Usando parâmetros em solicitações GET


Uma solicitação GET pode conter parâmetros parecidos com este em um URL:

 https://site.com/?foo=bar 

Ao usar o Axios, uma consulta desse tipo pode ser feita assim:

 axios.get('https://site.com/?foo=bar') 

O mesmo efeito pode ser alcançado configurando a propriedade params em um objeto com parâmetros:

 axios.get('https://site.com/', { params: {   foo: 'bar' } }) 

Solicitações POST


A execução de solicitações POST é muito semelhante à execução de solicitações GET, mas aqui, em vez do método axios.get() , o método axios.post() é usado:

 axios.post('https://site.com/') 

Como segundo argumento, o método post aceita um objeto com parâmetros de solicitação:

 axios.post('https://site.com/', { foo: 'bar' }) 

Usando o protocolo WebSocket no Node.js


O WebSocket é uma alternativa ao HTTP, pode ser usado para organizar a troca de dados em aplicativos da web. Esse protocolo permite criar canais de comunicação bidirecional de longa duração entre o cliente e o servidor. Após o estabelecimento da conexão, o canal de comunicação permanece aberto, o que coloca a aplicação à disposição de uma conexão muito rápida, caracterizada por baixas latências e uma pequena carga adicional no sistema.

O protocolo WebSocket é suportado por todos os navegadores modernos.

▍ Diferenças HTTP


HTTP e WebSocket são protocolos muito diferentes que usam abordagens diferentes para a troca de dados. O HTTP é baseado no modelo "solicitação-resposta": o servidor envia alguns dados para o cliente após a solicitação. No caso do WebSocket, tudo é organizado de maneira diferente. Ou seja:

  • O servidor pode enviar mensagens para o cliente por sua própria iniciativa, sem aguardar uma solicitação do cliente.
  • O cliente e o servidor podem trocar dados ao mesmo tempo.
  • Ao transmitir uma mensagem, uma quantidade extremamente pequena de dados de serviço é usada. Isso, em particular, leva a baixa latência na transmissão de dados.

O protocolo WebSocket é muito adequado para comunicações em tempo real através de canais que permanecem abertos por muito tempo. O HTTP, por sua vez, é excelente para organizar sessões de comunicação ocasionais iniciadas pelo cliente. Ao mesmo tempo, deve-se notar que, do ponto de vista da programação, é muito mais fácil implementar a troca de dados usando o protocolo HTTP do que usando o protocolo WebSocket.

Version Versão protegida do protocolo WebSocket


Há uma versão não segura do protocolo WebSocket (esquema URI ws:// ), que se assemelha, em termos de segurança, ao protocolo http:// . O uso de ws:// deve ser evitado, preferindo uma versão segura do protocolo - wss:// .

ReatCriando uma conexão WebSocket


Para criar uma conexão WebSocket, você precisa usar o construtor apropriado:

 const url = 'wss://myserver.com/something' const connection = new WebSocket(url) 

Após uma conexão bem-sucedida, o evento open é gerado. Você pode organizar esse evento atribuindo uma função de retorno de chamada à propriedade onopen do objeto de connection :

 connection.onopen = () => { //... } 

Para manipular erros, o manipulador de eventos onerror é usado:

 connection.onerror = error => { console.log(`WebSocket error: ${error}`) } 

DataEnviando dados para o servidor


Após abrir uma conexão WebSocket com o servidor, você pode enviar dados para ele. Isso pode ser feito, por exemplo, no onopen aberto:

 connection.onopen = () => { connection.send('hey') } 

▍ Obtendo dados do servidor


Para receber dados enviados usando o protocolo WebSocket do servidor, você pode atribuir o onmessage onmessage, que será chamado quando o evento da message for recebido:

 connection.onmessage = e => { console.log(e.data) } 

▍ Implementação do servidor WebSocket no ambiente Node.js.


Para implementar um servidor WebSocket no ambiente Node.js., você pode usar a popular biblioteca ws . Vamos usá-lo para o desenvolvimento de servidores, mas é adequado para criar clientes, bem como para organizar a interação entre dois servidores.

Instale esta biblioteca inicializando o projeto:

 yarn init yarn add ws 

O código para o servidor WebSocket que precisamos escrever é bastante compacto:

 constWebSocket = require('ws') const wss = newWebSocket.Server({ port: 8080 }) wss.on('connection', ws => { ws.on('message', message => {   console.log(`Received message => ${message}`) }) ws.send('ho!') }) 

Aqui, criamos um novo servidor que escuta na porta padrão 8080 o protocolo WebSocket e descreve um retorno de chamada que, quando a conexão é estabelecida, envia uma mensagem ho! ao cliente ho! e imprime no console uma mensagem recebida do cliente.

Aqui está um exemplo de trabalho de um servidor WebSocket e aqui está um cliente que pode interagir com ele.

Sumário


Hoje falamos sobre os mecanismos de rede suportados pela plataforma Node.js., traçando paralelos com mecanismos similares usados ​​nos navegadores. Nosso próximo tópico estará trabalhando com arquivos.

Caros leitores! Você usa o protocolo WebSocket em seus aplicativos Web, cujo servidor foi criado usando o Node.js.?

Source: https://habr.com/ru/post/pt424557/


All Articles