Symfony 1.1, la guía definitiva

12.4. HTTP 1.1 y la cache del lado del cliente

El protocolo HTTP 1.1 define una serie de cabeceras que se pueden utilizar para acelerar una aplicación controlando la cache del navegador del usuario.

La especificación del protocolo HTTP 1.1 publicada por el W3C (World Wide Web Consortium) define todas las cabeceras con gran detalle (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html). Si una acción tiene habilitada la cache y utiliza la opción with_layout, entonces puede hacer uso de los mecanismos que se describen en las siguientes secciones.

Aunque algunos de los navegadores de los usuarios no soporten HTTP 1.1, no existe ningún riesgo en utilizar las opciones de cache de HTTP 1.1. Los navegadores que reciben cabeceras que no entienden, simplemente las ignoran, por lo que se aconseja utilizar los mecanismos de cache de HTTP 1.1.

Además, las cabeceras de HTTP 1.1 también las interpretan los servidores proxy y servidores cache. De esta forma, aunque el navegador del usuario no soporte HTTP 1.1, puede haber un proxy en la ruta de la petición que pueda aprovechar esas características.

12.4.1. Uso de la cabecera ETag para evitar el envío de contenidos no modificados

Cuando se habilita la característica de ETag, el servidor web añade a la respuesta una cabecera especial que contiene una firma de la respuesta enviada.

ETag: "1A2Z3E4R5T6Y7U"

El navegador del usuario almacena esta firma y la envía junto con la petición la próxima vez que el usuario acceda a la misma página. Si la firma demuestra que la página no se ha modificado desde la primera petición, el servidor no envía de nuevo la página de respuesta. En su lugar, envía una cabecera de tipo 304: Not modified. Esta técnica ahorra tiempo de CPU (si se está utilizando la compresión de contenidos) y ancho de banda (ya que la página no se vuelve a enviar) en el servidor, y tiempo de carga (porque la página no se envía de nuevo) en el cliente. En resumen, las páginas que se guardan en la cache con la cabecera ETag son todavía más rápidas de cargar que las páginas que están en la cache y no tienen ETag.

Symfony permite activar la característica ETag para toda la aplicación en el archivo settings.yml. El valor por defecto de la opción ETag se muestra a continuación:

all:
  .settings:
    etag: on

En las acciones que se guardan en la cache junto con el layout, la respuesta se obtiene directamente del directorio cache/, por lo que el proceso es todavía más rápido.

12.4.2. Añadiendo la cabecera Last-Modified para evitar el envío de contenidos todavía válidos

Cuando el servidor envía la respuesta al navegador, puede añadir una cabecera especial que indica cuando se modificaron por última vez los datos contenidos en la página:

Last-Modified: Sat, 23 Nov 2006 13:27:31 GMT

Los navegadores interpretan esta cabecera y la próxima vez que solicitan la misma página, añaden una cabecera If-Modified apropiada:

If-Modified-Since: Sat, 23 Nov 2006   13:27:31 GMT

El servidor entonces puede comparar el valor enviado por el cliente y el valor devuelto por la aplicación. Si coinciden, el servidor devuelve una cabecera304: Not modified, ahorrando ancho de banda y tiempo de CPU, al igual que sucedía con la cabecera ETag.

Symfony permite establecer la cabecera Last-Modified de la misma forma que se establece cualquier otra cabecera. En una acción se puede añadir de la siguiente manera:

$this->getResponse()->setHttpHeader('Last-Modified', $this->getResponse()->getDate($timestamp));

La fecha puede ser la fecha actual o la fecha de la última actualización de los datos de la página, obtenida a partir de la base de datos o del sistema de archivos. El método getDate() del objeto sfResponse convierte un timestamp en una fecha formateada según el estándar requerido por la cabecera Last-Modified (RFC1123).

12.4.3. Añadiendo cabeceras Vary para permitir varias versiones de la página en la cache

Otra de las cabeceras de HTTP 1.1 es Vary, que define los parámetros de los que depende una página y que utilizan los navegadores y los servidores proxy para organizar la cache de las páginas. Si por ejemplo el contenido de una página depende de las cookies, se puede utilizar la siguiente cabecera Vary:

Vary: Cookie

En la mayoría de ocasiones, es difícil habilitar la cache para las acciones porque la página puede variar en función de las cookies, el idioma del usuario o cualquier otro parámetro. Si no es un inconveniente aumentar el tamaño de la cache, se puede utilizar en este caso la cabecera Vary. Además, se puede emplear esta cabecera para toda la aplicación o solo para algunas acciones, definiéndolo en el archivo de configuración cache.yml o mediante el método disponible en sfResponse, como se muestra a continuación:

$this->getResponse()->addVaryHttpHeader('Cookie');
$this->getResponse()->addVaryHttpHeader('User-Agent');
$this->getResponse()->addVaryHttpHeader('Accept-Language');

Symfony guarda en la cache versiones diferentes de la página en función de cada uno de estos parámetros. Aunque el tamaño de la cache aumenta, la ventaja es que cuando el servidor recibe una petición que coincide con estas cabeceras, la respuesta se obtiene directamente de la cache en vez de tener que procesarla. Se trata de un mecanismo muy útil para mejorar el rendimiento de las páginas que solo varían en función de las cabeceras de la petición.

12.4.4. Añadiendo la cabecera Cache-Control para permitir la cache en el lado del cliente

Hasta ahora, aunque se hayan añadido las cabeceras, el navegador sigue enviando peticiones al servidor a pesar de disponer de una versión de la página en su cache. Para evitar estas peticiones, se pueden añadir las cabeceras Cache-Control y Expires a la respuesta. PHP deshabilita por defecto estas cabeceras, pero Symfony puede saltarse este comportamiento para evitar las peticiones innecesarias al servidor.

Como es habitual, esta opción se activa mediante un método del objeto sfResponse. En una acción se puede definir el tiempo máximo que una página debería permanecer en la cache (en segundos):

$this->getResponse()->addCacheControlHttpHeader('max_age=60');

Además, se pueden especificar las condiciones bajo las cuales se guarda la página en la cache, de forma que la cache del proveedor no almacene por ejemplo datos privados (como números de cuenta y contraseñas):

$this->getResponse()->addCacheControlHttpHeader('private=True');

Mediante el uso de las directivas HTTP de Cache-Control es posible controlar los diversos mecanismos de cache existentes entre el servidor y el navegador del cliente. La especificación del W3C de Cache-Control contiene la explicación detallada de todas estas directivas.

Symfony permite añadir otra cabecera llamada Expires:

$this->getResponse()->setHttpHeader('Expires', $this->getResponse()->getDate($timestamp));

Nota La consecuencia más importante de activar el mecanismo Cache-Control es que los logs del servidor no muestran todas las peticiones realizadas por los usuarios, sino solamente las que recibe realmente el servidor. De esta forma, si mejora el rendimiento de un sitio web, su popularidad descenderá de forma aparente en las estadísticas de acceso al sitio.