Práctica 1_2 - myTeachingURJC/2018-19-LTAW GitHub Wiki
Sesión Laboratorio 2: Práctica 1-2
- Tiempo: 2h
- Objetivos de la sesión:
- Aprender a leer ficheros con node
- Continuar trabajando en la práctica 1
Contenido
Actividades guiadas
Actividades guiadas por el profesor. Haremos lo siguiente:
Ejemplo 1: Lectura de un fichero de texto
Los ficheros se leen desde los programas de node.js usando el módulo fs
- Crea un fichero de texto: test.txt y guardalo
- Ejecuta este programa en node (fs_test1.js). Simplemente abre el fichero e imprime en pantalla su contenido
var fs = require('fs');
//-- Funcion llamada cuando se ha terminado de leer el fichero
function show_file(err, data) {
console.log(data)
}
//-- Leer el fichero. Al terminar se invoca a la función show_file
fs.readFile('test.txt', 'utf8', show_file);
Esta es la salida del programa:
$ node fs_test1.js
En un laboratorio de la URJC, de cucyo nombre no quiero acordarme...
(contenido del fichero de prueba)
$
El fichero se lee con la función readFile(). Se le pasan como argumentos el nombre del fichero a leer y opcionalmente la codificación. Para ficheros de texto usamos UTF8. El último parámetro es la función que se llamará una vez que se ha terminado de leer el fichero
RECORDATORIO: Javascript es un lenguaje orientado a eventos. El código de los programas javascript lo forman funciones de retrollamda (callbacks) que se invocan cuando ocurre determinado evento. En este ejemplo, el evento es el fin de la lectura del fichero, y la función de retrollamada es la que hemos llamado show_file()
El módulo fs invoca a esta función y le pasa dos parámetros:
- Err: es un objecto que tiene información sobre el error producido. Cuando es NULL significa que NO hay error
- Data: Los datos que se han leido del fichero. Si le hemos indicado el parámetro "UTF8", estos datos serán una cadena (string)
Ejemplo 2: ¡Javascript es un lenguaje orientado a eventos!
No hay que perder de vista que javascript es un lenguaje orientado a eventos. ¿Qué ocurre si modificamos el programa anterior de la siguiente manera? ¿En qué orden aparecerán los mensajes en la consola?
var fs = require('fs');
console.log("Este mensaje está AL COMIENZO del código")
//-- Leer el fichero. Al terminar se invoca a la función show_file
fs.readFile('test.txt', 'utf8', show_file);
//-- Funcion llamada cuando se ha terminado de leer el fichero
function show_file(err, data) {
console.log("---> Comienzo del fichero leido")
console.log(data)
console.log("---> Final del fichero")
}
console.log("Este mensaje está al FINAL del código")
- Ejecutar el código y probarlo. La salida es así:
$ node fs_test2.js
Este mensaje está AL COMIENZO del código
Este mensaje está al FINAL del código
---> Comienzo del fichero leido
En un laboratorio de la URJC, de cucyo nombre no quiero acordarme...
(contenido del fichero de prueba)
---> Final del fichero
$
¿¿¿COMO???. El funcionamiento cobra sentido si se tienen en cuenta los eventos. Al arrancar el programa se importa la biblioteca fs y se imprimer el primero mensaje. A continuación se invoca a la función readFile() para leer el fichero. Pero leer el fichero requiere algo de tiempo. El módulo fs no espera a que se lea el fichero, sino que se deja configurada la llamada a la función show_file(). Cuando se termine de leer el fichero se llamará a esta función.
Mientras tanto, nodejs sigue ejecutando el resto de instrucciones. La siguiente es la declaración de show_file(), que no se ejecuta. Y se llega a la última instrucción que imprime el mensaje de que se ha llegado al final del código.
Por ello, los dos primeros mensajes que aparecen son:
Este mensaje está AL COMIENZO del código
Este mensaje está al FINAL del código
Cuando node llega al final del código no se detiene porque fs.readFile() se está ejecutando (está leyendo el fichero). Cuando termina, emite el evento de fichero leido e invoca a la función show_file() que muestra el último mensaje
---> Comienzo del fichero leido
En un laboratorio de la URJC, de cucyo nombre no quiero acordarme...
(contenido del fichero de prueba)
---> Final del fichero
Ejemplo 3: Funciones como parámetros
Como la invocación de funciones de retrollamda cuando ocurren eventos es parte central de javascript, la función que emite el evento y la función de retrollamada se agrupan. Así, el ejemplo anterior reescribe así:
var fs = require('fs');
console.log("Este mensaje está AL COMIENZO del código")
//-- Leer el fichero
fs.readFile('test.txt', 'utf8', function (err, data) {
console.log("---> Comienzo del fichero leido")
console.log(data)
console.log("---> Final del fichero")
});
console.log("Este mensaje está al FINAL del código")
En el tercer parámetro de readFile() se ha incluido directamente la función de retrollamada completa. En javascript las funciones son objetos, y se pueden definir directamente en la zona de los prámetros. Tiene la ventaja de que no es necesario ponerle un nombre. La línea se lee así:
Lanzar la lectura del fichero test.txt y seguir ejecutando instrucciones. Cuando el fichero se haya leido, ejecutar las instrucciones que están entre llaves
Por supuesto la salida es exactamente la misma que en el ejemplo anterior
$ node fs_test3.js
Este mensaje está AL COMIENZO del código
Este mensaje está al FINAL del código
---> Comienzo del fichero leido
En un laboratorio de la URJC, de cucyo nombre no quiero acordarme...
(contenido del fichero de prueba)
---> Final del fichero
Ejemplo 4: Operador =>
Se puede simplificar todavía un poco más usando el operador =>, pero el funcionamiento es exactamente igual. Estamo definiendo una función que será ejecutada cuando se termine de leer el fichero. El programa quedaría así (Se han quitado la impresión de los mensajes inicial y final)
var fs = require('fs');
//-- Leer el fichero
fs.readFile('test.txt', 'utf8', (err, data) => {
console.log("---> Comienzo del fichero leido")
console.log(data)
console.log("---> Final del fichero")
});
Ejemplo 5: Generando errores
En los ejemplos anteriores NO estamos procesando errores. Vamos a forzar un error para ver qué ocurre. Intentaremos leer un fichero que NO existe: test2.txt
var fs = require('fs');
//-- Leer el fichero test2.txt, que NO existe
fs.readFile('test2.txt', 'utf8', (err, data) => {
console.log("---> Comienzo del fichero leido")
console.log(data)
console.log("---> Final del fichero")
});
Al ejecutarlo obtenemos esto:
$ node fs_test5.js
---> Comienzo del fichero leido
undefined
---> Final del fichero
A diferencia de otros lenguajes, no aparece ninguna excepción, ni se genera ningún tipo de error en tiempo de ejecución. Lo que ocurre es que se invoca la función de retrollamada con el parámetro data sin definir (porque NO hay datos), y la información sobre el error en el parámetro err
Pero como nuestro programa no lo tiene en cuenta, simplemente imprime el contenido de data, que es indefinido.
Ejemplo 6: Procesando errores
Es muy importante gestionar los errores. Para detectar si hay algún error al leer el fichero, hay que comprobar si el parámetro err está definido. En caso contrario todo habrá ido bien y los datos estarán en el parámetro data
El objeto err tiene la propiedad message que contiene la cadena con el mensaje de error, para tener una pista de qué es lo que lo ha originado
var fs = require('fs');
//-- Leer el fichero
fs.readFile('test2.txt', 'utf8', (err, data) => {
if (err) {
console.log()
console.log("-------> ERROR!!")
console.log(err.message)
console.log()
}
else { //-- Lectura normal, cuando no hay errores
console.log("---> Comienzo del fichero leido")
console.log(data)
console.log("---> Final del fichero")
}
});
Si ejecutamos el programa haciendo que lea un archivo inexistente, obtendremos lo siguiente:
$ node fs_test6.js
-------> ERROR!!
ENOENT: no such file or directory, open 'test2.txt'
$
Actividades guidas: módulo http
Actividades guiadas por el profesor, para entender el Módulo http de node.js
Ejemplo 7: Creando un servidor "nulo"
Vamos a entender mejor el funcionamiento del módulo http, y de los servidores que creamos con él. Emmpezaremos por un servidor todavía más básico: No responde a las peticiones, simplemente nos notifica por la consola cada vez que llega una
var http = require('http');
console.log("Arrancando servidor...")
//-- Configurar el servidor. Cada vez que llegue una peteicion se emite un
//-- evento y se invoa a la funcion server_req
server = http.createServer(server_req)
//-- Funcion de retrollamada de servicio de las peticiones
//-- No se devuelve mensaje, se indica en consola que ha llegado
//-- una peticion
function server_req(req, res) {
console.log("---> Peticion recibida")
}
//-- Queremos que el servidor escuche peticiones en puerto 8080
server.listen(8080);
- Al ejecutarlo obtenemos esta salida:
$ node http-test1.js
Arrancando servidor...
y se queda esperando a que lleguen peticiones. Si nos conectamos a esta URL local desde nuestro navegador, en la consola aparecerá el mensaje de petición recibida:
$ node http-test1.js
Arrancando servidor...
---> Peticion recibida
---> Peticion recibida
---> Peticion recibida
Ejemplo 8: Servidor "nulo" más compacto
Modificamos el ejemplo anterior para que tenga el mismo comportamiento, pero usando una notación más compacta: incorporamos la función de retrollama en el argumento de createServer()
var http = require('http');
console.log("Arrancando servidor...")
//-- Configurar el servidor. Cada vez que llegue una peteicion se emite un
//-- evento y se invoa a la funcion server_req
server = http.createServer((req, res) => {
console.log("---> Peticion recibida")
});
//-- Queremos que el servidor escuche peticiones en puerto 8080
server.listen(8080);
Y todavía se podría compactar más sabiendo que lo que devuelve la función createServer() es un servidor, al que se puede invocar directamente a su método listen()
var http = require('http');
console.log("Arrancando servidor...")
//-- Configurar y lanzar el servidor. Por cada peticion recibida
//-- se imprime un mensaje en la consola
http.createServer((req, res) => {
console.log("---> Peticion recibida")
}).listen(8080);
El funcionamiento es exactamente el mismo que en el ejemplo anterior
Ejemplo 9: Mensaje de petición
Cada vez que se recibe una petición, llega un mensaje de entrada, con los datos de la petición (incoming message). Desde la página con la API del módulo http encontramos más información: Class http_incomingmessage
Modificamos nuestro mini-servidor para procesar el mensaje recibido. Simplemente vamos a algunos campos del objeto req. Por ejemplo su cabecera:
var http = require('http');
console.log("Arrancando servidor...")
//-- Configurar y lanzar el servidor. Por cada peticion recibida
//-- se imprime un mensaje en la consola
http.createServer((req, res) => {
console.log("---> Peticion recibida")
console.log("--> Cabecera de la solicitud: ")
//-- Es un objeto. Esto imprimirá todas sus propiedades
console.log(req.headers)
}).listen(8080);
Por cada respuesta que recibidos, imprimimos el atributo headers, que contiene información sobre la cabecera. Este es un ejemplo de lo recibido:
$ node http-test4.js
Arrancando servidor...
---> Peticion recibida
--> Cabecera de la solicitud:
{ host: 'localhost:8080',
'user-agent':
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0',
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'accept-language': 'en-US,en;q=0.5',
'accept-encoding': 'gzip, deflate',
connection: 'keep-alive',
cookie:
'username-localhost-8888="2|1:0|10:1548063057|23:username-localhost-8888|44:YjJlOWM0OTllN2U1NGU3MTk3MjVjNjUzOGFhMDRlZTU=|db5edc9e0f105df5afd0a3399fcf45d5baa03c08c99f25514d30b8731d3799ba"',
'upgrade-insecure-requests': '1' }
Ejemplo 10: Accediendo a la cabecera
En realidad, el atributo headers es un objecto, que está definido en el formado JSON
Los elementos que no están entre comillas son accesibles colocando un punto y a continuación su nombre. Así, por ejemplo para acceder a la propiedad host, podemos usar: req.headers.host. Para acceder al atributo 'user-agent' usamos: req.headers['user-agent']
var http = require('http');
console.log("Arrancando servidor...")
//-- Configurar y lanzar el servidor. Por cada peticion recibida
//-- se imprime un mensaje en la consola
http.createServer((req, res) => {
console.log("---> Peticion recibida")
console.log("HOST: " + req.headers.host)
console.log("USER AGENT: " + req.headers['user-agent'])
}).listen(8080);
Ejemplo 11: Accediendo al recurso solicitado (URL)
Al introducir la URL se está indicando qué recurso del servidor se quiere. Si no se indica nada, por defecto la URL recibida será "/", indicando que se quiere el fichero raíz. El recurso solicitado se encuentra dentro del mensaje de solicitud, en el atributo url:
var http = require('http');
console.log("Arrancando servidor...")
//-- Configurar y lanzar el servidor. Por cada peticion recibida
//-- se imprime un mensaje en la consola
http.createServer((req, res) => {
console.log("---> Peticion recibida")
console.log("Recurso solicitado (URL): " + req.url)
}).listen(8080);
Lo ejecutamos y solicitmos diferentes recursos. Podemos probar con estas URLs: Raíz, Hola, tienda/producto1.html. En la consola aparecerá lo siguiente:
$ node http-test6.js
Arrancando servidor...
---> Peticion recibida
Recurso solicitado (URL): /
---> Peticion recibida
Recurso solicitado (URL): /hola
---> Peticion recibida
Recurso solicitado (URL): /tienda/producto1.html
Actividades NO guiadas
- Continúa con la creación del servidor web para tu tienda
- Para devolver el recurso solicitado tendrás que acceder al sistema de ficheros, leer los ficheros pedidos y meterlos en el mensaje de respuesta. La API sobre el mensaje de respuesta está en este enlace
Autores
- Jose María Cañas
- Juan González-Gómez (Obijuan)