Cache en WebAPI con CacheCow

jueves, 11 de diciembre de 2014

WebAPI CacheCow

Cuando desarrollamos un servicio HTTP utilizando WebApi una de las características que se echan en falta es el soporte para cache como en Asp.net MVC pero por suerte existen librería de terceros que rellenan este hueco como CacheCow.

En este artículo vamos a ver cómo podemos implementar cache de servidor y soportar cabeceras HTTP de cache utilizando CacheCow.

Hay dos artículos que explican las cabeceras HTTP de cache que me gustan bastante, son: https://devcenter.heroku.com/articles/increasing-application-performance-with-http-cache-headers y https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching.

Introducción a CacheCow

CacheCow es una librería que aporta una solución de cache de servidor para WebApi y una cache de cliente también. Además tiene soporte para cabeceras HTTP de cache siguiendo la especificación estándar.

Existe un paquete común CacheCow.Common y un paquete para servidor y cliente, CacheCow.Server y CacheCow.Client.

Se basa en handlers tanto para la parte cliente como para la parte servidor, mediante estos handlers intercepta la petición y la respuesta aplicando lógica y reglas de cache, añadiendo las cabeceras de cache y es bastante configurable tanto a nivel de controlador, como a nivel de acción o método.

La cache tiene varias formas de realizarla, la más básica es en memoria y es la forma que vamos a ver como funciona en este artículo pero también tiene paquetes de NuGet complementarios para realizar cache en Sql Server, RavenDB, Redis y alguna opción más. Tambien puedes implementar tu propio storage handler.

Ejemplo

El ejemplo que vamos a ver es muy básico pero que nos sirve perfectamente para entender como implementar CacheCow.

Vamos a tener un controlador con un metodo GET que nos devuelve un IEnumerable de strings, previamente vamos a añadir un Thread.Sleep simulando un proceso de acceso a base de datos o a otro servicio externo.

 
public class ValuesController : ApiController
{
    public IEnumerable Get()
    {
        //simulación de proceso
        Thread.Sleep(2000);

        return new string[] { "value1", "value2" };         
    }
}


Si ahora hacemos una peticion al servicio, las cabeceras de respuesta son las siguientes.

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNceHVyeG9cRHJvcGJveFxEZXNhcnJvbGxvXFByb3llY3Rvc1BlcnNvbmFsZXNcWHVyeG9EZXZlbG9wZXIuQmxvZ3Nwb3RcV2ViQXBpQ2FjaGVDb3dcV2ViQXBpQ2FjaGVDb3dFeGFtcGxlXFdlYkFwaUNhY2hlQ293RXhhbXBsZVxhcGlcdmFsdWVz?=
X-Powered-By: ASP.NET
Date: Wed, 10 Dec 2014 20:53:42 GMT
Content-Length: 195


Como podemos ver por defecto no hay cache.

Añadiendo el handler

Ahora simplemente vamos a añadir el handler de CacheCow modificando el HttpConfiguration desde WebApiConfig o desde startup, dependiendo de si estamos usando OWIN o no.

 
CachingHandler cachingHandler = new CachingHandler(config);

config.MessageHandlers.Add(cachingHandler);


Solo con este pequeño cambio las cabeceras de la respuesta ahora son asi

HTTP/1.1 200 OK
Cache-Control: no-transform, must-revalidate, max-age=0, private
Content-Type: application/xml; charset=utf-8
Last-Modified: Wed, 10 Dec 2014 21:05:48 GMT
ETag: W/"c8f0b5fd32ae47e6a9847e24382d87f4"
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNceHVyeG9cRHJvcGJveFxEZXNhcnJvbGxvXFByb3llY3Rvc1BlcnNvbmFsZXNcWHVyeG9EZXZlbG9wZXIuQmxvZ3Nwb3RcV2ViQXBpQ2FjaGVDb3dcV2ViQXBpQ2FjaGVDb3dFeGFtcGxlXFdlYkFwaUNhY2hlQ293RXhhbXBsZVxhcGlcdmFsdWVz?=
X-Powered-By: ASP.NET
Date: Wed, 10 Dec 2014 21:05:48 GMT
Content-Length: 195


Ahora la cabecera cache-control es diferente y han aparecido Last-Modified y Etag. Ahora mismo en servidor hay cache y como fecha de mofidicación se envía la fecha de creación de cache en servidor y a ese conjunto de datos se le ha asignado un hash que es lo que viene como ETag. El código de respuesta es 200 pero si volvemos a realizar la petición las caceberas son estas.

HTTP/1.1 304 Not Modified
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
ETag: W/"c8f0b5fd32ae47e6a9847e24382d87f4"
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNceHVyeG9cRHJvcGJveFxEZXNhcnJvbGxvXFByb3llY3Rvc1BlcnNvbmFsZXNcWHVyeG9EZXZlbG9wZXIuQmxvZ3Nwb3RcV2ViQXBpQ2FjaGVDb3dcV2ViQXBpQ2FjaGVDb3dFeGFtcGxlXFdlYkFwaUNhY2hlQ293RXhhbXBsZVxhcGlcdmFsdWVz?=
X-Powered-By: ASP.NET
Date: Wed, 10 Dec 2014 21:05:59 GMT
El resultado de la petición es 304, que indica que no se ha modificado el conjunto de datos desde que el mismo cliente pregunto la última vez. Y el servidor devolverá 304 mientras dure la cache o mientras el conjunto de datos no sea modificado mediante un post o put. Por defecto la cache en CacheCow es de tiempo indefinido.

Configurando las cabeceras Http

Como hemos visto en las peticiones anteriores por defecto CacheCow habilita al cliente a tener una cache con duración 0, si queremos poder controlar el tiempo de cache que el servidor indica al cliente que puede tener, CacheCow habilita un atributo que se llama HttpCacheControlPolicyAttribute, para habilitarlo tenemos que asignar un CacheControlHeaderProvider al CachingHandler y este se encarga de interpretar el atributo que puede ser asignado a un método o al controller entero.

 
CachingHandler cachingHandler = new CachingHandler(config);

cachingHandler.CacheControlHeaderProvider = new AttributeBasedCacheControlPolicy(
                             new CacheControlHeaderValue()
                             {
                                 MaxAge = TimeSpan.FromSeconds(0),
                                 Private = true,
                             }).GetCacheControl;

config.MessageHandlers.Add(cachingHandler);


Al asignar AttributeBasedCacheControlPolicy asignamos un CacheControlHeaderValue por defecto y luego podemos personalizarlo mediante el atributo HttpCacheControlPolicyAttribute.

 
[HttpCacheControlPolicy(true, 300)]
public IEnumerable Get()
{
    //simulate process
    Thread.Sleep(2000);

    return new string[] { "value1", "value2" };
               
}


Ahora las cabeceras de la respuesta son asi:

HTTP/1.1 200 OK
Cache-Control: must-revalidate, max-age=300, private
Content-Type: application/xml; charset=utf-8
Last-Modified: Wed, 10 Dec 2014 21:29:31 GMT
ETag: W/"4620366aa7c5448290fbfa882772c30e"
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNceHVyeG9cRHJvcGJveFxEZXNhcnJvbGxvXFByb3llY3Rvc1BlcnNvbmFsZXNcWHVyeG9EZXZlbG9wZXIuQmxvZ3Nwb3RcV2ViQXBpQ2FjaGVDb3dcV2ViQXBpQ2FjaGVDb3dFeGFtcGxlXFdlYkFwaUNhY2hlQ293RXhhbXBsZVxhcGlcdmFsdWVz?=
X-Powered-By: ASP.NET
Date: Wed, 10 Dec 2014 21:29:31 GMT
Content-Length: 195


Ahora en la cabecera Cache-Control indicamos al cliente que puede hacer una cache de 300 segundos.

Configurando la cache de servidor

Por defecto CacheCow hace una cache de servidor de tiempo ilimitado pero se puede configurar de una forma parecida a la cabecera CacheControl para el cliente. Existe un atributo que se llama HttpCacheRefreshPolicyAttribute y tenemos que asignar al CachingHandler un CacheRefreshPolicyProvider con un tiempo por defecto que puede ser personalizado mediante el atributo HttpCacheRefreshPolicyAttribute.

 
CachingHandler cachingHandler = new CachingHandler(config);

cachingHandler.CacheRefreshPolicyProvider = new AttributeBasedCacheRefreshPolicy(TimeSpan.FromSeconds(0))
                                            .GetCacheRefreshPolicy;

cachingHandler.CacheControlHeaderProvider = new AttributeBasedCacheControlPolicy(
                     new CacheControlHeaderValue()
                     {
                         MaxAge = TimeSpan.FromSeconds(0),
                         Private = true,
                     }).GetCacheControl;

config.MessageHandlers.Add(cachingHandler);


Al asignar CacheRefreshPolicyProvider asigamos un tiempo por defecto de cache de servidor y luego podemos personalizarlo mediante el atributo HttpCacheRefreshPolicyAttribute.

 
[HttpCacheRefreshPolicy(86400)]
[HttpCacheControlPolicy(true, 300)]
public IEnumerable Get()
{
    //simulate process
    Thread.Sleep(2000);

    return new string[] { "value1", "value2" };
               
}


En este caso la cache de servidor dura 24 horas o hasta que el conjunto de datos se modifica mediante un post o put.

Libros relacionados

ASP.NET Web API 2: Building a REST Service from Start to Finish

Designing Evolvable Web APIs with ASP.NET

ASP.NET Web API 2 Recipes: A Problem-Solution Approach

No hay comentarios:

Publicar un comentario