Anatomía de una transacción HTTP

articulo extractado de la guia de la pagina oficial de node js


puedes revisar tambien la guia de bextal curso de node js los videos del 1 al 8


El propósito de esta guía es impartir una sólida comprensión del proceso de manejo HTTP de Node.js. Asumiremos que usted sabe, en un sentido general, cómo funcionan las solicitudes HTTP, independientemente del idioma o del entorno de programación. También asumiremos un poco de familiaridad con Node.js EventEmittersStreamsSi no estás muy familiarizado con ellos, vale la pena tomar una rápida lectura a través de los documentos de la API para cada uno de ellos.

Crear el servidor

Cualquier aplicación de servidor web de nodo en algún momento tendrá que crear un objeto de servidor web. Esto se hace utilizando createServer.

const http = require('http');

const server = http.createServer((request, response) => {
  // magic happens here!
});

La función que se pasa a createServerse llama una vez para cada solicitud HTTP que se hace en contra de ese servidor, por lo que se llama el controlador de solicitud. De hecho, el Serverobjeto devuelto por createServeres un EventEmitter, y lo que tenemos aquí es sólo una abreviatura para crear un serverobjeto y luego agregar el oyente más tarde.
const server = http.createServer();
server.on('request', (request, response) => {
  // the same kind of magic happens here!
});
Cuando una solicitud HTTP llega al servidor, el nodo llama a la función del manejador de peticiones con unos pocos objetos prácticos para tratar con la transacción, requestyresponseNos pondremos en contacto con ellos en breve.
Para poder atender realmente las peticiones, el listenmétodo debe ser llamado en el serverobjeto. En la mayoría de los casos, todo lo que tendrá que pasar a listenes el número de puerto que desea que el servidor para escuchar. Hay algunas otras opciones también, así que consulte la referencia de la API .

Método, URL y Encabezados

Al manejar una solicitud, lo primero que probablemente querrá hacer es mirar el método y la URL, para que se puedan tomar las acciones apropiadas. Node hace esto relativamente indoloro poniendo propiedades prácticas sobre el requestobjeto.
const { method, url } = request;
Nota: El requestobjeto es una instancia de IncomingMessage.
El methodaquí siempre será un método HTTP normal / verbo. El urles la dirección URL completa sin el servidor, protocolo o puerto. Para una URL típica, esto significa todo después e incluyendo la tercera barra inclinada.
Los encabezados tampoco están muy lejos. Están en su propio objeto en requestllamadosheaders.
const { headers } = request;
const userAgent = headers['user-agent'];
Es importante tener en cuenta aquí que todos los encabezados están representados sólo en minúsculas, independientemente de cómo el cliente realmente los envió. Esto simplifica la tarea de analizar los encabezados para cualquier propósito.
Si se repiten algunos encabezados, sus valores se sobrescriben o se unen como cadenas separadas por comas, dependiendo del encabezado. En algunos casos, esto puede ser problemático, por rawHeaderslo que también está disponible.

Órgano de solicitud

Al recibir una solicitud POSTPUT, el cuerpo de la solicitud podría ser importante para su solicitud. Llegar a los datos del cuerpo es un poco más complicado que acceder a los encabezados de las solicitudes. El requestobjeto que se pasa a un controlador implementa la ReadableStreaminterfaz. Este flujo se puede escuchar o canalizar en otros lugares como cualquier otro flujo. Podemos obtener los datos directamente del flujo escuchando los flujos 'data''end'eventos.
El fragmento emitido en cada 'data'evento es a BufferSi sabes que va a ser datos de cadena, lo mejor que puedes hacer es recopilar los datos en un array, luego en el 'end', concatenarlo y encadenarlo.
let body = [];
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // at this point, `body` has the entire request body stored in it as a string
});
Nota: Esto puede parecer un poco tedioso, y en muchos casos, lo es. Por suerte, hay módulos como concat-streambodyen el npmque puede ayudar a esconder parte de esta lógica. Es importante tener una buena comprensión de lo que está pasando antes de seguir por ese camino, ¡y es por eso que estás aquí!

Una cosa rápida sobre los errores

Dado que el requestobjeto es a ReadableStream, también es un EventEmittery se comporta como uno cuando ocurre un error.
Un error en el requestflujo se presenta emitiendo un 'error'evento en el flujo. Si usted no tiene un oyente para ese evento, será el error arrojado , lo que podría bloquear el programa de Node.js. Por lo tanto, debe agregar un 'error'oyente a sus flujos de solicitud, incluso si acaba de registrarlo y continuar en su camino. (Aunque probablemente sea mejor enviar algún tipo de respuesta de error HTTP. Más sobre esto más adelante.)
request.on('error', (err) => {
  // This prints the error message and stack trace to `stderr`.
  console.error(err.stack);
});
Hay otras maneras de manejar estos errores , como otras abstracciones y herramientas, pero siempre tenga en cuenta que los errores pueden ocurrir, y usted va a tener que lidiar con ellos.

Lo que tenemos tan lejos

En este punto, hemos cubierto la creación de un servidor, y agarrar el método, la URL, los encabezados y el cuerpo de las solicitudes. Cuando pongamos todo eso, podría parecer algo así:
const http = require('http');

http.createServer((request, response) => {
  const { headers, method, url } = request;
  let body = [];
  request.on('error', (err) => {
    console.error(err);
  }).on('data', (chunk) => {
    body.push(chunk);
  }).on('end', () => {
    body = Buffer.concat(body).toString();
    // At this point, we have the headers, method, url and body, and can now
    // do whatever we need to in order to respond to this request.
  });
}).listen(8080); // Activates this server, listening on port 8080.
Si ejecutamos este ejemplo, podremos recibir solicitudes, pero no responder a ellas. De hecho, si pulsa este ejemplo en un navegador web, su solicitud se agotará, ya que no se devuelve nada al cliente.
Hasta ahora no hemos tocado el responseobjeto en absoluto, que es una instancia de ServerResponse, que es a WritableStreamContiene muchos métodos útiles para enviar datos al cliente. Vamos a cubrir la siguiente.

Código de estado HTTP

Si no te molesta en configurarlo, el código de estado HTTP en una respuesta siempre será 200. Por supuesto, no todas las respuestas HTTP lo garantizan, y en algún momento definitivamente querrás enviar un código de estado diferente. Para ello, puede establecer la statusCodepropiedad.
response.statusCode = 404; // Tell the client that the resource wasn't found.
Hay algunos otros atajos a esto, como veremos pronto.

Configuración de encabezados de respuesta

Los encabezados se establecen a través de un método conveniente llamado setHeader.
response.setHeader('Content-Type', 'application/json');
response.setHeader('X-Powered-By', 'bacon');
Al establecer los encabezados en una respuesta, el caso es insensible en sus nombres. Si se establece un encabezado repetidamente, el último valor que se establece es el valor que se envía.

Envío explícito de datos de encabezado

Los métodos de configuración de los encabezados y el código de estado que ya hemos discutido suponen que está utilizando "encabezados implícitos". Esto significa que usted está contando con un nodo para enviar los encabezados para usted en el momento correcto antes de empezar a enviar datos de cuerpo.
Si lo desea, puede escribir explícitamente los encabezados en el flujo de respuesta. Para ello, hay un método llamado writeHead, que escribe el código de estado y los encabezados de la secuencia.
response.writeHead(200, {
  'Content-Type': 'application/json',
  'X-Powered-By': 'bacon'
});
Una vez que haya configurado los encabezados (implícita o explícitamente), estará listo para comenzar a enviar datos de respuesta.

Órgano de respuesta de envío

Dado que el responseobjeto es a WritableStream, escribir un cuerpo de respuesta al cliente es sólo cuestión de usar los métodos de flujo habituales.
response.write('<html>');
response.write('<body>');
response.write('<h1>Hello, World!</h1>');
response.write('</body>');
response.write('</html>');
response.end();
La endfunción en los flujos también puede tomar en algunos datos opcionales para enviar como el último bit de datos en el flujo, por lo que podemos simplificar el ejemplo anterior como sigue.
response.end('<html><body><h1>Hello, World!</h1></body></html>');
Nota: Es importante establecer el estado y los encabezados antes de comenzar a escribir trozos de datos en el cuerpo. Esto tiene sentido, ya que los encabezados vienen antes que el cuerpo en las respuestas HTTP.

Otra cosa rápida sobre los errores

El responseflujo también puede emitir 'error'eventos, y en algún momento vas a tener que lidiar con eso también. Todos los consejos para requesterrores de flujo siguen siendo válidos aquí.

Ponlo todo junto

Ahora que hemos aprendido a hacer respuestas HTTP, vamos a ponerlo todo junto. Sobre la base del ejemplo anterior, vamos a hacer un servidor que envía de vuelta todos los datos que nos fue enviado por el usuario. Formataremos los datos como JSON usando JSON.stringify.
const http = require('http');

http.createServer((request, response) => {
  const { headers, method, url } = request;
  let body = [];
  request.on('error', (err) => {
    console.error(err);
  }).on('data', (chunk) => {
    body.push(chunk);
  }).on('end', () => {
    body = Buffer.concat(body).toString();
    // BEGINNING OF NEW STUFF

    response.on('error', (err) => {
      console.error(err);
    });

    response.statusCode = 200;
    response.setHeader('Content-Type', 'application/json');
    // Note: the 2 lines above could be replaced with this next one:
    // response.writeHead(200, {'Content-Type': 'application/json'})

    const responseBody = { headers, method, url, body };

    response.write(JSON.stringify(responseBody));
    response.end();
    // Note: the 2 lines above could be replaced with this next one:
    // response.end(JSON.stringify(responseBody))

    // END OF NEW STUFF
  });
}).listen(8080);

Ejemplo del servidor Echo

Vamos a simplificar el ejemplo anterior para hacer un servidor de eco simple, que sólo envía los datos que se reciben en la solicitud de nuevo en la respuesta. Todo lo que necesitamos hacer es tomar los datos de la secuencia de solicitud y escribir esos datos en la secuencia de respuestas, similar a lo que hicimos anteriormente.
const http = require('http');

http.createServer((request, response) => {
  let body = [];
  request.on('data', (chunk) => {
    body.push(chunk);
  }).on('end', () => {
    body = Buffer.concat(body).toString();
    response.end(body);
  });
}).listen(8080);
Ahora vamos a ajustar esto. Solo queremos enviar un eco en las siguientes condiciones:
  • El método de solicitud es GET.
  • La URL es /echo.
En cualquier otro caso, queremos simplemente responder con un 404.
const http = require('http');

http.createServer((request, response) => {
  if (request.method === 'GET' && request.url === '/echo') {
    let body = [];
    request.on('data', (chunk) => {
      body.push(chunk);
    }).on('end', () => {
      body = Buffer.concat(body).toString();
      response.end(body);
    });
  } else {
    response.statusCode = 404;
    response.end();
  }
}).listen(8080);
Nota: Al comprobar la URL de esta manera, estamos haciendo una forma de "enrutamiento". Otras formas de enrutamiento pueden ser tan simples como switchdeclaraciones o tan complejas como marcos completos como expressSi usted está buscando algo que hace enrutamiento y nada más, intente router.
¡Estupendo! Ahora vamos a tomar una puñalada en la simplificación de esto. Recuerde, el requestobjeto es a ReadableStreamy el responseobjeto es a WritableStreamEso significa que podemos usar pipepara dirigir los datos de uno a otro. Eso es exactamente lo que queremos para un servidor de eco!
const http = require('http');

http.createServer((request, response) => {
  if (request.method === 'GET' && request.url === '/echo') {
    request.pipe(response);
  } else {
    response.statusCode = 404;
    response.end();
  }
}).listen(8080);
Yay arroyos!
Todavía no hemos terminado. Como se mencionó varias veces en esta guía, los errores pueden ocurrir y suceden, y necesitamos lidiar con ellos.
Para manejar errores en la secuencia de peticiones, registraremos el error stderry enviaremos un código de estado 400 para indicar a Bad RequestEn una aplicación del mundo real, sin embargo, queremos inspeccionar el error para averiguar cuál sería el código de estado correcto y el mensaje. Como es habitual con los errores, debe consultar laErrordocumentación .
En la respuesta, solo registraremos el error en stdout.
const http = require('http');

http.createServer((request, response) => {
  request.on('error', (err) => {
    console.error(err);
    response.statusCode = 400;
    response.end();
  });
  response.on('error', (err) => {
    console.error(err);
  });
  if (request.method === 'GET' && request.url === '/echo') {
    request.pipe(response);
  } else {
    response.statusCode = 404;
    response.end();
  }
}).listen(8080);
Ya hemos cubierto la mayoría de los aspectos básicos de la gestión de solicitudes HTTP. En este punto, usted debería ser capaz de:
  • Instanciar un servidor HTTP con una función de controlador de solicitud, y tenerlo escuchar en un puerto.
  • Obtener encabezados, URL, método y datos de cuerpo de requestobjetos.
  • Hacer decisiones de enrutamiento basadas en URL y / u otros datos en requestobjetos.
  • Enviar encabezados, códigos de estado HTTP y datos de cuerpo a través de responseobjetos.
  • Pipe datos de requestobjetos y responseobjetos.
  • Maneja los errores de flujo en ambos requesty en los responseflujos.
De estos conceptos básicos, se pueden construir servidores HTTP Node.js para muchos casos de uso típicos. Hay un montón de otras cosas estas API proporcionan, así que asegúrese de leer a través de los documentos de la API para EventEmittersStreamsHTTP.

Comentarios

Entradas populares de este blog

Investigación #1

WebSockets