Uso de enlaces asíncronos para el manejo del contexto de solicitudes en Node.js

U

Introducción

Ganchos asíncronos son un módulo principal de Node.js que proporciona una API para realizar un seguimiento de la vida útil de los recursos asincrónicos en una aplicación de Node. Un recurso asincrónico se puede considerar como un objeto que tiene una devolución de llamada asociada.

Los ejemplos incluyen, pero no se limitan a: Promesas, Tiempos de espera, TCPWrap, UDP, etc. Se puede encontrar la lista completa de recursos asincrónicos que podemos rastrear usando esta API aquí.

La función Async Hooks se introdujo en 2017, en la versión 8 de Node.js y aún es experimental. Esto significa que aún se pueden realizar cambios incompatibles con versiones anteriores en versiones futuras de la API. Dicho esto, actualmente no se considera apto para producción.

En este artículo, analizaremos en profundidad los Async Hooks: qué son, por qué son importantes, dónde podemos usarlos y cómo podemos aprovecharlos para un caso de uso particular, es decir, el manejo de contexto de solicitud en un node. js y la aplicación Express.

¿Qué son los Async Hooks?

Como se indicó anteriormente, la clase Async Hooks es un módulo principal de Node.js que proporciona una API para rastrear recursos asincrónicos en su aplicación Node.js. Esto también incluye el seguimiento de los recursos creados por módulos de node nativos como fs y net.

Durante la vida útil de un recurso asincrónico, hay 4 eventos que se activan y podemos rastrear, con Async Hooks. Éstas incluyen:

  • init – Llamado durante la construcción del recurso asincrónico
  • before – Llamado antes de que se llame a la devolución de llamada del recurso
  • after – Llamado después de que se haya invocado la devolución de llamada del recurso
  • destroy – Llamado después de que se destruye el recurso asincrónico
  • promiseResolve – Llamado cuando el resolve() se invoca la función de una Promesa.

A continuación se muestra un fragmento resumido de la API de enlaces asíncronos de la descripción general en la documentación de Node.js:

const async_hooks = require('async_hooks');

const exec_id = async_hooks.executionAsyncId();
const trigger_id = async_hooks.triggerAsyncId();
const asyncHook = async_hooks.createHook({
  init: function (asyncId, type, triggerAsyncId, resource) { },
  before: function (asyncId) { },
  after: function (asyncId) { },
  destroy: function (asyncId) { },
  promiseResolve: function (asyncId) { }
});
asyncHook.enable();
asyncHook.disable();

los executionAsyncId() El método devuelve un identificador del contexto de ejecución actual.

los triggerAsyncId() El método devuelve el identificador del recurso principal que desencadenó la ejecución del recurso asíncrono.

los createHook() El método crea una instancia de enlace asíncrono, tomando los eventos antes mencionados como devoluciones de llamada opcionales.

Para permitir el seguimiento de nuestros recursos, llamamos al enable() método de nuestra instancia de gancho asíncrono que creamos con el createHook() método.

También podemos desactivar el seguimiento llamando al disable() función.

Habiendo visto lo que implica la API Async Hooks, veamos por qué deberíamos usarla.

Cuándo usar Async Hooks

La adición de Async Hooks a la API central ha aprovechado muchas ventajas y casos de uso. Algunos de ellos incluyen:

  • Mejor depuración: mediante el uso de Async Hooks, podemos mejorar y enriquecer los seguimientos de pila de funciones asincrónicas.
  • Potentes capacidades de seguimiento, especialmente cuando se combinan con la API de rendimiento de Node. Además, dado que la API de Async Hooks es nativa, la sobrecarga de rendimiento es mínima.
  • Manejo del contexto de la solicitud web: para capturar la información de una solicitud durante la vida útil de esa solicitud, sin pasar el objeto de la solicitud a todas partes. Usando Async Hooks, esto se puede hacer en cualquier parte del código y podría ser especialmente útil cuando se rastrea el comportamiento de los usuarios en un servidor.

En este artículo, veremos cómo manejar el rastreo de ID de solicitud usando Async Hooks en una aplicación Express.

Uso de enlaces asíncronos para el manejo del contexto de solicitudes

En esta sección, ilustraremos cómo podemos aprovechar los Async Hooks para realizar un seguimiento de ID de solicitud simple en una aplicación Node.js.

Configuración de controladores de contexto de solicitud

Comenzaremos creando un directorio donde residirán los archivos de nuestra aplicación, luego nos moveremos a él:

mkdir async_hooks && cd async_hooks 

A continuación, necesitaremos inicializar nuestra aplicación Node.js en este directorio con npm y configuración predeterminada:

npm init -y

Esto crea una package.json archivo en la raíz del directorio.

A continuación, necesitaremos instalar Express y uuid paquetes como dependencias. Usaremos el uuid package para generar un ID único para cada solicitud entrante.

Finalmente, instalamos el esm module para que las versiones de Node.js inferiores a v14 puedan ejecutar este ejemplo:

npm install express uuid esm --save

A continuación, cree un hooks.js archivo en la raíz del directorio:

touch hooks.js

Este archivo contendrá el código que interactúa con el async_hooks módulo. Exporta dos funciones:

  • Uno que habilita un Async Hook para una solicitud HTTP, realizando un seguimiento de su ID de solicitud dada y cualquier información de solicitud que nos gustaría conservar.
  • El otro devuelve los datos de la solicitud administrados por el gancho dado su ID de gancho asíncrono.

Pongamos eso en código:

require = require('esm')(module);
const asyncHooks = require('async_hooks');
const { v4 } = require('uuid');
const store = new Map();

const asyncHook = asyncHooks.createHook({
    init: (asyncId, _, triggerAsyncId) => {
        if (store.has(triggerAsyncId)) {
            store.set(asyncId, store.get(triggerAsyncId))
        }
    },
    destroy: (asyncId) => {
        if (store.has(asyncId)) {
            store.delete(asyncId);
        }
    }
});

asyncHook.enable();

const createRequestContext = (data, requestId = v4()) => {
    const requestInfo = { requestId, data };
    store.set(asyncHooks.executionAsyncId(), requestInfo);
    return requestInfo;
};

const getRequestContext = () => {
    return store.get(asyncHooks.executionAsyncId());
};

module.exports = { createRequestContext, getRequestContext };

En este fragmento de código, primero requerimos el esm module para proporcionar compatibilidad con versiones anteriores de Node que no tienen soporte nativo para exportaciones de módulos experimentales. Esta función es utilizada internamente por el uuid módulo.

A continuación, también requerimos tanto async_hooks y uuid módulos. Desde el uuid módulo, desestructuramos el v4 , que usaremos más adelante para generar UUID de la versión 4.

A continuación, creamos una tienda que mapeará cada recurso asíncrono a su contexto de solicitud. Para ello, utilizamos un mapa de JavaScript simple.

A continuación, llamamos al createHook() método del async_hooks módulo e implementar el init() y destroy() devoluciones de llamada. En la implementación de nuestro init() devolución de llamada, comprobamos si el triggerAsyncId está presente en la tienda.

Si existe, creamos un mapeo del asyncId a los datos de solicitud almacenados en el triggerAsyncId. De hecho, esto garantiza que almacenemos el mismo objeto de solicitud para los recursos asincrónicos secundarios.

los destroy() La devolución de llamada comprueba si la tienda tiene la asyncId del recurso y lo elimina si es verdadero.

Para usar nuestro gancho, lo habilitamos llamando al enable() método del asyncHook instancia que hemos creado.

A continuación, creamos 2 funciones: createRequestContext() y getRequestContext que usamos para crear y obtener nuestro contexto de solicitud respectivamente.

los createRequestContext() La función recibe los datos de la solicitud y un ID único como argumentos. Luego crea un requestInfo objeto de ambos argumentos e intenta actualizar la tienda con el ID asíncrono del contexto de ejecución actual como clave, y el requestInfo como el valor.

los getRequestContext() La función, por otro lado, comprueba si la tienda contiene un ID correspondiente al ID del contexto de ejecución actual.

Finalmente exportamos ambas funciones usando el module.exports() sintaxis.

Hemos configurado correctamente nuestra funcionalidad de manejo de contexto de solicitud. Procedamos a configurar nuestro Express servidor que recibirá las solicitudes.

Configuración del servidor Express

Habiendo configurado nuestro contexto, ahora procederemos a crear nuestro Express servidor para que podamos capturar solicitudes HTTP. Para hacerlo, cree un server.js archivo en la raíz del directorio de la siguiente manera:

touch server.js

Nuestro servidor aceptará una solicitud HTTP en el puerto 3000. Crea un Async Hook para rastrear cada solicitud llamando al createRequestContext() en una función de middleware: una función que tiene acceso a los objetos de solicitud y respuesta de HTTP. Luego, el servidor envía una respuesta JSON con los datos capturados por el Async Hook.

Dentro de server.js archivo, ingrese el siguiente código:

const express = require('express');
const ah = require('./hooks');
const app = express();
const port = 3000;

app.use((request, response, next) => {
    const data = { headers: request.headers };
    ah.createRequestContext(data);
    next();
});

const requestHandler = (request, response, next) => {
    const reqContext = ah.getRequestContext();
    response.json(reqContext);
    next()
};

app.get("https://Pharos.sh.com/", requestHandler)

app.listen(port, (err) => {
    if (err) {
        return console.error(err);
    }
    console.log(`server is listening on ${port}`);
});

En este código, requerimos express y nuestro hooks módulos como dependencias. Luego creamos un Express aplicación llamando al express() función.

A continuación, configuramos un middleware que desestructura los encabezados de la solicitud y los guarda en una variable llamada data. Luego llama al createRequestContext() función pasando data como argumento. Esto asegura que los encabezados de la solicitud se conservarán durante todo el ciclo de vida de la solicitud con el Async Hook.

Finalmente, llamamos al next() función para ir al siguiente middleware en nuestra canalización de middleware o invocar el siguiente controlador de ruta.

Después de nuestro middleware, escribimos el requestHandler() función que maneja un GET solicitud en el dominio raíz del servidor. Notará que en esta función, podemos tener acceso a nuestro contexto de solicitud a través del getRequestContext() función. Esta función devuelve un objeto que representa los encabezados de solicitud y el ID de solicitud generado y almacenado en el contexto de solicitud.

Luego creamos un punto final simple y adjuntamos nuestro controlador de solicitudes como devolución de llamada.

Finalmente, hacemos que nuestro servidor escuche las conexiones en el puerto 3000 llamando al listen() método de nuestra instancia de aplicación.

Antes de ejecutar el código, abra el package.json archivo en la raíz del directorio y reemplace el test sección del script con esto:

"start": "node server.js"

Hecho esto, podemos ejecutar nuestra aplicación con el siguiente comando:

npm start

Debería recibir una respuesta en su terminal indicando que la aplicación se está ejecutando en el puerto 3000, como se muestra:

> [email protected] start /Users/allanmogusu/Pharos.sh/async-hooks-demo
> node server.js

(node:88410) ExperimentalWarning: Conditional exports is an experimental feature. This feature could change at any time
server is listening on 3000

Con nuestra aplicación ejecutándose, abra una instancia de terminal separada y ejecute lo siguiente curl comando para probar nuestra ruta predeterminada:

curl http://localhost:3000

Esta curl comando hace un GET solicitud a nuestra ruta predeterminada. Debería obtener una respuesta similar a esta:

$ curl http://localhost:3000
{"requestId":"3aad88a6-07bb-41e0-ab5a-fa9d5c0269a7","data":{"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"}}}%

Note que el generado requestId y se devuelven nuestros encabezados de solicitud. La repetición del comando debería generar un nuevo ID de solicitud, ya que realizaremos una nueva solicitud:

$ curl http://localhost:3000
{"requestId":"38da84792-e782-47dc-92b4-691f4285b172","data":{"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"}}}%

La respuesta contiene el ID que generamos para la solicitud y los encabezados que capturamos en la función de middleware. Con Async Hooks, podríamos pasar fácilmente datos de un middleware a otro para la misma solicitud.

Conclusión

Async Hooks proporciona una API para realizar un seguimiento de la vida útil de los recursos asincrónicos en una aplicación Node.js.

En este artículo, hemos analizado brevemente la API Async Hooks, la funcionalidad que proporciona y cómo podemos aprovecharla. Hemos cubierto específicamente un ejemplo básico de cómo podemos usar Async Hooks para manejar y rastrear el contexto de las solicitudes web de manera eficiente y limpia.

Sin embargo, desde la versión 14 de Node.js, la API Async Hooks se envía con almacenamiento local asíncrono, una API que facilita el manejo del contexto de la solicitud en Node.js. Puedes leer más sobre esto aquí. Además, se puede acceder al código de este tutorial. aquí.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad