El módulo de solicitud de Node.js

En estos días, nuestras aplicaciones web tienden a tener muchas integraciones con otros servicios, ya sea interactuando con un servicio REST como Twitter o descargando...

En estos días, nuestras aplicaciones web tienden a tener muchas integraciones con otros servicios, ya sea interactuando con un servicio REST como Twitter o descargando imágenes de Flickr. Usar Node/JavaScript es uno de los lenguajes más populares para manejar aplicaciones como esta. De cualquier manera, estarás haciendo muchas solicitudes HTTP, lo que significa que necesitarás un módulo sólido para que escribir el código sea mucho más llevadero.

El módulo solicitud es, con mucho, el paquete Node (no estándar) más popular para realizar solicitudes HTTP. En realidad, es solo un envoltorio alrededor del módulo http integrado de Node, por lo que puede lograr todas las mismas funciones por su cuenta con http , pero request lo hace mucho más fácil.

Realización de solicitudes HTTP

Si bien hay bastantes opciones disponibles para usted en request (muchas de las cuales cubriremos a lo largo de este artículo), también puede ser bastante simple de usar. El ejemplo de "hola mundo" para esta biblioteca es tan fácil como pasar una URL y una devolución de llamada:

1
2
3
4
5
const request = require('request');

request('https://wikihtp.com', function(err, res, body) {
    console.log(body);
});

El código anterior envía una solicitud HTTP GET a wikihtp.com y luego imprime el HTML devuelto en la pantalla. Este tipo de solicitud funciona para cualquier punto final HTTP, ya sea que devuelva HTML, JSON, una imagen o cualquier otra cosa.

El primer argumento de request puede ser una cadena de URL o un objeto de opciones. Estas son algunas de las opciones más comunes que encontrará en sus aplicaciones:

  • url: La URL de destino de la solicitud HTTP
  • método: El método HTTP a utilizar (GET, POST, DELETE, etc)
  • headers: un objeto de encabezados HTTP (clave-valor) que se establecerá en la solicitud
  • form: un objeto que contiene datos de formulario de clave-valor
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const request = require('request');

const options = {
    url: 'https://www.reddit.com/r/funny.json',
    method: 'GET',
    headers: {
        'Accept': 'application/json',
        'Accept-Charset': 'utf-8',
        'User-Agent': 'my-reddit-client'
    }
};

request(options, function(err, res, body) {
    let json = JSON.parse(body);
    console.log(json);
});

Con el objeto “opciones”, esta solicitud utiliza el método GET para recuperar datos JSON directamente de Reddit, que se devuelven como una cadena en el campo “cuerpo”. Desde aquí, puede usar JSON.parse y usar los datos como un objeto JavaScript normal.

Este mismo formato de solicitud se puede utilizar para cualquier tipo de método HTTP, ya sea DELETE, PUT, POST u OPTIONS. Aunque, no todos los métodos se utilizan exactamente igual. Algunos, como el método POST, pueden incluir datos dentro de la solicitud. Hay algunas formas en que se pueden enviar estos datos, algunas de las cuales son:

  • cuerpo: un objeto Buffer, String o Stream (puede ser un objeto si la opción json se establece en true)
  • form: un objeto de datos de par clave-valor (repasaremos esto más adelante)
  • multipart: una matriz de objetos que pueden contener sus propios encabezados y atributos de cuerpo

Cada uno responde a una necesidad diferente (e incluso hay más formas de enviar datos, que se pueden encontrar en esta sección del LÉAME de la solicitud) . Sin embargo, el módulo request contiene algunos métodos convenientes que hacen que sea un poco más fácil trabajar con ellos, así que asegúrese de leer los documentos completos para evitar que su código sea más difícil de lo que debe ser.

Hablando de métodos auxiliares, una forma mucho más sucinta de llamar a los diferentes métodos HTTP es usar los respectivos métodos auxiliares proporcionados. Estos son algunos de los más utilizados:

  • request.get(opciones, devolución de llamada)
  • request.post (opciones, devolución de llamada)
  • request.head(opciones, devolución de llamada)
  • request.delete(opciones, devolución de llamada)

Si bien esto no le ahorrará una tonelada de líneas de código, al menos hará que su código sea un poco más fácil de entender al permitirle simplemente mirar el método que se llama y no tener que analizar todas las diversas opciones para Encuéntralo.

Formularios

Ya sea que esté interactuando con una API REST o creando un bot para rastrear y enviar datos en sitios web, en algún momento deberá enviar datos para un formulario. Como siempre con request, esto se puede hacer de diferentes maneras, dependiendo de sus necesidades.

Para formularios regulares (codificados en URL, con un tipo MÍMICA de application/x-www-form-urlencoded), lo mejor es usar el método de conveniencia .post() con el objeto formulario:

1
2
3
4
5
6
7
8
9
let options = {
    url: 'http://http://mockbin.com/request',
    form: {
        email: '[correo electrónico protegido]',
        password: 'myPassword'
    }
};

request.post(options, callback);

Esto cargará datos como lo haría un formulario HTML, con la única limitación de que no puede cargar archivos de esta manera. Para hacerlo, debe usar la opción formData, que usa la biblioteca formulario-datos debajo.

Usando formData en su lugar, ahora podemos pasar datos de archivo al servidor a través de Buffers, Streams o incluso datos que no son de archivo (como antes) con simples pares clave-valor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let formData = {
    // Pass single file with a key
    profile_pic: fs.createReadStream(__dirname + '/me.jpg'),

    // Pass multiple files in an array
    attachments: [
        fs.readFileSync(__dirname + '/cover-letter.docx'),  // Buffer
        fs.createReadStream(__dirname + '/resume.docx'),    // Stream
    ],

    // Pass extra meta-data with your files
    detailed_file: {
        value: fs.createReadStream(__dirname + '/my-special-file.txt'),
        options: {
            filename: 'data.json',
            contentType: 'application/json'
        }
    },

    // Simple key-value pairs
    username: 'ScottWRobinson'
};

request.post('http://http://mockbin.com/request', {formData: formData}, callback);

Esto enviará sus archivos con un tipo MIME de multipart/form-data, que es una carga de formulario de varias partes.

Si bien esto será más que suficiente para la mayoría de los casos de uso de los usuarios, hay ocasiones en las que necesita un control aún más detallado, como CLRF (nuevas líneas) previo/posterior, fragmentación o especificación de sus propias partes múltiples. Para obtener más información sobre estas opciones adicionales, consulte esta sección de la solicitud README.

Corrientes

Una de las características menos utilizadas en muchos lenguajes de programación, en mi opinión, son las transmisiones. Su utilidad se extiende más allá de las solicitudes de red, pero esto sirve como un ejemplo perfecto de por qué debería usarlos. Para una breve descripción de cómo y por qué debería usarlos, consulte la sección "Streams" de los [Servidores HTTP de nodo para el servicio de archivos estáticos](/servidores-http-de-nodo-para-el- artículo servicio-de-archivos-estaticos/).

En resumen, el uso de secuencias para grandes cantidades de datos (como archivos) puede ayudar a reducir la huella de memoria y el tiempo de respuesta de su aplicación. Para hacer esto más fácil de usar, cada uno de los métodos de solicitud puede canalizar su salida a otra secuencia.

En este ejemplo, descargamos el logotipo de Node.js mediante una solicitud GET y lo transmitimos a un archivo local:

1
2
let fileStream = fs.createWriteStream('node.png');
request('https://nodejs.org/static/images/logos/nodejs-new-white-pantone.png').pipe(fileStream);

Tan pronto como la solicitud HTTP comience a devolver partes de la imagen descargada, 'canalizará' esos datos directamente al archivo 'node.png'.

Descargar un archivo de esta manera también tiene otros beneficios. Las secuencias son excelentes para aplicar transformaciones en los datos a medida que se descargan. Entonces, por ejemplo, supongamos que está descargando una gran cantidad de datos confidenciales con una “solicitud” que debe cifrarse de inmediato. Para hacer esto, puede aplicar una transformación de cifrado canalizando la salida de request a crypto.createCipher:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let url = 'http://example.com/super-sensitive-data.json';
let pwd = new Buffer('myPassword');

let aesTransform = crypto.createCipher('aes-256-cbc', pwd);
let fileStream = fs.createWriteStream('encrypted.json');

request(url)
    .pipe(aesTransform)     // Encrypts with aes256
    .pipe(fileStream)       // Write encrypted data to a file
    .on('finish', function() {
        console.log('Done downloading, encrypting, and saving!');
    });

Es fácil pasar por alto las secuencias, y muchas personas lo hacen cuando están escribiendo código, pero pueden ayudar bastante a su rendimiento, especialmente con una biblioteca como request.

Varios Configuraciones

Las solicitudes HTTP son mucho más que solo especificar una URL y descargar los datos. Para aplicaciones más grandes, y especialmente aquellas que tienen que admitir una gama más amplia de entornos, es posible que sus solicitudes deban manejar bastantes parámetros de configuración, como proxies o certificados de confianza SSL especiales.

Una miscelánea importante. La característica a destacar es el método request.defaults(), que le permite especificar parámetros predeterminados para que no tenga que proporcionarlos para cada solicitud que realice.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let req = request.defaults({
    headers: {
        'x-access-token': '123abc',
        'User-Agent': 'my-reddit-client'
    }
});

req('http://your-api.com', function(err, res, body) {
    console.log(body);
});

Ahora, en el ejemplo anterior, todas las solicitudes realizadas con req siempre tendrán los encabezados x-access-token y User-Agent establecidos. Esto es ideal para configurar encabezados como estos, servidores proxy o configuraciones TLS/SSL.

A lo largo del resto de esta sección, veremos algunas características más comunes que encontrará:

Proxy

Ya sea que su computadora esté detrás de un proxy corporativo o si desea redirigir su tráfico a otro país, es posible que en algún momento deba especificar una dirección de proxy. La forma más sencilla de lograr esto es usar la opción proxy, que toma una dirección a través de la cual se transmite el tráfico:

1
2
3
4
5
6
let options = {
    url: 'https://www.google.com',
    proxy: 'http://myproxy.com'
};

request(options, callback);

El objeto options es una forma de especificar un proxy, pero request también usa las siguientes variables de entorno para configurar una conexión proxy:

  • HTTP_PROXY / http_proxy
  • HTTPS_PROXY / https_proxy
  • NO_PROXY / no_proxy

Esto le da un poco más de control, como establecer qué sitios no deben ser enviados por proxy a través de la variable NO_PROXY.

#####TLS/SSL

A veces, una API necesita tener algo de seguridad adicional y, por lo tanto, requiere un certificado de cliente. En realidad, esto es bastante común con las API corporativas privadas, por lo que vale la pena saber cómo hacerlo.

Otro escenario posible es que desee que sus solicitudes HTTP confíen explícitamente en ciertas autoridades de certificación, que podrían incluir certificados autofirmados por usted o su empresa.

Al igual que con todas las demás configuraciones que hemos visto hasta ahora, estas se establecen en el objeto options:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const fs = require('fs');
const request = require('request');

let myCertFile = fs.readFileSync(__dirname + '/ssl/client.crt')
let myKeyFile = fs.readFileSync(__dirname + '/ssl/client.key')
let myCaFile = fs.readFileSync(__dirname + '/ssl/ca.cert.pem')
 
var options = {
    url: 'https://mockbin.com/request',
    cert: myCertFile,
    key: myKeyFile,
    passphrase: 'myPassword',
    ca: myCaFile
};
 
request.get(options);
Autenticación básica

Aún se puede acceder a los sitios que usan [Autenticación de acceso básico] (https://en.wikipedia.org/wiki/Basic_access_authentication) usando la opción auth:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const request = require('request');
 
var options = {
    url: 'https://mockbin.com/request',
    auth: {
        username: 'ScottWRobinson',
        password: 'myPassword'
    }
};
 
request.get(options);

Esta opción establece uno de los encabezados HTTP como "autorización": "Basic c2NvdHQ6cGFzc3dvcmQh". La cadena 'Básica' en el encabezado 'autorización' declara que se trata de una solicitud de autenticación básica y la cadena alfanumérica que sigue es una codificación RFC2045-MIME (una variante de Base64) de nuestro nombre de usuario y contraseña.

Redireccionamientos

Descubrí que en algunas aplicaciones, como web scraping, hay bastantes casos en los que debe seguir las redirecciones para que su solicitud sea exitosa. Como probablemente hayas adivinado, existe una opción para especificar si se deben seguir los redireccionamientos de forma predeterminada, pero request va un paso más allá y te permitirá proporcionar una función que se puede usar para determinar condicionalmente si se debe seguir el redireccionamiento.

Algunas de las opciones de redirección son:

  • followRedirect: si es true, siga todos los redireccionamientos HTTP 3xx. O envíe una función (res) {} que se usa para determinar si seguir o no la redirección
  • followAllRedirects: siga todos los redireccionamientos HTTP 3xx que no sean GET
  • maxRedirects: el número máximo de veces para seguir los redireccionamientos encadenados (predeterminado en 10)

Conclusión

Sin duda request es un módulo poderoso, y probablemente uno que usará a menudo. Dadas todas las funciones que proporciona, puede actuar como un excelente punto de partida para cualquier cosa, desde un rastreador web hasta una biblioteca de cliente para su API.

Hay bastantes opciones y configuraciones más que se pueden usar con request que las que mostramos aquí, así que asegúrese de consultar la documentación para más detalles. Tenga en cuenta que no todo en el módulo está documentado, por lo que es posible que deba realizar más búsquedas/experimentos para encontrar su respuesta.

¿Has usado request en alguno de tus proyectos? ¿Si es así, cómo? ómo?*