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

    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铆.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *