8 - Servidor web de archivos estáticos html, css, jpg, mp3, mp4, ico etc.


Vimos en el concepto anterior como crear un servidor web solo de páginas HTML. Ahora extenderemos nuestro servidor web de páginas estáticas a otros formatos de archivos.

Un sitio web como sabemos está constituido entre otro por archivos html, js, css, jpg, gif, mp3, mp4, ico etc.

Siempre que devolvemos un recurso a un cliente (normalmente el navegador) en la cabecera de respuesta indicamos el tipo de recurso devuelto:

          respuesta.writeHead(200, {'Content-Type': 'text/html'})
          respuesta.write(contenido)
          respuesta.end()

Debemos inicializar el valor 'Content-Type' con el tipo de recurso a devolver, como en el concepto anterior solo devolvíamos archivos html nos bastaba con indicar siempre el valor: 'text/html'

Protocolo MIME

Los MIME Types son la manera estándar de mandar contenido a través de internet. Especifican a cada archivo con su tipo de contenido.

Según el tipo de archivo que retornamos indicamos un valor distinto a Content-Type. Ejemplos de valores:

Content-type: text/html
Content-type: text/css
Content-type: image/jpg
Content-type: image/x-icon
Content-type: audio/mpeg3
Content-type: video/mp4
etc.

MIME es un acrónimo que significa "Multipurpose Internet Mail Extensions" (Extensiones Multipropósito de Correo de Internet). MIME es un estándar de Internet que amplía el formato de correo electrónico para admitir contenido multimedia, como imágenes, audio, video y otros tipos de datos no textuales, además del texto simple.
El Protocolo MIME se utiliza para especificar el tipo de contenido de un mensaje de correo electrónico o de los datos transmitidos a través de protocolos como HTTP utilizado en navegadores web.

Un listado bastante completo de tipos MIME lo podemos ver en este sitio.

Problema

Confeccionaremos un sitio que contenga una serie de archivos html, css, jpg, mp3, mp4 e ico.

Crearemos un directorio llamado ejercicio10 y dentro de este otro directorio interno llamado static donde dispondremos todos los archivos html,css, jpg, mp3, mp4 e ico.

En el directorio ejercicio10 tipearemos nuestra aplicacion Node.js que tiene por objetivo servir las páginas HTML y otros recursos que pidan los navegadores web.

La aplicación Node.js la llamamos ejercicio10.js

ejercicio10.js
const http = require('node:http')
const fs = require('node:fs')

const mime = {
  'html': 'text/html',
  'css': 'text/css',
  'jpg': 'image/jpg',
  'ico': 'image/x-icon',
  'mp3': 'audio/mpeg3',
  'mp4': 'video/mp4'
}

const servidor = http.createServer((pedido, respuesta) => {
  const url = new URL('http://localhost:8888' + pedido.url)
  let camino = 'static' + url.pathname
  if (camino == 'static/')
    camino = 'static/index.html'
  fs.stat(camino, error => {
    if (!error) {
      fs.readFile(camino, (error, contenido) => {
        if (error) {
          respuesta.writeHead(500, { 'Content-Type': 'text/plain' })
          respuesta.write('Error interno')
          respuesta.end()
        } else {
          const vec = camino.split('.')
          const extension = vec[vec.length - 1]
          const mimearchivo = mime[extension]
          respuesta.writeHead(200, { 'Content-Type': mimearchivo })
          respuesta.write(contenido)
          respuesta.end()
        }
      })
    } else {
      respuesta.writeHead(404, { 'Content-Type': 'text/html' })
      respuesta.write('<!doctype html><html><head></head><body>Recurso inexistente</body></html>')
      respuesta.end()
    }
  })
})

servidor.listen(8888)

console.log('Servidor web iniciado')

Este proyecto lo puede descargar en un zip con todos los archivos js, html, jpg etc. desde este enlace : ejercicio10

Veamos los cambios que debemos hacer con respecto al concepto anterior que servíamos solo archivos HTML.

Primero definimos un objeto literal asociando las distintas extensiones de archivos y su valor MIME:

const mime = {
  'html': 'text/html',
  'css': 'text/css',
  'jpg': 'image/jpg',
  'ico': 'image/x-icon',
  'mp3': 'audio/mpeg3',
  'mp4': 'video/mp4'
}

Todo el resto del código es idéntico al concepto anterior salvo cuando tenemos que retornar el recurso que solicita el navegador:

          const vec = camino.split('.')
          const extension = vec[vec.length - 1]
          const mimearchivo = mime[extension]
          respuesta.writeHead(200, { 'Content-Type': mimearchivo })
          respuesta.write(contenido)
          respuesta.end()

Descomponemos en un vector separando por el punto la constante camino:

          const vec = camino.split('.')

Si tenemos en la constante camino el valor:

/pagina1.html

Luego de ejecutarse el método split pidiendo que separa por el caracter punto tenemos en la constante vec dos elementos:

   vec[0] tiene almacenado  /pagina1
   vec[1] tiene almacenado  html

Sacamos el último elemento del vector que en definitiva almacena la extensión del archivo:

          const extension = vec[vec.length - 1]

Seguidamente rescatamos la propiedad del objeto literal mime:

          const mimearchivo = mime[extension]

Por ejemplo si tenemos en la variable extension el valor 'html' luego en la variable mimearchivo se almacena: 'text/html' que es el valor para dicha propiedad definida en la variable mime.

Ahora llamamos al métdodo writeHead donde retornamos el tipo MIME para dicha extensión:

          respuesta.writeHead(200, { 'Content-Type': mimearchivo })

El resto de este programa no sufre cambios con respecto a devolver siempre archivos HTML.

Luego de iniciar la aplicación desde la consola:

servidor web nodejs multples archivos html, jpg, mp3, mp4, ico etc.

Ya podemos solicitar páginas al servidor que acabamos de inicializar y comprobar que nos retorna los distintos recursos enumerados dentro de cada página (imágenes, audios, videos, hojas de estilo etc.):

servidor web nodejs múltples archivos html, jpg, mp3, mp4, ico etc.

La organización de los archivos en el disco duro es:

directorio aplicacion nodejs

Como vemos en la carpeta ejercicio10 se ubica la aplicación Node.js y la carpeta static con todos los recursos:

servidor web nodejs múltples archivos html, jpg, mp3, mp4, ico etc.

En nuestro ejemplo hemos limitado a servir 6 formatos de archivos distintos:

const mime = {
   'html' : 'text/html',
   'css'  : 'text/css',
   'jpg'  : 'image/jpg',
   'ico'  : 'image/x-icon',
   'mp3'  : 'audio/mpeg3',
   'mp4'  : 'video/mp4'
}

Para servir otros formatos de archivos deberíamos agregar al objeto literal mime los valores respectivos que define el protocolo MIME.

Implementación alternativa empleando el módulo 'fs/promises'

ejercicio10b.js
const http = require('node:http')
const fs = require('node:fs/promises')

const mime = {
  'html': 'text/html',
  'css': 'text/css',
  'jpg': 'image/jpg',
  'ico': 'image/x-icon',
  'mp3': 'audio/mpeg3',
  'mp4': 'video/mp4'
}

const servidor = http.createServer((pedido, respuesta) => {
  const url = new URL('http://localhost:8888' + pedido.url)
  let camino = 'static' + url.pathname
  if (camino == 'static/')
    camino = 'static/index.html'

  fs.stat(camino)
    .then(() => {
      fs.readFile(camino)
        .then(contenido => {
          const vec = camino.split('.')
          const extension = vec[vec.length - 1]
          const mimearchivo = mime[extension]
          respuesta.writeHead(200, { 'Content-Type': mimearchivo })
          respuesta.write(contenido)
          respuesta.end()
        })
        .catch(error => {
          respuesta.writeHead(500, { 'Content-Type': 'text/plain' })
          respuesta.write('Error interno')
          respuesta.end()
        })
    })
    .catch(error => {
      respuesta.writeHead(404, { 'Content-Type': 'text/html' })
      respuesta.end('<h1>Error 404: No existe el recurso solicitado</h1>')
    })
})


servidor.listen(8888)

console.log('Servidor web iniciado')

Como podemos comprobar al utilizar las funciones que retornan promesas nos permiten generar un código mucho más legible.

Retornar