Cómo escribir un comprobador de DNS con Node.js

Cómo usar el paquete DNS de Node.js para realizar una búsqueda de DNS para un dominio y crear una interfaz de usuario simple para automatizar el proceso de búsqueda.

Primeros pasos

Para este tutorial, vamos a utilizar el marco JavaScript de pila completa de CheatCode, Joystick. Joystick reúne un marco de interfaz de usuario de front-end con un back-end de Node.js para crear aplicaciones.

Para comenzar, querremos instalar Joystick a través de NPM. Asegúrese de estar usando Node.js 16+ antes de instalar para garantizar la compatibilidad (lea este tutorial primero si necesita aprender a instalar Node.js o ejecutar varias versiones en su computadora):

Terminal

npm i -g @joystick.js/cli

Esto instalará Joystick globalmente en su computadora. Una vez instalado, vamos a crear un nuevo proyecto:

Terminal

joystick create app

Después de unos segundos, verá un mensaje desconectado de cd en su nuevo proyecto y ejecute joystick start :

Terminal

cd app && joystick start

Después de esto, su aplicación debería estar ejecutándose y estamos listos para comenzar.

Conectando un captador para recuperar registros DNS

Para comenzar, vamos a utilizar los captadores de Joystick para conectar la recuperación de nuestros registros DNS. En el /api carpeta creada al ejecutar joystick create (en la raíz del proyecto), queremos crear una nueva carpeta dns y dentro de eso, un archivo llamado getters.js :

/api/dns/getters.js

import DNS from 'dns';

const dns = DNS.promises;

export default {
  checkDNS: {
    input: {
      domain: {
        type: "string",
        required: true,
      },
    },
    get: async (input = {}) => {
      return {
        ipAddresses: await dns.resolve(input?.domain, 'A').catch((error) => {
          if (error?.code === 'ENODATA') {
            return [];
          }
        }),
        cname: await dns.resolve(input?.domain, 'CNAME').catch((error) => {
          if (error?.code === 'ENODATA') {
            return [];
          }
        }),
        nameserver: await dns.resolveNs(input?.domain).catch((error) => {
          if (error?.code === 'ENODATA') {
            return [];
          }
        }),
      }
    },
  },
};

Debido a que el código que necesitamos escribir es relativamente simple, hemos generado el archivo completo que necesitamos escribir arriba.

Primero, en la parte superior del archivo, observe que hemos importado DNS del dns paquete. Aquí, dns es un paquete integrado de Node.js para trabajar con registros DNS en una aplicación de Node.js. Hemos usado la versión en mayúsculas para el valor importado aquí porque queremos hacer uso de la versión Promesa de JavaScript, no la devolución de llamada/versión asíncrona predeterminada, de los métodos del paquete.

Para acceder a esto, creamos una nueva variable justo debajo de nuestra importación const dns almacenando el valor DNS.promises (donde el paquete almacena su API basada en Promise).

Moviéndose hacia abajo, desde nuestro archivo exportamos un objeto JavaScript simple y en él, agregamos una propiedad checkDNS establecido en otro objeto. Aquí, checkDNS es el nombre del captador queremos definir. Tenga en cuenta que estamos definiendo esto en el objeto principal que estamos exportando, lo que significa que, si queremos, podemos definir varios captadores en un archivo (veremos cómo se usa esto a continuación).

Centrándose en el valor establecido en checkDNS , en que objeto, tenemos dos propiedades:

  1. input que describe la forma esperada de los valores de entrada que anticipamos que se pasarán a nuestro captador.
  2. get que es la función que maneja o "resuelve" la solicitud del captador recuperando y devolviendo algunos datos.

Para el input , para recuperar la información de DNS, necesitaremos un nombre de dominio (en la documentación del dns paquete, esto se conoce indistintamente como el "nombre de host"). A input , pasamos un objeto que describe la forma del objeto de entrada que esperamos recibir con la solicitud. Aquí, esperamos una propiedad domain y queremos validar que contiene un valor con un tipo de datos JavaScript de string y que el valor está presente (sugerido configurando required a true aquí).

Una vez que nuestra entrada pasa la validación, a continuación, debemos conectar el get() función para realmente responder a las solicitudes a nuestro captador.

Dentro de esa función, como primer argumento, tomamos el input validado que recibimos del cliente (esto no se modifica con respecto a lo que el cliente nos pasa originalmente).

En el interior, configuramos nuestro código para devolver un objeto que describirá los diferentes registros DNS que nos interesan para nuestro dominio, en particular:ipAddresses , cname y nameserver .

Para recuperar cada uno, ponemos el dns paquete a utilizar. Note que delante de la función pasada a get , hemos añadido la palabra clave async . Esto le dice a JavaScript que vamos a usar el await palabra clave dentro de la función a la que se antepone esa palabra clave para "esperar" la respuesta a la función ante la que la colocamos.

Como podemos ver, cada una de nuestras llamadas a dns.<method> están usando el await palabra clave. Esto significa que esperamos que esas funciones devuelvan una promesa de JavaScript de la que queremos esperar la respuesta. Estamos usando dos funciones diferentes de dns aquí:

  1. dns.resolve() que toma un nombre de host como primer argumento y un tipo de registro DNS como segundo argumento. Esto devuelve los valores encontrados para ese tipo de registro como una matriz.
  2. dns.resolveNs() que toma un nombre de host como primer argumento y devuelve una matriz de servidores de nombres DNS asociados con el dominio.

Para recuperar cualquier dirección IP conocida para nuestro dominio, llamamos dns.resolve() pasando el A Tipo de registro DNS. Para recuperar cualquier cname conocido para nuestro nombre de dominio, pasamos el CNAME Tipo de registro DNS.

Finalmente, para recuperar cualquier servidor de nombres conocido para nuestro dominio, llamamos a dns.resolveNs() pasando nuestro nombre de dominio.

Para las tres llamadas, queremos llamar la atención sobre dos cosas. Primero, para nuestro valor de nombre de host estamos pasando input.domain que es el dominio que esperamos recibir de la solicitud a nuestro captador. Luego, al final de cada llamada de función, agregamos un .catch() devolución de llamada que dice "si esta función no recibe ningún dato asociado o tiene un error, haga esto ." Aquí, "esto" está comprobando si error.code el valor se establece en ENODATA cuál es la respuesta que esperamos si el registro DNS dado no se puede recuperar.

Si no puede ser, queremos devolver una matriz vacía (esto evita romper el captador y significa volver a la solicitud de que no se pudieron encontrar datos para ese valor).

¡Eso es todo! A continuación, debemos conectar este captador a nuestra API para asegurarnos de que sea accesible.

/api/index.js

import dnsGetters from './dns/getters';

export default {
  getters: {
    ...dnsGetters,
  },
  setters: {},
};

Aquí, dentro de /api/index.js (un archivo generado para nosotros automáticamente al ejecutar joystick create ) hemos importado el objeto que exportamos desde /api/dns/getters.js como dnsGetters . Abajo, en el objeto que estamos exportando desde esto archivo, tenemos dos propiedades:getters y setters fijados a sus propios objetos. Aquí, definimos todos los getters y setters (los hermanos de getters que nos ayudan a "establecer" o modificar datos en nuestra aplicación).

El patrón que vemos aquí es puramente organizativo. Para mantener nuestro código ordenado, ponemos nuestro dnsGetters en otro archivo y luego use el ... operador de propagación en JavaScript para "desempaquetarlos" en el getters global objeto aquí. Decimos "global" porque lo que definamos aquí se transfiere a Joystick en /index.server.js como el api valor. Joystick usa esto para generar puntos finales HTTP para cada uno de nuestros captadores y definidores.

Si seguimos adelante y guardamos este archivo, para demostrar que, si abrimos un navegador ahora y ejecutamos lo siguiente, deberíamos obtener una respuesta:

http://localhost:2600/api/_getters/checkDNS?input={%22domain%22:%22cheatcode.co%22}

Tenga en cuenta que aquí, nuestro getter se registró como un punto final HTTP en nuestra aplicación automáticamente mediante el joystick en /api/_getters/checkDNS .

A continuación, para terminar, vamos a conectar un componente de la interfaz de usuario para que nos dé un formulario simple para llamar a nuestro captador y mostrar la respuesta en el navegador.

Conectando una ruta para nuestra interfaz de usuario

Antes de pasar al cliente, muy rápido, queremos conectar una ruta para la página que construiremos y crear un componente ficticio.

/index.servidor.js

import node from "@joystick.js/node";
import api from "./api";

node.app({
  api,
  routes: {
    "/": (req, res) => { ... },
    "/dns": (req, res) => {
      res.render("ui/pages/dns/index.js");
    },
    "*": (req, res) => { ... },
  },
});

Aquí, en nuestro index.server.js (este es el archivo responsable de iniciar nuestro servidor que Joystick ejecuta automáticamente para nosotros a través de joystick start ), al routes objeto, hemos añadido una nueva ruta /dns . Detrás de escena, Joystick registrará automáticamente esto como una ruta Express.js (esto es lo que Joystick usa internamente para ejecutar nuestro servidor HTTP), tomando la función que hemos pasado aquí y usándola como el "controlador" para la ruta.

Si alguna vez ha trabajado con Express.js, esto equivale a escribir algo como...

app.get('/dns', (req, res) => {
  res.render('ui/pages/dns/index.js');
});

La única diferencia aquí es que Joystick nos brinda un método estandarizado para definir nuestras rutas y luego genera automáticamente este código para nosotros. Además, en el res objeto que nos pasa Express.js, Joystick define un .render() especial función que está diseñada para representar el componente Joystick en la ruta que pasamos.

Aquí, estamos anticipando un componente Joystick que representa una página en nuestra aplicación en /ui/pages/dns/index.js . Agreguemos un marcador de posición para eso ahora:

/ui/pages/dns/index.js

import ui from '@joystick.js/ui';

const DNS = ui.component({
  render: () => {
    return `
      <div>
        <p>DNS</p>
      </div>
    `;
  },
});

export default DNS;

En ese archivo, importamos ui de @joystick.js/ui que es la parte "front-end" del marco Joystick. En el ui objeto que hemos importado aquí, un método component() se define lo que nos ayuda a definir un componente Joystick.

En ese componente, definimos un render función que devuelve una cadena de marcado HTML que queremos representar nuestro componente. Aquí, para empezar, solo estamos agregando un <div></div> simple etiqueta con un <p></p> etiqueta dentro de él.

Con esto y index.server.js arriba guardado, si visitamos http://localhost:2600/dns en nuestro navegador, deberíamos ver el texto "DNS" impreso en pantalla.

Conexión de una interfaz de usuario para recuperar registros de DNS

Centrándonos de nuevo en el esqueleto del componente que acabamos de agregar, ahora queremos expandirlo para incluir un formulario para ingresar un dominio, una forma de mostrar los resultados de nuestra llamada a checkDNS y todos los adornos necesarios para llamar a nuestro getter a través de nuestra interfaz de usuario.

/ui/pages/dns/index.js

import ui, { get } from '@joystick.js/ui';

const DNS = ui.component({
  state: {
    dns: null,
  },
  events: {
    'submit form': (event, component) => {
      event.preventDefault();

      get('checkDNS', {
        input: {
          domain: event?.target?.domain?.value,
        },
      }).then((response) => {
        component.setState({ dns: response });
      }).catch((error) => {
        console.warn(error);
      });
    },
  },
  css: `
    table {
      width: 50%;
    }

    table tr th,
    table tr td {
      border: 1px solid #eee;
      padding: 10px;
    }

    table tr th {
      text-align: left;
    }
  `,
  render: ({ state, when }) => {
    return `
      <div>
        <form>
          <input type="text" name="domain" placeholder="Type your domain name here..." />
          <button type="submit">Check DNS</button>
        </form>
        ${when(!!state.dns, `
          <table>
            <tbody>
              <tr>
                <th>IP Addresses</th>
                <td>${state.dns?.ipAddresses?.join(', ')}</td>
              </tr>
              <tr>
                <th>CNAMEs</th>
                <td>${state.dns?.cname?.join(', ')}</td>
              </tr>
              <tr>
                <th>Nameservers</th>
                <td>${state.dns?.nameserver?.join(', ')}</td>
              </tr>
            </tbody>
          </table>
        `)}
      </div>
    `;
  },
});

export default DNS;

Esto es todo lo que necesitaremos. Recorrámoslo. Primero, en la parte superior de nuestro componente, agregamos una propiedad state que se establece en un objeto que contiene una propiedad dns establecido en null . Como veremos, aquí es donde esperamos que los datos que recibimos de nuestro getter se almacenen en nuestro componente. Aquí, establecemos ese valor dns a null como predeterminado valor.

A continuación, saltando al render función, utilizando la desestructuración de JavaScript para "arrancar" los valores de la instancia del componente pasado como primer argumento a nuestro render función, tenemos acceso a state (el valor que acabamos de establecer como predeterminado) y when , una "función de renderizado" (un tipo especial de función en un componente Joystick) que nos permite renderizar condicionalmente algo de HTML en nuestro componente cuando se cumple alguna condición.

Si miramos en la cadena HTML devuelta por nuestro render función, podemos ver una expresión ${} siendo utilizado (esto se conoce como "interpolación" en JavaScript y nos permite pasar un valor dinámico dentro de una cadena de JavaScript), con una llamada a when() pasado dentro de ella. A ese when() función, pasamos !!state.dns como primer argumento, seguido de una cadena de HTML como segundo argumento.

Esto dice:"cuando state.dns tiene un valor, representa este HTML". En otras palabras, una vez que hayamos recuperado nuestro dns valor de nuestro getter y lo colocó en el state de nuestro componente , queremos representar el HTML que hemos pasado aquí. Ese HTML, si miramos de cerca, contiene un HTML <table></table> etiqueta que representa una tabla que genera los valores de DNS que hemos obtenido para nuestro dominio. Tenga en cuenta que estamos usando ${} interpolación de nuevo para generar el contenido de state.dns . También a partir de ese valor, tenga en cuenta que estamos anticipando los mismos valores que devolvimos en el objeto de nuestro captador en el servidor:ipAddresses , cname y nameserver .

Debido a que esperamos que cada uno de esos valores contenga una matriz, para que se ajusten a la visualización, usamos JavaScript .join() método para decir "unir todos los valores de esta matriz en una cadena separada por comas".

Para llegar a ese punto, arriba de nuestra llamada a when() , podemos ver un HTML simple <form></form> siendo definido con un <input /> y un <button></button> con un tipo de submit .

/ui/pages/dns/index.js

events: {
  'submit form': (event, component) => {
    event.preventDefault();

    get('checkDNS', {
      input: {
        domain: event?.target?.domain?.value,
      },
    }).then((response) => {
      component.setState({ dns: response });
    }).catch((error) => {
      console.warn(error);
    });
  },
},

Si nos desplazamos hacia arriba en nuestro componente, podemos ver cómo todo esto se junta en el events propiedad. Aquí, definimos detectores de eventos de JavaScript para nuestro componente. Para manejar el paso del dominio que escribimos en nuestra entrada a nuestro captador, definimos un detector de eventos para JavaScript submit evento en nuestro <form></form> etiqueta.

Al definir un detector de eventos en un componente Joystick, definimos el evento que queremos escuchar y el selector que queremos escuchar para ese evento en usando una cadena separada por espacios ('<event> <selector>' ) como una propiedad en nuestro events objeto y luego a esa propiedad, asignamos la función "controlador" para que se llame cuando se detecte ese evento (enviar) en el selector proporcionado (formulario).

Dentro de la función definida aquí, tomamos dos argumentos:event (el evento DOM de JavaScript que se activa) y component (la instancia del componente actual).

Primero, porque estamos manejando un <form></form> evento de envío, queremos llamar a event.preventDefault() para evitar el comportamiento predeterminado de envío de formularios en el navegador (esto desencadena una actualización de la página que queremos evitar).

A continuación, la parte importante, usando el get() función que hemos importado de @joystick.js/ui arriba, llamamos a nuestro checkDNS getter, pasando el valor de domain entrada de nuestro formulario (JavaScript asigna entradas automáticamente por su name atributo al event.target valor para que podamos hacer referencia a ellos directamente).

Finalmente, porque esperamos get() para devolver una promesa de JavaScript, agregamos un .then() y .catch() devolución de llamada a nuestra llamada. Para el .then() devolución de llamada, esperamos recuperar el valor que devolvimos de nuestro getter como response . Usando el segundo argumento pasado a nuestro controlador de eventos component , lo llamamos .setState() método, configurando el dns valor que le dimos por defecto antes como ese response . Si algo sale mal, en nuestro .catch() devolución de llamada, desconectamos el error en nuestra consola.

¡Deberias hacer eso! Ahora, si cargamos nuestra página en el navegador, deberíamos poder escribir un dominio y ver la información de DNS correspondiente.

Terminando

En este tutorial, aprendimos cómo conectar un comprobador de DNS simple utilizando el dns integrado. paquete en Node.js. Aprendimos cómo tomar un nombre de dominio y pasarlo a varias funciones definidas en ese paquete, recuperando las direcciones IP, los servidores de nombres y los cnames para ese dominio. También aprendimos cómo conectar un componente Joystick para que nos proporcione una GUI para recuperar la información de DNS de diferentes dominios.