Caché HTTP de cliente en C# (1 de 4): aplicaciones de escritorio


Hoy en día es muy habitual utilizar contenido web en nuestras aplicaciones y eso penaliza el rendimiento. Para mejorarlo vamos a tener que almacenar en caché dicho contenido. Hacerlo bien no es difícil, pero es necesario conocer cómo funcionan los mecanismos de caché en la web y buscar dentro de la documentación de .Net cómo podemos aprovechar estos.

Upload / Download
Upload / Download por johntrainor, en Flickr

Como no soy el primero en tener la necesidad, existen algunas entradas en StackOverflow sobre el tema. Parece que la solución propuesta funciona bien, pero todas los que he visto se olvidan de algunos puntos importantes, como por ejemplo pedir al servidor si hay una versión actualizada del archivo, añadir técnicas de scavenging para que el contenido descargado caduque, etc. Además se olvidan de que el sistema operativo ya sabe cachear y es un poco raro que tengamos que volver a programar el caché que tan bien hacen los navegadores.

Te recomiendo leer el artículo hasta el final, pero si tienes mucha prisa puedes descargar el código de ejemplo en GitHub.

Aprovechar los mecanismos de caché adecuadamente

Hay muchos casos en los que soluciones como la anterior valdrían, pues se supone que estamos metiendo en caché contenido estático, que no cambia nunca, pero en mi caso algunos archivos se actualizaban cada cierto tiempo. Un desarrollador con poca experiencia, o un poco perezoso, compararía la fecha de descarga y establecería un tiempo de vida para volver a descargar la imagen, pero eso es poco eficiente, puede producir falsos positivos e ignora completamente las especificaciones HTTP sobre caché.

Como lo que he visto no me ha convencido, voy a escribir aquí una pequeña serie de artículos sobre el caché de contenido y cómo aprovechar al máximo las capacidades del sistema operativo para mejorar nuestras aplicaciones. La he dividido en los siguientes capítulos:

  1. Caché HTTP de cliente en aplicaciones de escritorio (este post)
  2. Caché HTTP de cliente avanzado en aplicaciones de escritorio
  3. Caché para Windows Store
  4. Caché de contenido dinámico

¿Por qué debería saber algo sobre el caché?

Como he dicho antes, hoy en día utilizamos contenido web desde casi cualquier aplicación. Los navegadores entienden las reglas HTTP/1.x de caché y si estamos haciendo una aplicación HTML5/JavaScript no tenemos por qué preocuparnos mucho, pero en cualquier otro tipo de aplicación no tendremos un motor de HTML que maneje bien el caché y es posible que tengamos que manejar o por lo menos configurar algo para que nuestro software no se descargue todo el contenido cada vez que arrancamos la aplicación.

Si necesitáis justificar algunos motivos por los que aprender algo del caché HTTP de cliente, aquí tenéis algunos:

  • Tus aplicaciones irán más rápido
  • Consumirán menos datos y no gastarás toda la tarifa 4G de tus usuarios
  • Si son apps móviles, consumirán menos batería
  • Te ahorrarás dinero, si estás haciendo una aplicación que consume contenido proporcionado por tus servicios, cuantas menos veces se descargue tu contenido más te ahorras en servidores y cuotas de descarga

Pero, ¿Las imágenes no se cachean automáticamente?

Si y no. Por ejemplo, en WPF el control Image puede tener como fuente de datos una URI y el control realiza un caché local en memoria del bitmap, pero eso lo hace para cualquier fuente, sea web o disco. Es decir, la siguiente vez que abras la aplicación, ésta volverá a descargar las imágenes.

Para comprobarlo, basta crear una pequeña aplicación que muestre una imagen y observar con Fiddler qué está ocurriendo si abrimos la imagen con un navegador o si la abrimos desde la aplicación.
Al abrir una imagen de Flickr por primera vez con IE11 obtendremos unas cabeceras de respuesta tal que así:

HTTP/1.1 200 OK
Date: Sun, 06 Jul 2014 14:27:13 GMT
Content-Type: image/jpeg
Content-Length: 118810
Connection: keep-alive
P3P: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV"
Accept-Ranges: bytes
Cache-Control: max-age=315360000,public
Expires: Mon, 11 Mar 2024 03:21:39 UTC
Last-Modified: Mon, 03 Sep 2007 08:45:59 GMT
X-Cache: HIT from photocache202.flickr.bf1.yahoo.com
X-Cache-Lookup: HIT from photocache202.flickr.bf1.yahoo.com:85
Age: 417913
X-Cache: HIT from cache415.flickr.ch1.yahoo.com
X-Cache-Lookup: HIT from cache415.flickr.ch1.yahoo.com:3128
Via: 1.1 photocache202.flickr.bf1.yahoo.com:85 (squid/2.7.STABLE9), 1.1 cache415.flickr.ch1.yahoo.com:3128 (squid/2.7.STABLE9)

Las cabeceras que nos interesan son Cache-Control, Expires y Last-Modified. Estas cabeceras nos indican principalmente que podemos almacenar la imagen en caché.
La siguiente vez que carguemos la imagen desde IE11 (basta hacer un F5), ésta cargará mucho más rápido. Si miramos en Fiddler veremos que ha iniciado una conversación HTTP pero el resultado ha sido un código 304 y se ha descargado 0 bytes:

304

En este caso las cabeceras interesantes son las que ha enviado el navegador al servidor dentro del web request:

GET /1381/1310759230_9203a83da3.jpg HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US,en;q=0.9,es-ES;q=0.7,es;q=0.6,fr-FR;q=0.4,fr;q=0.3,ca;q=0.1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: farm2.staticflickr.com
If-Modified-Since: Mon, 03 Sep 2007 08:45:59 GMT
DNT: 1
Connection: Keep-Alive

La cabecera If-Modified-Since indica al servidor web que tenemos una imagen con esa fecha y que sólo nos tiene que proporcionar una nueva si fue modificada.

Comportamiento por defecto en WPF

Ahora, si intentamos esto desde una aplicación WPF esperaremos que ocurra lo mismo, pero nos encontraremos con que cada vez que abrimos la aplicación vuelve a descargarse la imagen.
El problema principal está en que el WebClient que utiliza el control necesita que se le especifique si se tiene que utilizar el caché o no y por defecto no lo tiene activado. Aquí se puede ver la diferencia entre cargar todo el contenido y habilitar la verificación:

Usar caché en WPF

Aunque el control imagen WPF no utiliza directamente el caché, se puede habilitar fácilmente sin necesidad de código. No está demasiado documentado, pero es posible configurar un BitmapImage con un RequestCacheLevel que permita usar el caché:

En este ejemplo observamos lo siguiente:

  • En la primera imagen no he puesto valor de CachePolicy y cargará siempre la imagen
  • La segunda usa la opción Revalidate, que pedirá siempre al servidor web si la imagen todavía es válida usando la cabecera If-Modified-Since
  • La tercera usará una imagen del caché si está disponible y si no la pedirá a la web, pero no comprobará si existe una imagen nueva

Hay más opciones, como forzar el uso del caché, no usarlo nunca, etc., tenéis todas las opciones en RequestCacheLevel.
Y os preguntaréis, ¿cual usa por defecto? Pues por defecto no usa ningún valor de CachePolicy y eso equivale a un valor BypassCache en el RequestCacheLevel, pero lo podemos asignar a nivel de app.config en la entrada requestCaching para todas las llamadas que no especifiquen un valor:

Enlaces interesantes

Para profundizar más en el tema, aquí tenéis algunos enlaces que hablan sobre el caché en aplicaciones .Net:

Anuncios

Un Comentario

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s