Revista Informática

Sinatra desde Cero: Caching y Etags

Publicado el 13 febrero 2014 por Codehero @codeheroblog

Para comprender correctamente todo lo que hablaremos en esta serie, es conveniente tener un conocimiento básico sobre el lenguaje de Ruby. Podrás conseguir toda la información desees aquí

La información para este artículo la sacamos de la recopilación de información del libro Sinatra: Up and Running y de la Documentación oficial


Caching

¿Qué es Caching?

Cuando hablamos de Caching en una aplicación web asumimos que se refiere al control que nos otorgan ciertos parámetros del encabezado HTTP que permite acelerar el tiempo de acceso a una data previamente enviada, reducir el ancho de banda consumido y disminuir la carga en el servidor. Existen múltiples etiquetas o parámetros dentro del encabezado HTTP que nos permiten realizar distintas estrategias de caching y es por esta razón que podemos decir que el caching en una aplicación web es un tema bastante complejo que contiene múltiples vertientes y formas de realizarlo en distintos niveles.

Tipos de control de Caché:

Estos son los mecanismos básicos para el control de caché:

  • Validación: que puede usarse para comprobar si una respuesta cacheada sigue siendo buena tras caducar. Por ejemplo, si la respuesta tiene una cabecera Last-Modified, un caché puede hacer una petición condicional usando la cabecera If-Modified-Since para saber si la página cambió.

  • Frescura: que permite que una respuesta sea usada sin comprobar de nuevo el servidor origen, y puede ser controlada tanto por el servidor como el cliente. Por ejemplo, la cabecera de respuesta Expires facilita una fecha en la que el documento caduca, y la directiva Cache-Control: max-age informa al caché del número de segundos durante los que la respuesta será válida.

  • Invalidación: que normalmente es un efecto secundario de otra petición que pasa por la caché. Por ejemplo, si la URL asociada con una respuesta cacheada es solicitada posteriormente mediante una petición POST, PUT o DELETE, la respuesta cacheada quedará invalidada.

Esta información es un extracto de Caché Web, Wikipedia.

Como se usa el Caching en Sinatra

Para agregar la variable Cache-Control antes mencionada a la cabecera se puede hacer de dos maneras; una completamente manual y otra por medio del helper expires de Sinatra. Realicemos la prueba de ambas para que observen como se hacen:

Control de caché de tipo Frescura

Para agregar el encabezado Cache-Control manualmente, creemos el siguiente archivo:

require 'sinatra' before do content_type :txt end get '/' do headers "Cache-Control" => "public, must-revalidate, max-age=3600", "Expires" => Time.at(Time.now.to_i + (60 * 60)).to_s "This page rendered at #{Time.now}." end

1234567891011 require'sinatra' before do  content_type:txtend get'/'do  headers"Cache-Control"=>"public, must-revalidate, max-age=3600",  "Expires"=>Timeat(Timenowto_i+(60*60))to_s  "This page rendered at #{Time.now}."end

Cómo siempre debemos tener Sinatra instalado y agregarlo al archivo por medio de un require. Si observamos la ruta '/' podemos apreciar que estamos explícitamente indicándole a Sinatra que agregue un encabezado a la petición llamado Cache-Control que tiene un tiempo de vida max-age de una hora y se debe revalidar en lo que se sirva el archivo y presente el caché expirado. Luego estamos confirmando la fecha en la que debe expirar.

Vamos a hacer la petición mediante un cURL ampliada con -v verbose para ver que está ocurriendo:

$ curl -v localhost:4567 * Adding handle: conn: 0x7f9909803a00 * Adding handle: send: 0 * Adding handle: recv: 0 * Curl_addHandleToPipeline: length: 1 * - Conn 0 (0x7f9909803a00) send_pipe: 1, recv_pipe: 0 * About to connect() to localhost port 4567 (#0) * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 4567 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.30.0 > Host: localhost:4567 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: text/plain;charset=utf-8 < Cache-Control: public, must-revalidate, max-age=3600 < Expires: 2014-02-12 22:15:42 -0430 < Content-Length: 48 < X-Content-Type-Options: nosniff * Server WEBrick/1.3.1 (Ruby/2.0.0/2013-11-22) is not blacklisted < Server: WEBrick/1.3.1 (Ruby/2.0.0/2013-11-22) < Date: Thu, 13 Feb 2014 01:45:42 GMT < Connection: Keep-Alive < * Connection #0 to host localhost left intact Página cargada a las 2014-02-12 21:15:42 -0430.

123456789101112131415161718192021222324252627 $curl-vlocalhost:4567*Adding handle:conn:0x7f9909803a00*Adding handle:send:0*Adding handle:recv:0*Curl_addHandleToPipeline:length:1*-Conn0(0x7f9909803a00)send_pipe:1,recv_pipe:0*About toconnect()tolocalhost port4567(#0)*   Trying127.0.0.1...*Connected tolocalhost(127.0.0.1)port4567(#0)>GET/HTTP/1.1>User-Agent:curl/7.30.0>Host:localhost:4567>Accept:*/*><HTTP/1.1200OK<Content-Type:text/plain;charset=utf-8<Cache-Control:public,must-revalidate,max-age=3600<Expires:2014-02-1222:15:42-0430<Content-Length:48<X-Content-Type-Options:nosniff*Server WEBrick/1.3.1(Ruby/2.0.0/2013-11-22)isnotblacklisted<Server:WEBrick/1.3.1(Ruby/2.0.0/2013-11-22)<Date:Thu,13Feb201401:45:42GMT<Connection:Keep-Alive<*Connection#0 to host localhost left intactPágina cargadaalas2014-02-1221:15:42-0430.

Podemos ver que la petición arrojó un código 200 OK y a su vez vemos todo el encabezado de dicha petición. Apreciamos lo siguiente:

  • Cache-Control: public, must-revalidate, max-age=3600
  • Expires: 2014-02-12 22:15:42 -0430

El encabezado de dicha petición contiene todo lo que agregamos manualmente, la etiqueta Cache-Control y la Fecha de expiración de la información que ahí presentamos.

Control de caché de tipo Frescura con expires

Para agregar el encabezado mediante el helper expires realizamos lo siguiente:

require 'sinatra' before do content_type :txt end get '/2' do expires 3600, :public, :must_revalidate "Página cargada a las: #{Time.now}." end

12345678910 require'sinatra' before do  content_type:txtend get'/2'do  expires3600,:public,:must_revalidate  "Página cargada a las: #{Time.now}."end

Utilizando la etiqueta expires realizamos exactamente lo mismo que hicimos manualmente pero por medio del helper; es decir él agregará las etiquetas de Cache-Control y Expires al encabezado de la petición.

Hagamos la prueba utilizando cURL nuevamente:

$ curl -i localhost:4567/2 < HTTP/1.1 200 OK < Content-Type: text/plain;charset=utf-8 < Cache-Control: public, must-revalidate, max-age=3600 < Expires: Thu, 13 Feb 2014 02:48:08 GMT < Content-Length: 49 < X-Content-Type-Options: nosniff * Server WEBrick/1.3.1 (Ruby/2.0.0/2013-11-22) is not blacklisted < Server: WEBrick/1.3.1 (Ruby/2.0.0/2013-11-22) < Date: Thu, 13 Feb 2014 01:48:08 GMT < Connection: Keep-Alive < * Connection #0 to host localhost left intact Página cargada a las: 2014-02-12 21:18:08 -0430.

1234567891011121314 $curl-ilocalhost:4567/2<HTTP/1.1200OK<Content-Type:text/plain;charset=utf-8<Cache-Control:public,must-revalidate,max-age=3600<Expires:Thu,13Feb201402:48:08GMT<Content-Length:49<X-Content-Type-Options:nosniff*Server WEBrick/1.3.1(Ruby/2.0.0/2013-11-22)isnotblacklisted<Server:WEBrick/1.3.1(Ruby/2.0.0/2013-11-22)<Date:Thu,13Feb201401:48:08GMT<Connection:Keep-Alive<*Connection#0 to host localhost left intactPágina cargadaalas:2014-02-1221:18:08-0430.

Volvemos a apreciar que la petición responde con un código 200 OK y la misma contiene las etiquetas antes descritas y funciona correctamente.

Control de caché de tipo Validación

Para agregar el encabezado Last-Modified podemos realizarlo de la siguiente manera:

require 'sinatra' before do content_type :txt end get '/hola' do @article = '2014-02-12 21:56:25 -0430' last_modified @article end

12345678910 require'sinatra' before do  content_type:txtend get'/hola'do  @article='2014-02-12 21:56:25 -0430'  last_modified@articleend

Muy sencillo, al igual que Expires, last_modified es un helper el cual acepta como parámetro un identificador de tiempo, tal como un campo updated_at de tipo Date en la base de datos. Por no tener base de datos estamos simulando un objeto @article que representa una fecha y agregando esa información al campo Last-Modified para validar el momento de última modificación.

Si ahora probamos con la herramienta cURL podremos observar que se agregó dicho campo al encabezado.

$ curl -i localhost:4567/hola HTTP/1.1 200 OK Content-Type: text/plain;charset=utf-8 Last-Modified: Thu, 13 Feb 2014 02:26:25 GMT Content-Length: 0 X-Content-Type-Options: nosniff Server: WEBrick/1.3.1 (Ruby/2.0.0/2013-11-22) Date: Thu, 13 Feb 2014 02:43:55 GMT Connection: Keep-Alive

123456789 $curl-ilocalhost:4567/holaHTTP/1.1200OKContent-Type:text/plain;charset=utf-8Last-Modified:Thu,13Feb201402:26:25GMTContent-Length:0X-Content-Type-Options:nosniffServer:WEBrick/1.3.1(Ruby/2.0.0/2013-11-22)Date:Thu,13Feb201402:43:55GMTConnection:Keep-Alive

Podemos apreciar que la etiqueta se encuentra en el encabezado y se recibió un código 200 OK. Un detalle importante no mencionado anteriormente es que si la información que se presenta ya la tiene el usuario en caché el servidor responderá con un código 304 Not modified volviendo a cargar el contenido que se encontraba en caché.


Etags

¿Qué son Etags?

Los Etags como su nombre lo indica son Etiquetas de Entidad y son utilizadas como otra forma de validar la Frescura del recurso. Por lo general son huellas que pueden ser un sha1, md5, etc..) que representan la integridad de la data, es decir si cambiamos 1 bit, esta huella o fingerprint se modificará indicándole al navegador que el recurso cambió.

Vamos a agregar el Etag a nuestro encabezado:

require 'sinatra' require 'digest/sha1' before do content_type :txt @article = '2014-02-12 21:56:25 -0430' @article_etag = Digest::SHA1.hexdigest @article.to_s end get '/etag' do etag @article_etag "Valor del Etag: #{@article_etag}." end

12345678910111213 require'sinatra'require'digest/sha1' before do  content_type:txt  @article='2014-02-12 21:56:25 -0430'  @article_etag=Digest::SHA1hexdigest@articleto_send get'/etag'do  etag@article_etag  "Valor del Etag: #{@article_etag}."end

Podemos apreciar que se requiere la librería digest/sha1 para poder realizar le el checksum al objeto @article para generar el objeto @article_etag. Luego solo debemos agregar la etiqueta etag y pasarle el objeto @article_etag a la misma.

Si hacemos la petición del URL /etag apreciamos lo siguiente:

curl -i localhost:4567/etag HTTP/1.1 200 OK Content-Type: text/plain;charset=utf-8 Etag: "617f6a40761c57030dabf997bd912abf5c61729f" Content-Length: 57 X-Content-Type-Options: nosniff Server: WEBrick/1.3.1 (Ruby/2.0.0/2013-11-22) Date: Thu, 13 Feb 2014 04:37:54 GMT Connection: Keep-Alive Valor del Etag: 617f6a40761c57030dabf997bd912abf5c61729f.%

1234567891011 curl-ilocalhost:4567/etagHTTP/1.1200OKContent-Type:text/plain;charset=utf-8Etag:"617f6a40761c57030dabf997bd912abf5c61729f"Content-Length:57X-Content-Type-Options:nosniffServer:WEBrick/1.3.1(Ruby/2.0.0/2013-11-22)Date:Thu,13Feb201404:37:54GMTConnection:Keep-Alive Valor del Etag:617f6a40761c57030dabf997bd912abf5c61729f.%

Se generó correctamente dicha etiqueta y es un número que nos permitirá detallar si algo fue modificado en el objeto, sea lo que sea.


Conclusión

En este onceavo capítulo de la serie, hemos visto como realizar caching de lo que enviamos desde el servidor hacia el navegador del usuario. Vimos que podemos utilizar varias técnicas para realizarlo y además aprendimos sobre Etags, todo con la finalidad de comprobar la validez y frescura de las peticiones que enviamos para ahorrarnos ancho de banda, aumentar la velocidad de respuesta y minimizar la carga en el servidor. Si te surge algún tipo de duda no te detengas y déjanos un comentario, que gustosamente lo responderemos.

¡Hasta el próximo capítulo!


Volver a la Portada de Logo Paperblog