← Servidor web y API con Node

Sesión 10 · Semana 4

HTML generado en el servidor

Hoy · Hoja de ruta

  1. 1. Aprende: Cuándo conviene generar el HTML en el servidor y cómo se hace sin abrir un agujero.
  2. 2. Haz: Sirve una página de catálogo generada en el servidor.
  3. 3. Comprueba: Un producto con etiquetas en el nombre no rompe ni ejecuta nada.

Antes de empezar · 5 minutos, sin apuntes

  1. En la UD4 el catálogo se pintaba en el navegador. ¿Qué ve alguien con JavaScript desactivado?
  2. ¿Y un buscador que indexa tu web?
  3. ¿Qué pasaría si el nombre de un producto contuviera una etiqueta?

Las dos formas de pintar

Dónde se genera Ventajas Inconvenientes
En el servidor Llega listo, funciona sin JavaScript, se indexa Cada cambio recarga la página
En el cliente Interacción inmediata, menos trabajo del servidor Depende de que el código se ejecute

No hay que elegir una para siempre: lo habitual es que la primera carga llegue hecha del servidor y la interacción se resuelva en el cliente. Es exactamente lo que vas a montar: la página de catálogo llega renderizada, y los filtros siguen funcionando en el navegador como en la UD4.

Generar HTML con plantillas del lenguaje

export function paginaCatalogo(productos) {
  return `<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <title>${escapar("Catálogo")}</title>
  <link rel="stylesheet" href="/css/styles.css">
  <script type="module" src="/js/main.js"></script>
</head>
<body>
  <main>
    <h1>Catálogo</h1>
    <ul class="catalogo" data-js="catalogo">
      ${productos.map(tarjeta).join("")}
    </ul>
  </main>
</body>
</html>`;
}

function tarjeta(producto) {
  return `<li class="producto" data-id="${producto.id}">
    <h3>${escapar(producto.nombre)}</h3>
    <p class="precio">${escapar(formatearPrecio(producto.precio))}</p>
  </li>`;
}

No hace falta instalar un motor de plantillas: las plantillas del lenguaje, las de la UD3, sirven perfectamente para esto. Un motor aporta herencia de plantillas y sintaxis propia; a cambio, una dependencia más y un lenguaje más que aprender.

Escapar no es opcional

const ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };

export function escapar(valor) {
  return String(valor).replace(/[&<>"']/g, (caracter) => ESCAPES[caracter]);
}

Todo dato que entra en el HTML se escapa

Si un producto se llama Teclado <script>…</script> y se inserta sin escapar, ese código se ejecuta en el navegador de quien visite tu página, con sus permisos y sus datos de sesión. Esa vulnerabilidad se denomina cross-site scripting y es la vulnerabilidad más extendida de la web.

Es el mismo problema del innerHTML de la UD4, ahora del lado del servidor y peor: allí afectaba a quien lo escribía, aquí a todo el que visite la página.

Y ojo con el sitio donde insertas: escapar sirve para el contenido y para los atributos entrecomillados. Meter datos de fuera dentro de un bloque de código de la página o en una URL requiere reglas distintas; lo sensato es no hacerlo.

Tarea 10 · La página del servidor

  1. Escribe src/vistas/catalogo.js que genere la página completa.
  2. Escribe y usa la función de escapado en todos los datos.
  3. Sirve la página en GET / desde su router.
  4. Comprueba con «ver código fuente» que el HTML llega hecho.
  5. Añade un producto con etiquetas en el nombre y comprueba que se ve como texto.
  6. Valida el HTML generado en el W3C.
Objetivo mínimoCatálogo generado en el servidor, escapado y válido.
Si lo tienesAñade la página de detalle de un producto, con su 404 propio.
RetoDemuestra el ataque contra una versión sin escapado y explica qué ocurre.

Checkpoint · fin de la sesión 10

  • Generas HTML en el servidor con plantillas del lenguaje.
  • Escapas todo dato antes de insertarlo.
  • El HTML generado es válido y semántico.
  • Explicas qué es el cross-site scripting.
Ver respuestas

1 · Que llega hecha y funciona sin JavaScript, y que los buscadores la leen.

2 · Sustituir los caracteres con significado en HTML por sus entidades.

3 · Que se ejecute código ajeno en el navegador de cualquier visitante.