Si bien todos los navegadores tienen mecanismos de almacenamiento en caché, estos sistemas no son fiables y no siempre funcionan como debieran. HTML5 permite resolver algunas de las molestias asociadas al trabajo sin conexión mediante la interfaz ApplicationCache
.
Algunas de las ventajas que conlleva el uso de ésta caché para una aplicación son:
Para poder trabajar sin conexión, una aplicación únicamente necesita de un archivo de manifiesto, el cual indica al navegador que ficheros debe almacenar en la caché local. El contenido del manifiesto puede ser tan simple como un listado de archivos. Una vez que el navegador ha descargado y almacenado los ficheros (html, CSS, imágenes, javascripts, etc), el navegador hace uso de estos ficheros, incluso cuando el usuario actualiza la página en su navegador.
Además de especificar qué ficheros van a ser almacenados en la caché, es posible indicar cuáles no tienen que serlo, y por tanto obligar al navegador a realizar una petición de dichos ficheros al servidor. Finalmente, si intentamos acceder a un fichero no almacenado en local, y no disponemos de conexión, podemos mostrar un recurso que previamente hemos almacenado en la caché.
El archivo de manifiesto es lo que le indica al navegador cuando y qué tiene que almacenar en su caché, y qué tiene que traerse de la Web. Indicar al navegador el manifiesto que tiene que utilizar es muy sencillo:
<!DOCTYPE html> <html lang="en" manifest="/example.appcache"> ... </html>
El atributo manifest
debe estar incluido en todas las páginas de nuestra aplicación, que queramos que se almacenen en la caché. Es decir, además de los ficheros indicados en el manifiesto, la propia página que incluye el manifiesto es almacenada en la caché. El navegador no almacenará en caché ninguna página que no contenga el atributo manifest
(a menos que esa página aparezca explícitamente en el propio archivo de manifiesto).
El atributo manifest
puede señalar a una URL absoluta o a una ruta relativa, pero las URL absolutas deben tener el mismo origen que la aplicación web. Un archivo de manifiesto puede tener cualquier extensión, pero se debe mostrar con el tipo MIME correcto:
<html manifest="http://www.example.com/example.mf"> ... </html>
El tipo MIME con el que se deben mostrar los archivos de manifiesto es text/cache-manifest
. Es posible que se tenga que añadir un tipo de archivo personalizado a la configuración de .htaccess
o de tu servidor web.
Ejemplo de un archivo de manifiesto sencillo:
CACHE MANIFEST index.html stylesheet.css images/logo.png scripts/main.js
El archivo de manifiesto del ejemplo permite almacenar en caché los cuatro archivos especificados. El formato del manifiesto es importante:
CACHE MANIFEST
debe aparecer en la primera línea y es obligatoria.namespaces
. Si no se especifica ninguna categoría, todos los ficheros pertenecen a la categoría CACHE
.Un ejemplo más complejo sería:
CACHE MANIFEST # 2010-06-18:v2 CACHE: /favicon.ico index.html stylesheet.css images/logo.png scripts/main.js # Resources that require the user to be online. NETWORK: login.php /myapi http://api.twitter.com # static.html will be served if main.py is inaccessible # offline.jpg will be served in place of all images in images/large/ # offline.html will be served in place of all other .html files FALLBACK: /main.py /static.html images/large/ images/offline.jpg *.html /offline.html
Un archivo de manifiesto puede incluir tres categorías: CACHE
, NETWORK
y FALLBACK
.
CACHE
: esta es la sección predeterminada para las entradas. Los archivos incluidos en esta sección (o inmediatamente después de CACHE MANIFEST
) se almacenarán en caché explícitamente después de descargarse por primera vez.NETWORK
: los archivos incluidos en esta sección son recursos permitidos que requieren conexión al servidor. En todas las solicitudes enviadas a estos recursos se omite la caché, incluso si el usuario está trabajando sin conexión. Se pueden utilizar caracteres comodín.FALLBACK
: se trata de una sección opcional en la que se especifican páginas alternativas en caso de no poder acceder a un recurso. La primera URI
corresponde al recurso y la segunda, a la página alternativa. Ambas URI
deben estar relacionadas y tener el mismo origen que el archivo de manifiesto. Se pueden utilizar caracteres comodín.CACHE MANIFEST # 2010-06-18:v3 # Explicitly cached entries index.html css/style.css # offline.html will be displayed if the user is offline FALLBACK: / /offline.html # All other resources require the user to be online. NETWORK: * # Additional resources to cache CACHE: images/logo1.png images/logo2.png images/logo3.png
Nota
Las peticiones de recursos que den como resultado un error 404 (por ejemplo una imagen no encontrada), mostrarán en este caso el fichero
offline.html
Como hemos comentado anteriormente, el manifiesto puede tener cualquier extensión (aunque se recomienda que sea .appcache), pero lo importante es que el servidor envíe el fichero con el tipo MIME correcto. Si utilizamos Apache, es tan sencillo como añadir la siguiente línea al fichero mime.types
:
text/cache-manifest appcache
Esta configuración dependerá del servidor web que utilicemos. De todas maneras, para asegurarnos que el servidor está enviando el manifiesto con la cabecera correcta, podemos utilizar una herramienta como curl
de la siguiente manera:
curl -I http://mysite.com/manifest.appcache
O bien a través de las herramientas de desarrollo integradas en Google Chrome, Safari y Firefox. De cualquiera de las maneras, la respuesta tendría que ser algo parecido a esto:
HTTP/1.1 200 OK Date: Mon, 13 Sep 2010 12:59:30 GMT Server: Apache/2.2.13 (Unix) mod_ssl/2.2.13 OpenSSL/0.9.8l DAV/2 PHP/5.3.0 Last-Modified: Tue, 31 Aug 2010 03:11:00 GMT Accept-Ranges: bytes Content-Length: 113 Content-Type: text/cache-manifest
Cuando visitamos una página que hace uso de la cache de aplicación, el proceso de cacheado que se sigue es el siguiente:
Ahora, el navegador está listo y la caché contiene los ficheros indicados en el manifiesto. Si el manifiesto no ha cambiado, y la página se recarga, ocurre lo siguiente:
Una vez que el navegador tiene almacenados los recursos en su caché, los sirve de manera local y después solicita el manifiesto. Como se puede ver en la siguiente captura de pantalla, Google Chrome únicamente solicita al servidor aquellos ficheros que no se encuentran en la caché de la aplicación.
Si deseamos actualizar alguno de los recursos de la aplicación, tendremos que actualizar primero el manifiesto, para obligar al navegador a solicitar de nuevo todos los recursos. Para ello, es necesario marcar de alguna manera que el manifiesto ha cambiado, aunque los ficheros a cachear sean los mismos. Una práctica muy sencilla es añadir una número de versión o la fecha de modificación del manifiesto:
# 2010-06-18:v3
Una vez que el manifiesto ha cambiado, el comportamiento del navegador es el siguiente:
Hay que destacar, que a pesar de haber modificado los recursos en el navegador, estos cambios no se producen en este momento, ya que se siguen utilizando los cargados previamente. La nueva caché solo estaría disponible si volviésemos a recargar la página. Una manera de modificar este comportamiento es accediendo al objeto applicationCache
.
El objeto window.applicationCache
permite acceder mediante JavaScript a la caché de aplicación del navegador. Su propiedad status
permite comprobar el estado de la memoria caché, y es el encargado de notificarnos que se ha producido un cambio en la caché local.
var appCache = window.applicationCache; switch (appCache.status) { case appCache.UNCACHED: // UNCACHED == 0 return 'UNCACHED'; break; case appCache.IDLE: // IDLE == 1 return 'IDLE'; break; case appCache.CHECKING: // CHECKING == 2 return 'CHECKING'; break; case appCache.DOWNLOADING: // DOWNLOADING == 3 return 'DOWNLOADING'; break; case appCache.UPDATEREADY: // UPDATEREADY == 4 return 'UPDATEREADY'; break; case appCache.OBSOLETE: // OBSOLETE == 5 return 'OBSOLETE'; break; default: return 'UKNOWN CACHE STATUS'; break; };
Para actualizar la caché mediante JavaScript, primero se debe hacer una llamada a applicationCache.update()
. Al hacer esa llamada, se intentará actualizar la caché del usuario (para lo cual será necesario que haya cambiado el archivo de manifiesto). Finalmente, cuando el estado de applicationCache.status
sea UPDATEREADY
, al llamar a applicationCache.swapCache()
, se sustituirá la antigua caché por la nueva.
var appCache = window.applicationCache; appCache.update(); // Attempt to update the user's cache. if (appCache.status == window.applicationCache.UPDATEREADY) { appCache.swapCache(); // The fetch was successful, swap in the new cache. }
Al utilizar update()
y swapCache()
de este modo, no se muestran los recursos actualizados a los usuarios. El flujo indicado solo sirve para pedirle al navegador que busque un nuevo archivo de manifiesto, que descargue el contenido actualizado que se especifica y que actualice la caché de la aplicación. Por tanto, la página se tiene que volver a cargar dos veces para que se muestre el nuevo contenido a los usuarios: una vez para extraer una nueva caché de aplicación y otra para actualizar el contenido de la página.
Para que los usuarios puedan acceder a la versión más reciente del contenido de tu sitio, podemos establecer un escuchador que controle el evento updateready
cuando se cargue la página:
window.addEventListener('load', function(e) { window.applicationCache.addEventListener('updateready', function(e) { if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { // Browser downloaded a new app cache. // Swap it in and reload the page to get the new hotness. window.applicationCache.swapCache(); if (confirm('A new version of this site is available. Load it?')) { window.location.reload(); } } else { // Manifest didn't changed. Nothing new to server. } }, false); }, false);
Hay, además, algunos eventos adicionales que permiten controlar el estado de la caché. El navegador activa eventos para una serie de acciones (como el progreso de las descargas, la actualización de la caché de las aplicaciones y los estados de error). El siguiente fragmento permite establecer escuchadores de eventos para cada tipo de evento de caché:
function handleCacheEvent(e) {} function handleCacheError(e) { alert('Error: Cache failed to update!'); }; // Fired after the first cache of the manifest. appCache.addEventListener('cached', handleCacheEvent, false); // Checking for an update. Always the first event fired in the sequence. appCache.addEventListener('checking', handleCacheEvent, false); // An update was found. The browser is fetching resources. appCache.addEventListener('downloading', handleCacheEvent, false); // The manifest returns 404 or 410, the download failed, // or the manifest changed while the download was in progress. appCache.addEventListener('error', handleCacheError, false); // Fired after the first download of the manifest. appCache.addEventListener('noupdate', handleCacheEvent, false); // Fired if the manifest file returns a 404 or 410. // This results in the application cache being deleted. appCache.addEventListener('obsolete', handleCacheEvent, false); // Fired for each resource listed in the manifest as it is being fetched. appCache.addEventListener('progress', handleCacheEvent, false); // Fired when the manifest resources have been newly redownloaded. appCache.addEventListener('updateready', handleCacheEvent, false);
Si no se puede descargar el archivo de manifiesto o algún recurso especificado en él, fallará todo el proceso de actualización. Si se produce ese fallo, el navegador seguirá utilizando la antigua caché de la aplicación.
Como parte de la especificación de HTML5, el objeto navigator
incluye una propiedad que nos indica si se dispone de conexión o no, concretamente navigator.onLine
. Sin embargo, esta propiedad no se comporta de manera correcta en la mayoría de navegadores, y únicamente cambia su estado al indicar de manera explícita que funcione en modo offline. Como desarrolladores, lo que realmente nos interesa es conocer si realmente hay conexión o no con el servidor.
Una manera de identificar si existe conexión a internet, es utilizar la categoría FALLBACK del manifiesto. En esta categoría podemos indicar dos ficheros JavaScript que detectan si estamos online o no:
CACHE MANIFEST FALLBACK: online.js offline.js
online.js
contiene:
setOnline(true);
Y offline.js
contiene:
setOnline(false);
En nuestra aplicación, creamos una función llamada testOnline
que dinámicamente crea un elemento <script>
, el cual trata de cargar el fichero online.js
. Si la carga se realiza de manera correcta, se ejecuta el método setOnline(true)
. Si estamos offline, el navegador cargará el fichero offline.js
, ejecutando el método setOnline(false)
.
function testOnline(fn) { var script = document.createElement(‘script’) script.src = 'online.js'; // alias the setOnline function to the new function that was passed in window.setOnline = function (online) { document.body.removeChild(script); fn(online); }; // attaching script node trigger the code to run document.body.appendChild(script); } testOnline(function (online) { if (online) { applicationCache.update(); } else { // show users an unobtrusive message that they're disconnected } });
Ejercicio 11