navegadores

Muchas veces nos olvidamos que un pequeño cambio puede cambiar drásticamente el rendimiento de una página. En este caso vamos a hablar un poco de la caché de los navegadores.

El navegador cuando entra en una página, como norma general, se descarga todo su contenido, esto supone que la primera vez que se entra en una página se tenga que transferir mucha información. Imágenes, CSS, Javascripts, etc…

Los navegadores hace muchísimo tiempo que utlizan una caché interna para así acelerar el proceso de carga de las páginas. Ésto hace que si un navegador accede a una página y descarga el archivo styles.css por ejemplo, la próxima vez que cargue la página no lo descargará, a menos que el archivo haya cambiado. Esto, actualmente, se hace comprobando los headers que envia el servidor web del archivo. Si el archivo ha sido modificado, o su tiempo en caché ha expirado, se descarga de nuevo, sino se utiliza el archivo descargado previamente. Gracias a ésta técnica se cargan más rápido las páginas, ya que las imágenes y archivos complementarios solo se descargan una vez, y posteriormente se sirven de la caché local del navegador. Además es una tarea que incorpora automáticamente el servidor web y no debemos preocuparnos por ella.

Ahora bien, existe un pequeño problema con las páginas dinámicas. Éstas páginas se generan en el momento que el usuario acceden a ella, es decir, su fecha de creacion es justamente la fecha actual de la página, y es algo que debe ser así, ya que queremos que al usuario se le muestre el contenido más nuevo de la página, pero en situaciones queremos que no sea así. Podemos ver el caso de las imágenes generadas por PHP (como vimos en entradas anteriores).

Si generamos imágenes desde PHP, estas siempre seran generadas al momento, lo que provoa que, aunque la imagen sea la misma, el navegador no pueda hacer caché ya que las cabeceras del archivo le indican que la imágen se acaba de generar ahora. Por ello vamos a ver como generar las cabeceras necesarias para hacerle entender al navegador que la imágen es nueva. Básicamente seguiremos este diagrama:

Diagrama de flujo caché

Ante todo, debemos mostrar los headers para informarle al navegador que puede hacer caché de la imagen y que periodo de cache le damos:

header("Cache-Control: private, max-age=10800, pre-check=10800");

header("Expires: " . date(DATE_RFC822,strtotime(" 2 day")));

Después  necesitaremos saber si la imagen solicitada existe o no. Para ello simplemente con file_exists obtendremos la respuesta

if (file_exists($imagen)){
    //TO DO...
}
else{
    header("Redirect: image-not-found.jpg");
}

Ahora vamos a ver como comprobar si la imagen existe en la cache del navegador. Para ello vamos a ver como solicita una imagen el navegador cuando la tiene en caché.

Si el navegador no tiene la imagen en caché, simplemente hará una petición GET del archivo, pero si ya la tiene en la caché, hara una petición enviando la información en las cabeceras HTTP de la imagen que está pidiendo. Básicamente nos interesan las siguientes cabeceras:

If-Modified-Since:Sun, 03 Apr 2011 16:50:39 GMT
If-None-Match:"256c42e-580c-715cd5c0"
ETag:"256c42e-580c-715cd5c0"

Esto nos informa, de la fecha de creación de la imágen que está solicitando que tiene en caché, y de un codigo generado para dicha imágen.

Si nosotros queremos realizar las operaciones de caché en nuestro script, deberemos comprobar nosotros cuando la imagen es válida y cuando no. Por lo tanto tendremos que comprobar que la imagen que tenemos fue creada antes de la que tiene el navegador, y que el códdigo coincide. En ese caso le informaremos al navegador que la imagen es la misma, en caso contrario le deberemos proporcionar los nuevos datos y servirle la imágen.

Para generar nuestro código, simplemente cogeremos la fecha de modificación de la imágen y lo pasaremos por un md5. Para hacer esto lo hacemos de la siguiente manera:

$fp = fopen($imagen, "r");
$etag = md5(serialize(fstat($fp)));
fclose($fp);

Ahroa simplemente comprobamos que haya coincidencia en los parámetros y generamos las cabeceras necesarias:

header('Etag: '.$etag);
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= filemtime($imagen) || trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) ) {
    header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($imagen)).' GMT', true, 304);
    exit();
}
else{
    header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($imagen)).' GMT');
    readfile($imagen);
}

Tal y como vemos en el código, comprobamos si estamos recibiendo el parámetro If-Modified-Since, y comprobamos que esta fecha sea mayor que la del archivo, luego comprobamos también que If-None-Match coincida con el archivo, si todo es correcto, enviamos el header Last-Moddified, con un código 304, que significa que el contenido no ha cambiado, y salimos del script. Si no coincide, enviamos el Last-Modified igual pero sin codigo 304 para hacer que el navegador descargue el contenido, y leemos el servimos del fichero.

Por lo tanto el codigo final es el siguiente:

header("Cache-Control: private, max-age=10800, pre-check=10800");header("Expires: " . date(DATE_RFC822,strtotime(" 2 day")));
if (file_exists($imagen)){
$fp = fopen($imagen, "r");
<pre>    $etag = md5(serialize(fstat($fp)));
    fclose($fp);
    header('Etag: '.$etag);
    if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= filemtime($imagen) || trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) ) {
        header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($imagen)).' GMT', true, 304);
        exit();
    }
    else{
        header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($imagen)).' GMT');
        readfile($imagen);
    }</pre>
}
else{
 header("Location: image-not-found.jpg");
}

Por supuesto podemos combinar todo esto con el escalado de imágenes y otras operaciones. Aprovechar la capacidad de la caché de los navegadores, sobre todo en contenidos que se estan sirviendo continuamente en nuestra página, nos hará reducir en gran medida el tiemo de carga de páginas y sobre todo, el ancho de banda de nuestro servidor. En un cliente con esta simple operación, se ha reducido el ancho de banda del servidor en un 65%. Y con ello también se reduce el uso de CPU y de memoria.

Cualquier duda o comentario es bien recibido.

  2 Responses to “Caché en el navegador con imágenes generadas en PHP”

  1. Que Tal, Me ha Encantado este articulo. Deberias seguir produciendo cosas asi. Te paso mi blog para que lo leas y me digas que opines porfa . Hasta la vista jeje, Amelia

  2. Hola mira que en mi caso no tocó hacer todas las validaciones que haces.

    Simplemente colocar los headers:

    header(“Cache-Control: private, max-age=10800, pre-check=10800″);
    header(“Expires: ” . date(DATE_RFC822,strtotime(” 2 day”)));

    En el archivo que negera los thumbnails y las imágenes.

    Gracias por compartir tu información.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

   
© 2017 David Rojo González | Tecnología, programación web, SEO Suffusion theme by Sayontan Sinha