Para los que quieran leer los capítulos anteriores:

  1. Optimización web ( I )
  2. Optimización web ( II ) : CSS Sprites
  3. Optimización web ( III ) : Minimizando archivos

A lo largo de los capítulos anteriores hemos repasado ciertas técnicas para reducir el peso de nuestras páginas, y en alguna medida, para reducir el número de peticiones HTTP. También explicamos brevemente algún que otro truco para reducir el tiempo de renderizado de la página que no estaba íntimamente ligado con su tamaño o el número de peticiones HTTP. A algunos podría parecerles suficiente (espero que a pocos, dentro del mundo de la informática deberíamos ser siempre poco conformistas), pero el abanico de técnicas no se acaba aquí.

Hoy empezamos una subserie de artículos dedicados a los mecanismos de cache a diferentes niveles (dentro del contexto de la creación de páginas web). La palabra cache se usa dentro de muchos contextos y con significados muy diversos, pero todos ligados por una definición algo vaga y generalista, que es la siguiente (sacada de Wikipedia): "Una cache es un conjunto de datos duplicados de otros originales, con la propiedad de que los datos originales son costosos de acceder (comparativamente con el coste de acceder a la copia del dato en cache)".

¿Y en qué contextos usaremos nosotros las caches? Pues en diversos:

  1. Para no tener que solicitar ciertos recursos al servidor cada vez que recarguemos la página (imágenes, archivos CSS, archivos JS, algunas páginas estáticas... (cache del lado del cliente, o de proxies intermedios)
  2. Para no tener que hacer tantas peticiones a las bases de datos de la página cuando un dato es accedido muchas veces en modo lectura y no acostumbra a ser modificado... (cache del lado del servidor)
  3. Para no tener que generar ciertas páginas dinámicamente (con PHP, ASP, Java, Python, etc) cuando éstas cambien muy pocas veces a lo largo del tiempo... (cache del lado del servidor)

Empezaremos con las caches del lado del cliente. Antes de continuar debo indicar que, desgraciadamente, lo que explicaré a partir de ahora no es para nada universal, dependiendo de las tecnologías que uséis os resultará útil o no. Los conceptos generales sí que son válidos para cualquier opción que escojáis, pero las implementaciones no.

Después de asustar a unos pocos debo decir también que lo que sigue será útil para la mayoría (en el capítulo de hoy), trabajaremos con el servidor web Apache y PHP.

Para evitar volver a descargar ciertas imágenes y otros ficheros cada vez que recargamos la página hoy en día podemos encontrar como mínimo dos mecanismos con cierta importancia: las marcas de tiempo y las ETags (entity tags).

Vistas por encima, las marcas de tiempo vienen a ser ciertos datos adjuntos en las cabeceras de los mensajes HTTP que indican cuanto tiempo se podrá guardar un recurso concreto descargado sin tener que volver a solicitarlo. Por ejemplo, si adjuntamos una marca de tiempo que indique que cierta imagen será válida durante un día entero, ésta no volverá a ser descargada durante un día cada vez que se recargue la página.

El problema de las marcas de tiempo es que son algo inflexibles, si por alguna razón queremos modificar un recurso concreto puede darse el caso de que muchos navegantes sigan trabajando con una copia antigua de éste durante un tiempo. Pero tenemos el otro recurso que he mencionado anteriormente, las ETags. Éstas son tambien metainformación adjunta en las cabeceras de los mensajes HTTP que sirven para indicar si un recurso ha sido modificado o no. ¿Cómo se hace? Pues bien, se le adjunta un identificador largo a cada recurso de forma que, si este es modificado, el identificador cambie también. Si el navegador ha obtenido ya un recurso con una ETag, cuando quiera volver a recargar la página no enviará una petición GET (que sirve para pedir un recurso), sino una petición HEAD (que es casi como GET, pero que sirve solo para obtener la metainformación y no el recurso completo). Como respuesta a la petición HEAD le llegará la cabecera HTTP con la metainformación deseada, que nos indicará si el identificador del recurso ha cambiado o no, de manera que en caso de permanecer invariante no tendremos que malgastar recursos de red.

Ahora, habiéndoos explicado la mecánica general, os explicaré una implementación concreta del mecanismo de marcas de tiempo. ¿Cómo, por qué? ¡Pero si las ETags molan mucho más! Sí, pero también son algo más complicadas y creo que se merecen un capítulo a parte.

Para conseguir que nuestro servidor adjunte las marcas de tiempo podemos hacerlo mediante los ficheros .htaccess (recordad, estoy hablando de Httpd Apache y de PHP), la verdad es que es bastante sencillo, aquí os dejo un ejemplo y pasamos a comentarlo:

1
2
3
4
5
6
7
8
9
10
ExpiresActive On
ExpiresDefault A0
<FilesMatch ".(gif|jpg|jpeg|png|swf|ico)$">
  ExpiresDefault A2764800
  Header append Cache-Control "public"
</FilesMatch>
<FilesMatch ".(js|css)$">
  ExpiresDefault A2764800
  Header append Cache-Control "private"
</FilesMatch>

Estas líneas deberían añadirse al fichero .htaccess que tengamos en la carpeta raíz de nuestra página web. La primera línea nos indica que activamos el mecanismo de las marcas de tiempo. La segunda línea nos sirve para decir que los ficheros expirarán a los 0 segundos (notad el 0 del final), es decir, que por lo general no deberían guardarse en la cache del navegador.

Seguidamente nos encotnramos con un bloque algo más compacto, que es muy parecido al que le sigue. En la primera línea indicamos las extensiones de archivo para las cuales estamos definiendo una configuración concreta. En la segunda línea indicamos el número de segundos que deberían mantenerse los ficheros con esa extensión en la cache del navegador antes de solicitar una nueva copia, en nuestro caso concreto ese número de segundos equivale a 32 días. La tercera línea es muy importante, sirve para indicar si los datos pueden ser cacheados por proxies intermedios o solo en nuestro navegador, si utilizamos la directiva "public" los servidores proxy de nuestro ISP podrían guardar una copia del recurso para servirlo más rápidamente a otros clientes, si utilizamos la directiva "private" solo nuestro navegador guardará copia.

Aquí debo hacer una pequeña digresión: Los ISP pueden vulnerar dichas políticas si quieren (otra cosa es que sea legal o no, y es depende de las legislaciones locales), aunque por lo general acostumbran a respetar lo que se indica en las cabeceras (dentro de lo posible). Es sabido por casi todos que actualmente nuestros proveedores de acceso a Internet están casi obligados a espiarnos, guardando datos sobre nuestra navegación durante más de medio año (en Europa), pero eso no hace que esos datos puedan ser enviados a otros clientes como si estuvieran dentro de una cache.

Extra ( Compresión )

Hubiera sido interesante añadir esto en los capítulos dedicados a reducir el "peso" de las páginas web, pero he esperado porque era importante introducir el concepto de cache antes. Utilizamos imágenes comprimidas, reducimos el tamaño de nuestras páginas web.. pero ¿por qué no las comprimimos directamente antes de enviarlas? Bien, es una buena pregunta. De hecho sí que se acostumbra a hacer. Los ficheros de texto tienen la propiedad de ser altamente redundantes, lo que implica que se pueden comprimir muchísimo de forma sencilla, así pues, os explicaré un pequeño truco para enviar ciertas partes de vuestra web comprimidas para salvar ancho de banda y reducir los tiempos de transmisión (aunque con el pequeño coste de tener que comprimir y descomprimir).

Os dejo otro trozo de fichero .htaccess junto con un fichero .php que juntos sirven para conseguir ese objetivo.

.htaccess

1
2
3
4
5
6
7
8
# Activa la compresion en el servidor
php_flag zlib.output_compression On
# Indica el nivel de compresion de 1 a 9 (de menor a mayor compresion)
php_value zlib.output_compression_level 5
# Indica sobre que extensiones se aplica la compresion
AddHandler application/x-httpd-php .css .js
 
php_value auto_prepend_file /ruta/contentHeader.php

Éste trozo de código ya está casi comentado del todo, a excepción de la última línea. Lo que hacen estas líneas es activar la compresión (de nivel 5 en este caso) para los ficheros con extensión CSS y JS, aquí aparecen dos problemas:

  1. Se debe indicar en las cabeceras el MIMETYPE de los ficheros enviados ya que al comprimirlos el navegador puede confundirse.
  2. Al añadir un manejador específico para los ficheros CSS y JS después de las líneas dedicadas a las marcas de tiempo, éstas quedan invalidadas para estas dos extensiones.

Por todo estos puntos utilizamos la línea auto_prepend_file /ruta/contentHeader.php, que nos servirá para añadir al principio del fichero que enviamos la salida generada por contentHeader.php, siendo la salida las cabeceras específicas que necesitamos. Vamos entonces a escribir ese fichero:

1
2
3
4
5
6
7
<?php
$pi=pathinfo($_SERVER['PHP_SELF']);
$ext=$pi['extension'];
if($ext=='css')header("Content-type: text/css");
elseif($ext=='js')header("Content-type: text/javascript");
header('ExpiresDefault "access plus 31 days"');
?>

Este script identifica la extensión del archivo, y en función de esto añade una cabecera u otra indicando el MIMETYPE correspondiente, finalmente añade una cabecera con una marca de tiempo.

Espero que os haya resultado útil, hasta la próxima.