Skip to content

S3: Node.js. Módulos

Juan Gonzalez-Gomez edited this page Mar 7, 2022 · 15 revisions

Sesión 3: Node.js. Módulos

  • Tiempo: 2h (50 + 50min)
  • Fecha: Martes, 15-Febrero-2022
  • Objetivos de la sesión:
    • Aprender a analizar URLs con el módulo URL de nodejs
    • Analizar mensajes de solicitud y generar respuestas con el módulo http
    • Leer ficheros del ordenador utilizando el módulo fs
    • Entender la diferencia entre programación síncrona y asíncrona

Contenido

Introducción

En Node.js hay una serie de módulos que nos facilitan mucho la tarea de escribir los servidores. Recordamos de la sesión anterior que las tareas que realiza el servidor son:

  • Obtener el nombre del recurso solicitado por el cliente
  • Acceder al recurso
  • Generar el mensaje de respuesta

En esta sesión veremos tres módulos incluidos en nodejs:

  • url: para trabajar con URLs
  • http: para analizar los mensajes y generar las respuestas
  • fs: para acceder a los ficheros del sistema de archivos

Con estos módulos ya se pueden realizar la práctica 1 completa

Módulo URL

El módulo URL contiene utilidades para trabajar con URLs. Para utilizar este módulo hay que importarlo de la siguiente manera:

const url = require('url');

En la variable url tenemos acceso a todos los elementos definidos. Sin embargo, lo que más se usa es la clase URL que es la que me permite crear objetos de tipo URL, con los que trabajar. Accedemos a esa clase de esta forma:

const URL = url.URL;

Otra forma de importar directamente la clase URL es con esta línea:

const URL = require('url').URL;

Y esta es otra forma equivalente. Os pongo todas las formas para que podáis entender el código realizado por otros:

const { URL } = require('url');

Sin embargo, la clase URL se usa tanto que es directamente visible desde nuestros programas sin necesidad de importar nada

Estructura de una URL

Una URL está formada por la unión de diferentes campos. En esta figura se muestra una URL de ejemplo y el nombre que reciben los campos

Se denomina origen a la primera parte de la URL, que comprende el protocolo, el nombre de la máquina y el puerto. A continuación viene el recurso y después el fragmento

El recurso es el identificador que le llega al servidor en la primera línea del mensaje de solicitud del protocolo http. A su vez está formado por diferentes elementos, que deben ser analizador en el servidor

  • Ruta (o pathname): Es la parte comprendida desde el principio del recurso (el carácter / inicial) y el carácter ? (si lo hubiese). Identifica al recurso al que se quiere acceder
  • Búsqueda (Query): Es la cadena que va a continuación del carácter ?. Es opcional, e indica expresiones para que el servidor realice búsquedas. Las búsquedas a su vez están formadas por asignaciones de valores a parámetros separados por el carácter &. En el ejemplo mostrado en la figura se están buscando en la tienda artículos que son pen drives y con un color blanco

La última parte es opcional y se denomina fragmento. Se usa para denotar una parte dentro del recurso principal pedido. Típicamente se usa para referirse a los diferentes apartados de una página web. El fragmento NO es parte del recurso, por lo que NO se le pasa al servidor en el mensaje de solicitud de http. El fragmento sólo lo utiliza el navegador

Ejemplos de URL

Vamos a ver algunos ejemplos URLs que tienen todos estos campos. Las URLS cambian ligeramente cuando las muestra el navegador en la barra superior: Los caracteres unicode se muestran (acentos, letra ñ, etc...) y el protocolo de la parte inicial se elimina

  • Ejemplo 1

Este es un ejemplo de URL que contiene un fragmento. Es el link para acceder a la sección del módulo http de esta página

La URL completa es esta:

https://github.com/myTeachingURJC/2021-2022-LTAW/wiki/S3:-Node.js.-M%C3%B3dulos#m%C3%B3dulo-http
  • Ejemplo 2

Esta otra URL es un enlace a un Vídeo de Youtube

La URL completa es esta

https://www.youtube.com/watch?list=PLmnz0JqIMEzUKrrcKhBNfWbb1Th0o9tET&t=5&v=b2puPzjQ2Bo

El recurso del enlace es /watch, al que se le pasan 3 parámetros en la búsqueda

  • list: Identificador de la lista de reproducción donde está este vídeo
  • t: Tiempo en segundos por donde empezar a reproducirlo
  • v: Identificador del vídeo

La búsqueda son todos los caracteres que hay a continuación del carácter ?. Los parámetros están separados entre ellos por el carácter &

  • Ejemplo 3

Este es un ejemplo de una URL de Amazon: Cubo de Rubik en amazon

La URL completa es muy larga:

https://www.amazon.es/M%C3%A1gico-Velocidad-Originario-Est%C3%A1ndar-Durable/dp/B07F6Y99KJ/ref=sr_1_4?__mk_es_ES=%C3%85M%C3%85%C5%BD%C3%95%C3%91&dchild=1&keywords=rubik&qid=1614534648&sr=8-4

El recurso es Mágico-Velocidad-Originario-Estándar-Durable/dp/B07F6Y99KJ/ref=sr_1_4. Se le pasan 5 parámetros:

  • __mk_es_ES
  • dchild
  • keywords=rubik
  • qid
  • sr

Creando un objeto URL

Los objetos URL los creamos con la instrucción new URL(String), siendo String todos los caracteres de la URL. No hace falta incluir nada explícitamente, ya que la clase URL es visible directamente

//-- Construir un objeto URL
const myURL = new URL('https://sub.example.com:8080/p/a/t/h?query1=string1&query2=string2#hash');

//-- Imprimir el objeto URL para ver todas sus partes
console.log(myURL); 

En ete ejemplo se está construyendo una URL con muchos campos. Un vez creado el objeto myURL se imprime en la consola y esto es lo que aparece:

Métodos de acceso al objeto URL

Una vez que tenemos un objeto url, accedemos a todos sus campos de la manera indicada en este figura

Ejemplo 1: Imprimiendo los campos de una URL

En este ejemplo creamos el objeto myURL a partir de esta URL:

http://localhost:8080/mi_tienda/listados.html?articulo=pendrive&color=blanco#descripcion

Y luego se imprimen todos sus campos

//-- Construir un objeto URL
const myURL = new URL('http://localhost:8080/mi_tienda/listados.html?articulo=pendrive&color=blanco#descripcion');


//-- Imprimir la información de la URL
console.log("  * URL completa (href): " + myURL.href)
console.log("  * Origen: " + myURL.origin);
console.log("    * Protocolo: " + myURL.protocol);
console.log("    * host: " + myURL.hostname);
console.log("    * port: " + myURL.port);
console.log("  * Ruta: " + myURL.pathname);
console.log("  * Busqueda: " + myURL.search);

//-- Recorrer todas las búsquedas
myURL.searchParams.forEach((value, name)=>{
  console.log("      * Parametro: " + name + " = " + value);
});

//-- Imprimir directamente los valores de los parametros
console.log("    * Artículo: " + myURL.searchParams.get('articulo'));
console.log("    * Color: " + myURL.searchParams.get('color'));
console.log("    * Otro: " + myURL.searchParams.get('otro'));

//-- Ultima parte: Fragmento
console.log("  * Fragmento: " + myURL.hash);

Al ejecutarlo obtenemos esto

La información que se imprime en la consola es esta:

* URL completa (href): http://localhost:8080/mi_tienda/listados.html?articulo=pendrive&color=blanco#descripcion
  * Origen: http://localhost:8080
    * Protocolo: http:
    * host: localhost
    * port: 8080
  * Ruta: /mi_tienda/listados.html
  * Busqueda: ?articulo=pendrive&color=blanco
      * Parametro: articulo = pendrive
      * Parametro: color = blanco
    * Artículo: pendrive
    * Color: blanco
    * Otro: null
  * Fragmento: #descripcion

En la parte final, se imprime directamente los valores de los parámetros artículo, color y otro. El parámetro otro NO existe: no se ha proporcionado. Por ello tiene asignado el valor NULL. Esto nos permite comprobar fácilmente si un determinado parámero existe o no

Módulo HTTP

El módulo http de nodejs nos permite implementar clientes y servidores. Contiene dos clases muy importantes para gestionar los mensajes de solicitud y de respusta

Para acceder a todos los elementos del módulo http, utilizamos esta línea al comienzo de nuestro programas en node.js:

const http = require('http');

Mensajes de solicitud

Los mensajes de solicitud son objetos que pertenecen a la clase http.IncomingMessage. Al recibir un mensaje de solicitud, el módulo http nos da acceso al objeto que contiene el mensaje. Tìpicamente este objeto los llamamos req

El acceso a cada parte del mensaje se realiza mediante estos métodos:

  • req.method: El tipo de solicitud: "GET, POST, HEAD..."
  • req.url: El nombre del recurso solicitudad. El símbolo "/" significa el recurso raiz
  • req.httpVersion: Versión del protocolo HTTP usado por el cliente
  • req.headers: Objeto que contiene todas las cabeceras. Se accede al valor de las cabeceras usando su nombre:
    • req.headers[Nombre];

Los datos del cuerpo llegan a través de un stream de entrada. Los streams son objetos de node.js que nos permite enviar y recibir datos.

El evento 'data' se activa cuando hay datos pendientes de leer del cuerpo. Cuando ya se han leido dodos los datos del cuerpo, o bien se trata de un cuerpo sin datos, se activa el evento 'end'

Utilizaremos la funciones de retrollamadas que se ejecutarán, si es necesario, cuando ocurran esos eventos:

  • req.on('data', callback1): Se llama a la función de retrollamada cuando hay datos disponibles en el cuerpo para ser leidos
  • req.on('end', callback2): Se llama a la función de retrollamada cuando se ha terminado de leer el mensaje de solicitud completo: no hay más datos en el cuerpo

URL del mensaje de solicitud

En el mensaje de solicitud se recibe el recurso al que quiere acceder el cliente. Lo leemos a través de la propiedad req.url. Aunque esta propiedad se llame url, NO es una URL completa, sino sólo la cadena que identifica al recuro

Para convertirla a una URL completa, y poder así utilizar el módulo URL para acceder a todos sus campos, hacemos los siguiente:

const myURL = new URL(req.url, 'http://' + req.headers['host']);

El constructor URL permite crear la url a partir del recurso (primer parámetro) y del origen (segundo parámetro). El origen lo construimos a su vez concatenando http:// con la cabecera host

Ejemplo 2: Servidor que muestra el mensaje de solicitud

Es un ejemplo de un "Happy server" que analiza el mensaje de solitud y nos imprime en la consola toda su información

const http = require('http');

const PUERTO = 8080;

//-- Imprimir informacion sobre el mensaje de solicitud
function print_info_req(req) {

    console.log("");
    console.log("Mensaje de solicitud");
    console.log("====================");
    console.log("Método: " + req.method);
    console.log("Recurso: " + req.url);
    console.log("Version: " + req.httpVersion)
    console.log("Cabeceras: ");

    //-- Recorrer todas las cabeceras disponibles
    //-- imprimiendo su nombre y su valor
    for (hname in req.headers)
      console.log(`  * ${hname}: ${req.headers[hname]}`);

    //-- Construir el objeto url con la url de la solicitud
    const myURL = new URL(req.url, 'http://' + req.headers['host']);
    console.log("URL completa: " + myURL.href);
    console.log("  Ruta: " + myURL.pathname);
}

//-- SERVIDOR: Bucle principal de atención a clientes
const server = http.createServer((req, res) => {

  //-- Petición recibida
  //-- Imprimir información de la petición
  print_info_req(req);

  //-- Si hay datos en el cuerpo, se imprimen
  req.on('data', (cuerpo) => {

    //-- Los datos del cuerpo son caracteres
    req.setEncoding('utf8');

    console.log("Cuerpo: ")
    console.log(` * Tamaño: ${cuerpo.length} bytes`);
    console.log(` * Contenido: ${cuerpo}`);
  });

  //-- Esto solo se ejecuta cuando llega el final del mensaje de solicitud
  req.on('end', ()=> {
    console.log("Fin del mensaje");

    //-- Hayppy server. Generar respuesta
    res.setHeader('Content-Type', 'text/plain');
    res.write("Soy el happy server\n");
    res.end()
  });

});

server.listen(PUERTO);

console.log("Ejemplo 1. Happy Server listo!. Escuchando en puerto: " + PUERTO);

Lo probamos primero con curl. Ejecutamos el servidor lanzamos esta petición:

$ curl -v "127.0.0.1:8080/holaaaa?ad=1&d=5#hola"
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /holaaaa?ad=1&d=5 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Mon, 01 Mar 2021 04:32:02 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
< 
Soy el happy server
* Connection #0 to host 127.0.0.1 left intact
$

En el lado del servidor obtenemos esto:

Aquí vemos la salida con más detalle:

Mensaje de solicitud
====================
Método: GET
Recurso: /holaaaa?ad=1&d=5
Version: 1.1
Cabeceras: 
  * host: 127.0.0.1:8080
  * user-agent: curl/7.68.0
  * accept: */*
URL completa: http://127.0.0.1:8080/holaaaa?ad=1&d=5
  Ruta: /holaaaa
Fin del mensaje

Se ha hecho una petición de tipo GET, y no se ha incluido nada en el cuerpo del mensaje de solicitud. Por eso no se activa el evento data y no se imprime en la consola nada relacionado con el cuerpo. Sí que se ha detectado el evento end

Hacemos ahora una petición en la que se sí metemos un contenido en el cuerpo. Esto se hace añadiendo la opción -d en la llamada a curl:

$ curl -v -H "Content-Type: text/plain" -d "Hola Happy server! Soy tu cliente favorito" "127.0.0.1:8080/hola?ad=1&d=5#hola"
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> POST /hola?ad=1&d=5 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Type: text/plain
> Content-Length: 42
> 
* upload completely sent off: 42 out of 42 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Mon, 01 Mar 2021 04:40:39 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
< 
Soy el happy server
* Connection #0 to host 127.0.0.1 left intact
$

La respuesta del servidor es ahora:

Mensaje de solicitud
====================
Método: POST
Recurso: /hola?ad=1&d=5
Version: 1.1
Cabeceras: 
  * host: 127.0.0.1:8080
  * user-agent: curl/7.68.0
  * accept: */*
  * content-type: text/plain
  * content-length: 42
URL completa: http://127.0.0.1:8080/hola?ad=1&d=5
  Ruta: /hola
Cuerpo: 
 * Tamaño: 42 bytes
 * Contenido: Hola Happy server! Soy tu cliente favorito
Fin del mensaje

Se ha detectado que el mensaje tiene un cuerpo porque se ha recibido el evento data. Tras la lectura del cuerpo se recibe el evento end y se envía la respuesta

Ejemplo 3: ¡Esto es programación asíncrona!

Analiza el siguiente ejemplo:

const http = require('http');

const PUERTO = 8080;

//-- SERVIDOR: Bucle principal de atención a clientes
const server = http.createServer((req, res) => {

  console.log("\nMENSAJE A")

  req.on('data', (cuerpo) => {
    console.log("MENSAJE B")
  });

  req.on('end', ()=> {
    console.log("MENSAJE C");

    //-- Hayppy server. Generar respuesta
    res.setHeader('Content-Type', 'text/plain');
    res.write("Soy el happy server\n");
    res.end()
  });

  console.log("MENSAJE D");

});

console.log("MENSAJE E");
server.listen(PUERTO);
console.log("MENSAJE F");

Estudia el código y trata de averiguar el orden en el que aparecerán los mensajes impresos en la consola en las siguiente situaciones:

  • Situación 1: Se arranca el servidor. Se recibe una solicitud del cliente de tipo GET, sin cuerpo en el mensaje
  • Situación 2: Tras la situación anterior, se recibe ahora una solicitud que tiene información en el cuerpo
Comprobación experimental

Vamos a probar el programa y comprobar experimentalmente lo que ocurre

  • Situación 1: Arrancamos el servidor. Lanzamos la siguiente petición con curl:
curl  127.0.0.1:8080

En la consola del servidor vemos esto:

$ node Ej03-async.js 
MENSAJE E
MENSAJE F

MENSAJE A
MENSAJE D
MENSAJE C

Los mensajes E y F son los primeros que se aparecen al lanzar el servidor, y se imprimen en orden. Primero se crea el servidor con createServer. Después se imprime el mensaje E. A continuación se pone el servidor en modo escucha, esperando a recibir peticiones. En paralelo se escrie el mensaje F

Ahora, cuando llega la petición se llama a la función de retrollamda y se imprime el mensaje A. Se establecen dos funciones de retrollamada adicionales, asociadas a los eventos data y end, pero NO SE EJECUTAN. Node sigue ejecutando instrucciones hasta llegar a la impresión del mensaje D y se queda esperando a que ocurra algún evento

La petición no tiene cuerpo, por lo que se genera el evento end y se imprime el mensaje D

  • Situación 2: Con el servidor ya arrancado, y después de la petición anterior, lanzamos otra pero pasando información en el cuerpo del mensaje:
curl  -d "cuerpo" 127.0.0.1:8080

En la consola del servidor vemos esto:

MENSAJE A
MENSAJE D
MENSAJE B
MENSAJE C

Al llegar la petición se imprime primero el mensaje A y después el mensaje D (igual que antes) y se queda atendiendo a los eventos. El primero que llega es el evento data, ya que el cuerpo tiene datos. Se imprime el mensaje B. A continuación se genera el evento end y se imprime el mensaje C

Mensajes de respuesta

Los mensajes de respuesta son objetos que pertenecen a la clase http.ServerResponse. Al recibir un mensaje de solicitud, el módulo http nos crea un objeto con el mensaje de respuesta vacío, que típicamente llamaremos res.

Añadimos información al mensaje de respuesta con estos métodos:

  • res.statusCode: Establecer el código de respuesta
  • res.statusMessage: Establecer el código de respuesta "en humano"
  • **res.setHeader(nombre,valor): Añadir la cabecera indicada
  • **res.write(dato): Escribir información en el cuerpo del mensaje
  • res.end(): Terminar y enviar el mensaje

Ejemplo 4: Generando mensaje de respuesta

En este ejemplo se muestra un Happy server en el que se +genera el mensaje de respuesta* llamando a los métodos del objeto

const http = require('http');

const PUERTO = 8080;

//-- SERVIDOR: Bucle principal de atención a clientes
const server = http.createServer((req, res) => {

  console.log("Petición recibida")

  //-- Hayppy server. Generar respuesta
  //-- Código: todo ok
  res.statusCode = 200;
  res.statusMessage = "OK :-)";
  res.setHeader('Content-Type', 'text/plain');
  res.write("Soy el happy server\n");
  res.end()

});

server.listen(PUERTO);

console.log("Ejemplo 4. Happy Server listo!. Escuchando en puerto: " + PUERTO);

Lanzamos la petición con curl y obtenemos esto:

$ curl  -v 127.0.0.1:8080
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK :-)
< Content-Type: text/plain
< Date: Mon, 01 Mar 2021 05:39:25 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
< 
Soy el happy server
* Connection #0 to host 127.0.0.1 left intact

Ejemplo 5: Angry server

Es el contrario del Happy server. Responde siempre con un mensaje de error ante cualquier petición

const http = require('http');

const PUERTO = 8080;

//-- SERVIDOR: Bucle principal de atención a clientes
const server = http.createServer((req, res) => {

  console.log("Petición recibida")

  //-- Generar respuesta
  //-- Código: Error. No encontrado
  res.statusCode = 404;
  res.statusMessage = "Not Found :-(";
  res.setHeader('Content-Type', 'text/plain');
  res.write("Soy el ANGRY Server\n");
  res.end()

});

server.listen(PUERTO);

console.log("Ejemplo 5. Angry server!. Escuchando en puerto: " + PUERTO);

El navegador no hace nada diferente. Recibe el código de respuesta y muestra la información que le envía el servidor. Cada servicio web suele tener su própia página de error. Si nos conectamos al Angry server desde la herramienta Network vemos que efectivamente las peticiones realizadas fallan:

Cada servicio web suele tener su própia página de error. Por ejemplo si nos conectamos a github a esta URL:

https://github.com/trolface

nos sale una página de error como esta

Más ejemplos de servidores

Estos son algunos ejemplos de servidores para practicar

Ejemplo 6: Happy server en HTML

Este servidor es un Happy server pero que en vez de devolver siempre una cadena de texto, devuelve una cadena en formato HTML. Esta cadena la dejamos definida dentro del propio código, en la variable constante pagina

const http = require('http');

const PUERTO = 8080;

//-- Texto HTML
const pagina = `

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>¡Happy Server!</title>
</head>
<body style="background-color: lightblue">
    <h1 style="color: green">HAPPY SERVER!!!</h1>
</body>
</html>
`

const server = http.createServer((req, res)=>{
    console.log("Petición recibida!");

    res.statusCode = 200;
    res.statusMessage = "OK";
    res.setHeader('Content-Type','text/html');
    res.write(pagina);
    res.end();
});

server.listen(PUERTO);

console.log("Ejemplo 6. Happy Server HTML!. Escuchando en puerto: " + PUERTO);

Si lo probamos desde el navegador veremos que nos aparece una pantalla como esta:

Y si lo probamos con Curl vemos el texto HTML en la consola:

$ curl  127.0.0.1:8080

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>¡Happy Server!</title>
</head>
<body style="background-color: lightblue">
    <h1 style="color: green">HAPPY SERVER!!!</h1>
</body>
</html>

Y por supuesto lo podemos probar desde nuestro teléfono móvil. Veremos una cosa así:

Ejemplo 7: Página principal y página de error

El servidor de este ejemplo ya no es un Happy server. Su comportamiento todavía es muy básico, pero depende de lo que le solicite el cliente. Si se le solicita el recurso raiz / devuelve la página princpal, sin embargo si se solicita cualquier otro recurso devuelve una página de error

Ambas páginas son cadenas en html que están definidas dentro del propio servidor (no se accede al sistema de ficheros)

const http = require('http');

const PUERTO = 8080;

//-- Texto HTML de la página principal
const pagina_main = `

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mi tienda</title>
</head>
<body style="background-color: lightblue">
    <h1 style="color: green">MI TIENDA</h1>
</body>
</html>
`

//-- Texto HTML de la página de error
const pagina_error = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mi tienda</title>
</head>
<body style="background-color: red">
    <h1 style="color: white">ERROR!!!!</h1>
</body>
</html>
`

const server = http.createServer((req, res)=>{
    console.log("Petición recibida!");

    //-- Valores de la respuesta por defecto
    let code = 200;
    let code_msg = "OK";
    let page = pagina_main;

    //-- Analizar el recurso
    //-- Construir el objeto url con la url de la solicitud
    const url = new URL(req.url, 'http://' + req.headers['host']);
    console.log(url.pathname);

    //-- Cualquier recurso que no sea la página principal
    //-- genera un error
    if (url.pathname != '/') {
        code = 404;
        code_msg = "Not Found";
        page = pagina_error;
    }

    //-- Generar la respusta en función de las variables
    //-- code, code_msg y page
    res.statusCode = code;
    res.statusMessage = code_msg;
    res.setHeader('Content-Type','text/html');
    res.write(page);
    res.end();
});

server.listen(PUERTO);

console.log("Ejemplo 7. Escuchando en puerto: " + PUERTO);

La clave está en obtener el nombre del recurso solicituado. Para ello se crea el objeto url y se analiza el valor de la propia pathname. El mensaje de respuesta se crea a partir de las variables code, code_msg y page, que contienen respetivamente el código de la solicitud, el mensaje asociado a este código y la página

Si se trata de un recurso diferente al raiz se actualizan las variables con la información relativa a la página de error

En esta animación lo vemos en funcionamiento. Primero se accede a la página principal, luego a otro recurso y después otra vez a la página pricipal

Módulo fs

Para acceder desde node.js al sistema de ficheros de nuestro ordenador utilizamos el módulo fs. Para utilizar este módulo lo incluimos de la siguiente manera:

const fs = require('fs');

Ejemplo 8: Lectura síncrona

El acceso al sistema de ficheros se puede hacer de manera síncrona, en la que no se ejecuta la siguiente instrucción hasta que no se haya completado la operación de acceso. Es la manera clásica

La lectura síncrona se hace con la función readFileSync(nombre, codificacion). Como primer argumento se le pasa el nombre del fichero y como segundo la codificación. Para leer ficheros de texto usaremos 'utf8'. Para leeer fichero binarios (como una imagen, por ejemplo) no se indica codificación (por defecto se lee en binario)

En este ejemplo se lee el fichero de texto fich1.txt, que se encuentra en el mismo directorio en el que se está ejecutando el ejemplo. Como la lectura es síncrona, las instrucciones de este programa se ejecutan en orden, y los mensajes en la consola salen en orden también

//-- Importar el módulo FS
const fs = require('fs');

console.log("Lectura síncrona de un fichero");

//-- Realizar la lectura
const data = fs.readFileSync('fich1.txt','utf8');

//-- Esta instrucción se ejecuta una vez terminada
//-- la lectura síncrona
console.log("Lectura completada...")

//-- Mostrar el contenido del fichero
console.log("Contenido del fichero: \n")
console.log(data);

Este es el contenido del fichero fich1.txt:

Esta es la primera linea....
Linea 2
Linea 3
Fin del fichero fich1

Al ejecutarlo obtenemos lo siguiente:

Lectura síncrona de un fichero
Lectura completada...
Contenido del fichero: 

Esta es la primera linea....
Linea 2
Linea 3
Fin del fichero fich1

La lectura del fichero se hace en esta línea:

const data = fs.readFileSync('fich1.txt','utf8');

Se lee todo el fichero de texto y se almacena en la variable data. Como le hemos indicado que la codificación es utf8, la variable devuelta es de tipo string

Ejemplo 9: Lectura asíncrona

Cuando estamos implementando servidores web, utilizaremos la lectura asíncrona para que el rendimiento sea mejor, y nuestro servidor pueda atender más peticiones. La lectura síncrona se hace con la función readFile(), y utiliza los mismos argumentos que la función de lectura síncrona, salvo que se añade al final la función de callback

En este ejemplo se lee el fichero fich1.txt de manera asíncrona:

//-- Importar el módulo FS
const fs = require('fs');

console.log("Lectura asíncrona de un fichero");

//-- Realizar la lectura asíncrona
fs.readFile('fich1.txt','utf8', (err, data) => {

    //-- Cuando los datos están ya disponibles
    //-- los mostramos en la consola
    console.log("Lectura completada...")
    console.log("Contenido del fichero: \n")
    console.log(data);
});

console.log("Esperando al sistema de ficheros...")

Al ejecutarlo obtenemos esto en la consola:

Lectura asíncrona de un fichero
Esperando al sistema de ficheros...
Lectura completada...
Contenido del fichero: 

Esta es la primera linea....
Linea 2
Linea 3
Fin del fichero fich1

Al ser una lectura asíncrona, ahora el orden de los mensajes de la consola cambia. Al arrancar el programa se muestra el mensaje "Lectura asíncrona de un fichero". A continuación se solicita al sistema de ficheros que comience con la lectura de fich1.txt y se le indica que ejecute la función de callback cuando termine. Node sigue ejecutando el programa por lo que imprime el mensaje: "Esperando al sistema de ficheros..."

Cuando el sistema de ficheros ha terminado de leer fich1.txt, llama a la función de callback e imprime el resto de mensajes y el contenido del fichero fich1.txt

Gestión de errores

Los errores se gestionan de diferente manera según que las lecturas sean síncronas o asíncronas

Ejemplo 10: Error en lectura síncrona

Cuando estamos haciendo una lectura síncrona de un fichero, detectamos los errores mediante excepciones. En este ejemplo se produce un error porque estamos usando un fichero que no existe:

const fs = require('fs');

//-- Fichero a leer
const FICHERO = 'fich11.txt';

try {
  const data = fs.readFileSync(FICHERO, 'utf8');
  console.log("Lectura completada...")
  console.log("Contenido del fichero: \n")
  console.log(data);

} catch (err) {
  console.log("Error!!")
  console.log(err.message);
}

Al ejecutarlo aparece esto en la consola:

$ node fs-03-error.js 
Error!!
ENOENT: no such file or directory, open 'fich11.txt'

Ejemplo 11: Error en lectura asíncrona

Cuando la lectura es asíncrona, en la función de callback sabemos si ha ocurrido un error comprobando el primero argumento: err. Si tiene algún valor, es porque se ha producido un error. De lo contrario tenemos disponible los datos en data

const fs = require('fs');

//-- Fichero a leer
const FICHERO = 'fich11.txt';

fs.readFile(FICHERO, 'utf8', (err, data) => {

    if (err) {  //-- Ha ocurrido algun error
      console.log("Error!!")
      console.log(err.message);
    } 
    else {  //-- Lectura normal
        console.log("Lectura completada...")
        console.log("Contenido del fichero: \n")
        console.log(data);
    }
})

El funcionamiento es igual que el ejemplo anterior, salvo que es asíncrono

Autor

Licencia

Enlaces

TEORIA

Soluciones

LABORATORIO

Prácticas y sesiones de laboratorio

Práctica 0: Herramientas

Práctica 1: Node.js: Tienda Básica

Práctica 2: Interacción cliente-servidor. Tienda mejorada

Práctica 3: Websockets: Chat

Practica 4: Electron: Home Chat

  • L11: Home chat (26-Abril-2022)
  • L12: Laboratorio puro. NO hay contenido nuevo (9-Mayo-2022)
  • L13: Laboratorio/Tutorias. No hay contenido nuevo (10-Mayo-2022)

EXAMENES

Curso 2020-2021

Clone this wiki locally