Si bien uno de los grandes motivos de anteponer las aplicaciones nativas sobre las aplicaciones web era la posibilidad de que estas primeras pudieran seguir trabajando a pesar de no disponer de Internet o acceso a un servidor determinado, con la especificación de HTML 5 que vamos a ver hoy cambia totalmente el panorama: Application Cache.
¿Qué es Application Cache?
Esta especificación está pensada para escenarios donde podemos experimentar situaciones en las que no disponemos de conectividad, como en un avión, zonas con poca cobertura, desconexiones intermitentes, etcétera. Hasta ahora con el modo de caché habitual no eramos capaces de obtener recursos a los que no hubiéramos accedido previamente, ni podíamos indicar a qué páginas era posible navegar aún cuando no disponíamos de acceso al servidor, por falta de Internet o porque el mismo no estuviera disponible en ese momento. Pero no sólo los escenarios relacionados con la conectividad son los candidatos de disfrutar de esta API, sino que también nos permite mejorar rendimiento del servidor reduciendo las peticiones de recursos en usuarios recurrentes, manteniendo los más estáticos cacheados en el navegador de forma explícita.
Esta característica está siendo muy utilizada en diferentes escenarios como juegos (como la aplicación de Angry Birds de Chrome Web Store), aplicaciones offline de acceso al correo electrónico, clientes web de música por streaming como Deezer y todas aquellas que puedan trabajar en local y sean capaces de sincronizar su contenido más tarde, o necesiten reducir el flujo de peticiones.
¿Cómo funciona?
Para poder trabajar con App Cache lo primero que debemos conocer es el archivo llamado cache manifest. Se trata de un archivo de texto plano con la siguiente estructura:
CACHE MANIFEST #version 5.9 CACHE: #This section list all resources that should be downloaded and store locally #HTML files index.html #JavaScript files /Scripts/appCache.js /Content/images/glyphicons-halflings.png /Scripts/bootstrap.min.js /Scripts/jquery-1.9.1.min.js #CSS files /Content/bootstrap.min.css NETWORK: #This section list all URLs that may be loaded over Internet. online.html FALLBACK: #Lists replacements for network URLs to be used when the browser is offline or the remote server is unavailable /home.html /offline.html /Content/images/glyphicons-halflings-white.png /Content/offline.png
En primer lugar, todo manifiesto debe comenzar con CACHE MANIFEST en la primera línea del archivo, de lo contrario podemos encontrar problemas cuando se intenta procesar por el navegador.
Todas aquellas líneas que comiencen con una almohadilla (#) se tratan de comentarios. A continuación revisamos las siguientes secciones del manifiesto:
- CACHE
- Listado de todos aquellos recursos que deben ser descargados y almacenados localmente.
- NETWORK
- Lista de todas las URL que necesitan acceso al servidor remoto/Internet. Es posible utilizar el asterisco para indicar que cualquier recurso que no esté listado en el apartado CACHE necesita acceso a la red para acceder a él. Es posible hacer uso de URLs relativasy absolutas.
- FALLBACK
- En el caso de no tener acceso al servidor remoto se pueden especificar qué recursos deben responder por aquellos que no pueden ser recuperados. Los mismos pueden ser de cualquier tipo (una página HTML, una imágen, código JavaScript, etcétera). El primer parámetro es el recurso al que nos gustaría acceder y el segundo el que tomará su lugar cuando existan problemas de conectividad.
Para indicar que un sitio tiene un manifiesto relacionado, basta con asociarlo en la etiqueta html de la página:
<!DOCTYPE html> <html manifest="manifest.appcache"> <head>
El mismo puede tener el nombre y extensión que se prefiera, pero con convención se sugiere *.appcache. Como MIME type del mismo es recomendable servir text/cache-manifest. Si trabajamos con aplicaciones .NET podemos establecerlo a nivel del archivo web.config de la siguiente forma:
<?xml version="1.0"?> <!-- For more information on how to configure your ASP.NET application, please visit http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> </system.web> <system.webServer> <staticContent> <mimeMap fileExtension=".appcache" mimeType="text/cache-manifest"/> </staticContent> </system.webServer> </configuration>
Ciclo de vida de Application Cache
Una vez que la página asociada al manifiesto haya cargado, lo primero que el navegador comprobará es si tiene la versión más reciente del manifiesto para ese sitio. De no ser así descargará del servidor la última versión. Una vez obtenida comenzará a descargar todos los recursos nombrados en el apartado CACHE y aquellos que podrían ser utilizados como FALLBACK, en el caso de que no pudiéramos alcanzar el servidor de destino. A partir de este momento se trabajará con los recursos cacheados y no volverán a descargarse hasta que el manifiesto vuelva a sufrir una nueva actualización o limpiemos la datos temporales de nuestro navegador.
Si quisiéramos comprobar su efectividad, basta con desconectar el dispositivo de Internet e intentar navegar a aquellas páginas y recursos que fueron nombrados en el manifiesto. Del mismo modo, si intentáramos acceder a aquellos recursos que poseen un fallback, el navegador nos desviaría directamente a aquellas URL indicadas como segundo parámetro.
Eventos
En las versiones más recientes de Chrome (En el momento del post Chrome 26) es posible ver los eventos ocurridos a través de la consola de la herramienta de desarrollo:
No obstante, es posible añadir manejadores a cada uno de ellos con el siguiente código JavaScript:
window.onload = function () { //App Cache if (window.applicationCache) { console.log("Application cache is available"); var appCache = window.applicationCache; appCache.addEventListener("checking", appCacheHandler, false); appCache.addEventListener("downloading", appCacheHandler, false); appCache.addEventListener("noupdate", appCacheHandler, false); appCache.addEventListener("obsolete", appCacheHandler, false); appCache.addEventListener("progress", appCacheHandler, false); appCache.addEventListener("error", appCacheHandler, false); appCache.addEventListener("updateready", appCacheHandler, false); appCache.addEventListener("cached", appCacheHandler, false); function appCacheHandler(event) { var status = appCache.status; var msg = null; switch (status) { case appCache.CHECKING: msg = "<span class='label label-warning'>CHECKING</span>"; break; case appCache.DOWNLOADING: msg = "<span class='label label-info'>DOWNLOADING</span>"; break; case appCache.UNCACHED: msg = "<span class='label label-inverse'>UNCACHED</span>"; break; case appCache.OBSOLETE: msg = "<span class='label label-important'>OBSOLETE</span>"; break; case appCache.IDLE: msg = "<span class='label label-info'>IDLE</span>"; break; case appCache.UPDATEREADY: msg = "<span class='label label-success'>UPDATEREADY</label>"; break; default: msg = "UNKNOWN STATUS"; break; } var log = document.getElementById("log"); log.innerHTML += '<li> Event: <span class="label label-inverse">' + event.type + '</span> Status: ' + msg + "</li>"; if (status === appCache.UPDATEREADY) { appCache.swapCache(); if (window.confirm('App cache was updated, you need refresh this page... May I?')) window.location.reload(); else { log.innerHTML += '<li><span class="label label-important">You need to refresh this page to see the changes!</span></li>'; } } } } };
Lo primero que comprobamos en nuestro código es si Application Cache está soportado por nuestro navegador (las últimas versiones de los navegadores más populares ya lo implementan). Para tener una visión más completa de la disponibilidad de esta característica podemos consultar esta tabla.
En cuanto a los eventos tenemos los siguientes:
- checking
- Se lanza cuando el navegador está comprobando la última versión del manifiesto alojada en el servidor remoto.
- downloading
- Indica que el manifiesto ha sido descargado y comienza la obtención de los recursos.
- noupdate
- Indica que tenemos la última versión del manifiesto y no es necesario descargar ningún recurso.
- obsolete
- La versión del servidor está obsoleta.
- progress
- Se lanza por cada uno de los recursos incluídos en la sección CACHE
- error
- Como su propio nombre indica, se trata del evento reservado para los errores durante la obtención de los recursos indicados en el manifiesto.
- updateready
- Nos permite conocer cuándo todos los recursos han sido actualizados con la nueva versión. En este ejemplo, además de mostrar el texto correspondiente, hacemos una comprobación al terminar el switch donde verificamos si hemos llegado a este punto y forzamos el cambio de cache a los nuevos elementos a través de la función swapCache(). Si bien esta función nos ahorra una recarga en el sitio, swapCache no nos permite servir los recursos actualizados de manera automática, por lo que debemos preguntar al usuario si podemos refrescar la página para aplicar los nuevos cambios.
- cached
- Los recursos han sido descargados y la aplicación ha sido cacheada. Este evento sólo ocurre la primera vez que se cachea el contenido. En las siguientes descargas será updateready el que se lance.
En este ejemplo la misma función controla todos los eventos posibles y, a través de un switch, muestra además el estado que refleja cada uno de ellos donde curiosamente no tiene por qué coincidir con el nombre del evento. Podemos verlo en la siguiente imagen:
Eventos Online y Offline
Por último, aunque no pertenezca a la API de App Cache, me gustaría mostrar la forma de conocer cuándo una aplicación no dispone de acceso a Internet… ¡Efectivamente! Existe otra API para ello 😀 En este caso debemos hablar de los eventos online y offline pertenecientes al objeto window. A través de la propiedad navigator.onLine podemos conocer cuál es nuestro estado actual e incluso podemos manejar los eventos online y offline de la siguiente manera:
//Internet status function isOnline() { return navigator.onLine; } function internetStatus() { var className = isOnline() ? "badge-success" : "badge-important"; var statusName = isOnline() ? "online" : "offline"; var internet = document.getElementById("internet"); internet.className = "badge " + className; internet.innerText = statusName; } window.addEventListener("online", internetStatus, false); window.addEventListener("offline", internetStatus, false); internetStatus();
Como podemos ver en el ejemplo, hemos encapsulado el valor de [window.]navigator.onLine en la función isOnline con el objetivo de comprobar dentro de internetStatus cuál es su valor y asociar una clase y un texto a una etiqueta con id «internet» en nuestra página. Además asociamos esta función a los eventos online y offline de window para que, cada vez que ocurran, se vuelta a comprobar el estado de nuestro acceso a Internet. Por último se llama a internetStatus una primera vez al cargar la página para conocer en qué estado nos encontramos inicialmente.
Podéis encontrar más información y demos en la página Appcache Facts.
Existe una página llamada Cache Manifest Validator que nos permite comprobar si hemos creado nuestro manifiesto correctamente. De hecho, también disponemos de una extensión para Chrome que realiza la misma función.
Espero que haya sido de utilidad 😀
¡Saludos!