Guía de Node.js, Parte 8: Protocolos HTTP y WebSocket

Node.js es una plataforma de servidor. La tarea principal del servidor es procesar las solicitudes de los clientes, en particular de los navegadores, de la manera más rápida y eficiente posible. La octava parte de la traducción del tutorial Node.js que publicamos hoy trata sobre HTTP y WebSocket.




¿Qué sucede al hacer solicitudes HTTP?


Hablemos sobre cómo los navegadores realizan solicitudes a los servidores utilizando el protocolo HTTP / 1.1.

Si alguna vez tuvo una entrevista en el campo de TI, es posible que se le pregunte qué sucede cuando escribe algo en la barra de direcciones de su navegador y presiona Enter. Quizás esta sea una de las preguntas más populares que se produce en tales entrevistas. Cualquiera que haga tales preguntas quiere saber si puede explicar algunos conceptos bastante simples y averiguar si comprende los principios de Internet.

Esta pregunta toca muchas tecnologías, para comprender los principios generales de lo que significa comprender cómo se construye uno de los sistemas más complejos jamás creados por la humanidad, que cubre todo el mundo.

▍ protocolo HTTP


Los navegadores modernos pueden distinguir las URL reales ingresadas en su barra de direcciones de las consultas de búsqueda, para cuyo procesamiento generalmente se usa el motor de búsqueda predeterminado. Hablaremos de las URL. Si ingresa la dirección de un sitio web, como flaviocopes.com , en la línea del navegador, el navegador convierte esta dirección en el formulario http://flaviocopes.com , en el supuesto de que el protocolo HTTP se utilizará para intercambiar datos con el recurso especificado. Tenga en cuenta que en Windows, de lo que vamos a hablar aquí puede parecer un poco diferente que en macOS y Linux.

▍ Fase de búsqueda de DNS


Entonces, el navegador, comenzando a trabajar en la descarga de datos de la dirección solicitada por los usuarios, realiza la operación de búsqueda de DNS (búsqueda de DNS) para averiguar la dirección IP del servidor correspondiente. Los nombres simbólicos de los recursos ingresados ​​en la barra de direcciones son convenientes para las personas, pero el dispositivo de Internet implica la posibilidad de intercambiar datos entre computadoras usando direcciones IP, que son conjuntos de números como 222.324.3.1 (para IPv4).

Primero, al descubrir la dirección IP del servidor, el navegador busca en el caché de DNS local para ver si se ha realizado un procedimiento similar recientemente. En el navegador Chrome, por ejemplo, hay una manera conveniente de mirar el caché DNS ingresando la siguiente dirección en la barra de direcciones: chrome://net-internals/#dns .

Si no se puede encontrar nada en la memoria caché, el navegador utiliza la llamada del sistema POSIX gethostbyname para averiguar la dirección IP del servidor.

▍ función gethostbyname


La función gethostbyname primero verifica el hosts , que, en macOS o Linux, se puede encontrar en /etc/hosts para averiguar si se puede encontrar información local al encontrar la dirección del servidor.

Si local significa resolver la solicitud para averiguar la dirección IP del servidor falla, el sistema realiza una solicitud al servidor DNS. Las direcciones de dichos servidores se almacenan en la configuración del sistema.

Aquí hay un par de servidores DNS populares:

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

La mayoría de las personas usan los servidores DNS proporcionados por sus proveedores. El navegador realiza consultas DNS utilizando el protocolo UDP.

TCP y UDP son dos protocolos básicos utilizados en redes de computadoras. Están ubicados en el mismo nivel conceptual, pero TCP es un protocolo orientado a la conexión, y para el intercambio de mensajes UDP, cuyo procesamiento crea una pequeña carga adicional en el sistema, no se requiere un procedimiento de establecimiento de conexión. No hablaremos exactamente sobre cómo se intercambian los datos a través de UDP.

La dirección IP correspondiente al nombre de dominio que nos interesa puede estar en la caché del servidor DNS. Si este no es el caso, se pondrá en contacto con el servidor DNS raíz. El sistema de servidor DNS raíz consta de 13 servidores, de los cuales depende el funcionamiento de todo Internet.

Cabe señalar que el servidor DNS raíz no conoce la correspondencia entre todos los nombres de dominio y direcciones IP existentes en el mundo. Pero servidores similares conocen las direcciones de los servidores DNS de nivel superior para dominios como .com, .it, .pizza, etc.

Al recibir la solicitud, el servidor DNS raíz la redirige al servidor DNS del dominio de nivel superior, al llamado servidor TLD (desde el dominio de nivel superior).

Supongamos que el navegador está buscando la dirección IP para el servidor flaviocopes.com . En cuanto al servidor DNS raíz, el navegador recibirá de él la dirección del servidor TLD para la zona .com. Ahora esta dirección se almacenará en la memoria caché, como resultado, si necesita averiguar la dirección IP de otra URL de la zona .com, no tendrá que volver a ponerse en contacto con el servidor DNS raíz.

Los servidores de TLD tienen direcciones IP de servidores de nombres (Name Server, NS), con la ayuda de los cuales puede encontrar la dirección IP en la URL que tenemos. ¿De dónde obtiene el servidor NS esta información? El hecho es que si compra un dominio, el registrador de dominio envía datos al respecto a los servidores de nombres. Se realiza un procedimiento similar, por ejemplo, al cambiar el alojamiento.

Los servidores en cuestión generalmente son propiedad de proveedores de hosting. Como regla general, para protegerse contra fallas, se crean varios de estos servidores. Por ejemplo, pueden tener estas direcciones:

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

Para averiguar la dirección IP por URL, al final, recurren a dichos servidores. Almacenan los datos reales sobre las direcciones IP.

Ahora, después de que pudimos encontrar la dirección IP detrás de la URL ingresada en la barra de direcciones del navegador, pasamos al siguiente paso de nuestro trabajo.

▍ Establecer una conexión TCP


Tras conocer la dirección IP del servidor, el cliente puede iniciar una conexión TCP con él. En el proceso de establecer una conexión TCP, el cliente y el servidor se transmiten entre sí algunos datos de servicio, después de lo cual pueden intercambiar información. Esto significa que una vez establecida la conexión, el cliente podrá enviar una solicitud al servidor.

▍ Solicitud de envío


Una solicitud es un fragmento de texto estructurado de acuerdo con las reglas del protocolo utilizado. Consta de tres partes:

  • Cadena de consulta
  • Solicitar encabezado.
  • Solicitar cuerpo.

Cadena de consulta


La cadena de consulta es una cadena de texto única que contiene la siguiente información:

  • Método HTTP
  • Dirección de recursos
  • Versión de protocolo

Puede verse, por ejemplo, así:

 GET / HTTP/1.1 

Encabezado de solicitud


El encabezado de la solicitud está representado por un conjunto de : . Hay 2 campos de encabezado obligatorios, uno de los cuales es Host y el segundo es Connection . Los campos restantes son opcionales.

El título puede verse así:

 Host: flaviocopes.com Connection: close 

El campo Host indica el nombre de dominio que le interesa al navegador. El campo Connection , configurado para close , significa que la conexión entre el cliente y el servidor no necesita mantenerse abierta.

Otros encabezados de solicitud de uso común incluyen los siguientes:

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

De hecho, hay muchos más.

El encabezado de la solicitud termina con una cadena vacía.

Cuerpo de solicitud


El cuerpo de la solicitud es opcional; no se utiliza en solicitudes GET. El cuerpo de la solicitud se utiliza en solicitudes POST, así como en otras solicitudes. Puede contener, por ejemplo, datos en formato JSON.

Como ahora estamos hablando de una solicitud GET, el cuerpo de la solicitud estará vacío, no trabajaremos con ella.

▍ respuesta


Después de que el servidor recibe la solicitud enviada por el cliente, la procesa y envía una respuesta al cliente.

La respuesta comienza con un código de estado y un mensaje correspondiente. Si la solicitud es exitosa, el comienzo de la respuesta se verá así:

 200 OK 

Si algo salió mal, puede haber otros códigos. Por ejemplo, lo siguiente:

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

Además, la respuesta contiene una lista de encabezados HTTP y el cuerpo de la respuesta (que, dado que la solicitud es ejecutada por el navegador, será un código HTML).

Análisis HTML


Después de que el navegador recibe la respuesta del servidor, cuyo cuerpo contiene código HTML, comienza a analizarlo, repitiendo el proceso anterior para cada recurso que se necesita para formar la página. Dichos recursos incluyen, por ejemplo, los siguientes:

  • Archivos CSS
  • Imágenes
  • Icono de página web (favicon).
  • Archivos JavaScript

La forma exacta en que el navegador muestra la página no se aplica a nuestra conversación. Lo principal que nos interesa aquí es que el proceso anterior de solicitud y recepción de datos se utiliza no solo para el código HTML, sino también para cualquier otro objeto transferido desde el servidor al navegador utilizando el protocolo HTTP.

Acerca de crear un servidor simple usando Node.js


Ahora, después de examinar el proceso de interacción entre el navegador y el servidor, puede echar un vistazo a la sección de la aplicación First Node.js de la primera parte de esta serie de materiales, en la que describimos el código para un servidor simple.

Hacer solicitudes HTTP con Node.js


Para realizar solicitudes HTTP usando Node.js, se usa el módulo apropiado. Los siguientes ejemplos usan el módulo https . El hecho es que, en las condiciones modernas, siempre que sea posible, es necesario utilizar el protocolo HTTPS.

▍ Ejecutando solicitudes GET


Aquí hay un ejemplo de ejecución de una solicitud GET usando 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() 

Execution Ejecución de solicitud POST


Aquí se explica cómo realizar una solicitud POST desde 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 y BORRAR consultas


La ejecución de tales solicitudes tiene el mismo aspecto que la ejecución de solicitudes POST. La principal diferencia, además del contenido semántico de tales operaciones, es el valor de la propiedad del method del objeto de options .

▍ Realizar solicitudes HTTP en Node.js utilizando la biblioteca Axios


Axios es una biblioteca JavaScript muy popular que funciona tanto en el navegador (esto incluye todos los navegadores modernos e IE, comenzando con IE8), como en el entorno Node.js, que puede usarse para realizar solicitudes HTTP.

Esta biblioteca se basa en promesas, tiene algunas ventajas sobre los mecanismos estándar, en particular, sobre API Fetch. Entre sus ventajas están las siguientes:

  • Soporte para navegadores antiguos (necesita un polyfill para usar Fetch).
  • Posibilidad de interrumpir solicitudes.
  • Soporte para establecer tiempos de espera para solicitudes.
  • Protección incorporada contra ataques CSRF.
  • Soporte para cargar datos con el suministro de información sobre el progreso de este proceso.
  • Soporte para conversión de datos JSON.
  • Empleos en Node.js

Instalación


Puede usar npm para instalar Axios:

 npm install axios 

El mismo efecto se puede lograr con hilo:

 yarn add axios 

Puede conectar la biblioteca a la página usando unpkg.com :

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

API Axios


Puede realizar una solicitud HTTP utilizando el objeto axios :

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

Pero generalmente es más conveniente usar métodos especiales:

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

Esto es similar a cómo jQuery usa $.get() y $.post() lugar de $.ajax() $.post() .

Axios ofrece métodos separados para ejecutar otros tipos de solicitudes HTTP, que no son tan populares como GET y POST, pero que aún se utilizan:

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

La biblioteca tiene un método para ejecutar una solicitud diseñada para recibir solo encabezados HTTP, sin el cuerpo de respuesta:

  • axios.head()

OBTENER solicitudes


Axios es conveniente de usar usando la sintaxis moderna async / await. El siguiente ejemplo de código, diseñado para Node.js, usa la biblioteca para cargar una lista de razas de perros desde la API de perros . Aquí se aplica el método axios.get() y se cuentan las rocas:

 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() 

Lo mismo se puede reescribir sin usar async / await, aplicando promesas:

 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() 

Usar parámetros en solicitudes GET


Una solicitud GET puede contener parámetros que se ven así en una URL:

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

Al usar Axios, se puede hacer una consulta de este tipo de la siguiente manera:

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

Se puede lograr el mismo efecto configurando la propiedad params en un objeto con parámetros:

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

Solicitudes POST


La ejecución de solicitudes POST es muy similar a la ejecución de solicitudes GET, pero aquí, en lugar del método axios.post() , se utiliza el método axios.post() :

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

Como segundo argumento, el método post acepta un objeto con parámetros de solicitud:

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

Uso del protocolo WebSocket en Node.js


WebSocket es una alternativa a HTTP, se puede usar para organizar el intercambio de datos en aplicaciones web. Este protocolo le permite crear canales de comunicación bidireccionales de larga duración entre el cliente y el servidor. Una vez establecida la conexión, el canal de comunicación permanece abierto, lo que pone la aplicación a disposición de una conexión muy rápida, caracterizada por bajas latencias y una pequeña carga adicional en el sistema.

El protocolo WebSocket es compatible con todos los navegadores modernos.

▍ Diferencias HTTP


HTTP y WebSocket son protocolos muy diferentes que utilizan diferentes enfoques para intercambiar datos. HTTP se basa en el modelo de "solicitud-respuesta": el servidor envía algunos datos al cliente después de que se solicite. En el caso de WebSocket, todo está organizado de manera diferente. A saber:

  • El servidor puede enviar mensajes al cliente por iniciativa propia, sin esperar una solicitud del cliente.
  • El cliente y el servidor pueden intercambiar datos al mismo tiempo.
  • Al transmitir un mensaje, se utiliza una cantidad extremadamente pequeña de datos de servicio. Esto, en particular, conduce a una baja latencia en la transmisión de datos.

El protocolo WebSocket es muy adecuado para las comunicaciones en tiempo real a través de canales que permanecen abiertos durante mucho tiempo. HTTP, a su vez, es excelente para organizar sesiones de comunicación ocasionales iniciadas por el cliente. Al mismo tiempo, debe tenerse en cuenta que, desde el punto de vista de la programación, es mucho más fácil implementar el intercambio de datos utilizando el protocolo HTTP que utilizando el protocolo WebSocket.

▍ Versión protegida del protocolo WebSocket


Existe una versión insegura del protocolo WebSocket (esquema ws:// URI), que se asemeja, en términos de seguridad, al protocolo http:// . Se debe evitar el uso de ws:// , prefiriendo una versión segura del protocolo - wss:// .

▍Creando una conexión WebSocket


Para crear una conexión WebSocket, debe usar el constructor apropiado:

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

Después de establecer una conexión exitosa, se genera el evento open . Puede organizar este evento asignando una función de devolución de llamada a la propiedad onopen del objeto de connection :

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

Para manejar errores, se usa el controlador de eventos onerror :

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

▍Envío de datos al servidor


Después de abrir una conexión WebSocket al servidor, puede enviarle datos. Esto se puede hacer, por ejemplo, en la devolución de onopen abierta:

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

▍ Obtención de datos del servidor


Para recibir los datos enviados mediante el protocolo WebSocket desde el servidor, puede asignar la devolución de onmessage onmessage, que se llamará cuando se reciba el evento del message :

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

▍ Implementación del servidor WebSocket en el entorno Node.js


Para implementar un servidor WebSocket en el entorno Node.js, puede usar la popular biblioteca ws . Lo usaremos para el desarrollo del servidor, pero es adecuado para crear clientes, así como para organizar la interacción entre dos servidores.

Instale esta biblioteca inicializando primero el proyecto:

 yarn init yarn add ws 

El código para el servidor WebSocket que necesitamos escribir es 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!') }) 

Aquí creamos un nuevo servidor que escucha en el puerto estándar 8080 el protocolo WebSocket y describe una devolución de llamada que, cuando se establece la conexión, envía un mensaje ho! al cliente ho! e imprime en la consola un mensaje recibido del cliente.

Aquí hay un ejemplo funcional de un servidor WebSocket, y aquí hay un cliente que puede interactuar con él.

Resumen


Hoy hablamos sobre los mecanismos de red compatibles con la plataforma Node.js, trazando paralelos con mecanismos similares utilizados en los navegadores. Nuestro próximo tema será trabajar con archivos.

Estimados lectores! ¿Utiliza el protocolo WebSocket en sus aplicaciones web, cuyo lado del servidor se creó con Node.js?

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


All Articles