HTTP Caching Support

5 views
Skip to first unread message

Daniel Yoder

unread,
Sep 28, 2009, 1:59:39 AM9/28/09
to ruby...@googlegroups.com
Hi everybody,

It's been a few weeks since my last posts here, partly because I haven't been able to get as much done on Waves recently. However, I've spent a bit of time this past weekend experimenting with support for HTTP caching. I'd like to run some ideas by everyone and get some feedback. 

Roberto and Kaykay had previously done some work around using Rack::Cache with Waves, but I wanted to go a bit farther and make it easier to use HTTP caching appropriately in a Waves app, which you have to be able to do in order to take advantage of Rack::Cache anyway.

Rails provides a similar feature, with #fresh_when and #stale? I won't go into why I believe this interface is hopelessly confused, but they at least have the spirit of thing There is no reason not to try and encapsulate the otherwise repetitive logic associated with HTTP caching.

For the moment, I am focusing entirely on Last-Modified, because the logic here is simple and universal. Basically, the idea is that you respond with a 304: Not Modified if, in fact, the resource being requested hasn't been modified since the last time it was served. Nothing tricky there.

So mostly we are just doing what we usually do in Waves, which is NOT to try and hide this, but rather just make it easier to do. To begin with, I've added a simple method to the ResponseMixin called #modified? which takes a timestamp and returns true or false based on what the If-Modified-Since header it set to.

For example, suppose I am returning a Javascript resource that is stored in a file. I can determine whether not it needs to be reloaded from the file by simply doing this:

if modified?( File.mtime( path ) )
  File.read( path )
else
  # ... return a Not Found response ...
end

However, that by itself isn't terribly helpful because it isn't clear what to do if I don't need to reload the file. So first we have the #not_modified method that is now part of the ResponseMixin:

if modified?( File.mtime( path ) )
  File.read( path )
else
  not_modified
end

This will raise an Exception that will end processing and return control back to the Dispatcher, as well as modifying the response accordingly (including making sure there are no stray headers that will negate the Not Modified response).

However, even this obscures what we are really doing here, so there is a nice method to clean this up, called #http_cache, which takes a block that is run if necessary:

http_cache( File.mtime( path ) ) { File.read( path ) }

Later, I am thinking we can add ETag support in exactly the same fashion, effectively overloading #http_cache to take either a string (the ETag) or timestamp.

I've added a Resource mixin called FileMixin which implements this logic for file-based resources. You just include it and then use #load_from_file and it will automatically start serving resources from cache. I am doing this with my JavaScript and CSS right now and it works beautifully. (My Publisher project on GitHub has the code.) No need to timestamp those URLs anymore!

At the moment, this implementation relies on Rack::Cache because something (I suspect Mongrel) is automatically adding back in the prohibited headers, so you need some middleware to take them back out. However, generally Rack::Cache is a perfect complement to HTTP caching within your app, so that isn't a bad dependency to have. 

What do you all think of this basic approach? Good, bad, ugly?

Regards,
Dan

seneca

unread,
Sep 28, 2009, 6:25:58 PM9/28/09
to rubywaves
Thanks for your efforts.

I like your approach here, so go for it.

But on a slightly unrelated note. Can we have an alfa Waves release
out asap, so that we and everybody else can see that this project is
alive and well.
Alfa release can help us iron out all possible glitches befeore going
1.0 o r 0.9?

Best regards
Reply all
Reply to author
Forward
0 new messages