Where/when do I put my headers?

60 views
Skip to first unread message

TheWickedFlea

unread,
Jun 5, 2009, 5:58:14 PM6/5/09
to rack-cache
Hello,

I'm using Sinatra 0.9.2 and have a basic website set up, and I want to
add the caching in. My trouble is that I don't understand precisely
where, and when, to put my headers and when to generate a response.
The documentation is too nebulous about this, and the Sinatra example
is illogical to me (why use before-do, when the routes may have
convoluted logic?) and is therefore unhelpful.

I've got a simple flat-file system going for my articles, and lets say
that I want to cache them for 1 hour unless modified. Obviously this
is easy to do with File.mtime, but where do I send the headers? I
*could* use before-do, but I have other routes that need a different
logic to cache and I'd rather avoid if-then-else and case statements.

If I do,--

> get '/article/:id/ do
> expires 1.hour # sets response["Expires"] to the httpdate of 1.hour from Time.now
> last_modified article_mtime(params[:id]) # File.mtime().httpdate...
> haml :"articles/#{params[:id]}.haml"
> end

Obviously I'll still generate a HTML response, which might not even
get sent due to caching. But how do I know? Can I,--

> get '/article/:id/ do
> expires 1.hour
> last_modified article_mtime(params[:id])
> haml :"articles/#{params[:id]}.haml" unless response.fresh?
> end

Part of the trouble is that I've got semi-static routes with content
to be generated every 6/12/24 hours with different reasoning on when
it was last modified. You can guess how fast before-do will become
messy...

In other words, just how do I integrate with Rack::Cache?

Thanks,

Flea

Ryan Tomayko

unread,
Jun 8, 2009, 2:24:10 AM6/8/09
to rack-...@googlegroups.com
On Fri, Jun 5, 2009 at 2:58 PM, TheWickedFlea<thewic...@gmail.com> wrote:
> I'm using Sinatra 0.9.2 and have a basic website set up, and I want to
> add the caching in.  My trouble is that I don't understand precisely
> where, and when, to put my headers and when to generate a response.
> The documentation is too nebulous about this, and the Sinatra example
> is illogical to me (why use before-do, when the routes may have
> convoluted logic?) and is therefore unhelpful.

What Sinatra example are you referring to here? I'd like to take a
look. I've personally never used before filters to mess with cache
headers.

> I've got a simple flat-file system going for my articles, and lets say
> that I want to cache them for 1 hour unless modified.  Obviously this
> is easy to do with File.mtime, but where do I send the headers?  I
> *could* use before-do, but I have other routes that need a different
> logic to cache and I'd rather avoid if-then-else and case statements.
>
> If I do,--
>
>> get '/article/:id/ do
>>   expires 1.hour # sets response["Expires"] to the httpdate of 1.hour from Time.now
>>   last_modified article_mtime(params[:id]) # File.mtime().httpdate...
>>   haml :"articles/#{params[:id]}.haml"
>> end
>
> Obviously I'll still generate a HTML response, which might not even
> get sent due to caching.

That's not true, actually. The #last_modified methods exits the route
block immediately with a 304 Not Modified response if the request
includes an If-Modified-Since header that matches the date specified.
Both #last_modified and #etag have this behavior. Sinatra has a "halt"
primitive that uses throw to exit the route block immediate. See the
#last_modfied source for more:

http://tinyurl.com/mkumeg

> But how do I know?  Can I,--
>
>> get '/article/:id/ do
>>   expires 1.hour
>>   last_modified article_mtime(params[:id])
>>   haml :"articles/#{params[:id]}.haml" unless response.fresh?
>> end
>
> Part of the trouble is that I've got semi-static routes with content
> to be generated every 6/12/24 hours with different reasoning on when
> it was last modified.  You can guess how fast before-do will become
> messy...

Does understanding the halting behavior of last_modified and etag
clear this up? I wouldn't suggest using before filters for any of this
stuff.

> In other words, just how do I integrate with Rack::Cache?

Sounds like you're doing just fine :) You might also appreciate the
X-Rack-Cache response header, which you can see easily using a http
monitoring tool like firebug's network panel. The header includes
basic information on how the cache handled the request (whether it was
fresh or stale, validated, etc).

Thanks,
Ryan

TheWickedFlea

unread,
Jun 8, 2009, 8:59:21 AM6/8/09
to rack-cache
Hey Ryan,

On Jun 8, 2:24 am, Ryan Tomayko <r...@tomayko.com> wrote:
> > In other words, just how do I integrate with Rack::Cache?
>
> Sounds like you're doing just fine :) You might also appreciate the
> X-Rack-Cache response header, which you can see easily using a http
> monitoring tool like firebug's network panel. The header includes
> basic information on how the cache handled the request (whether it was
> fresh or stale, validated, etc).

Yes, it took about 2 days of scattered try-rescue-retry style
programming. ;-) So I wound up with a helper method like this,--

> def cached(options={})
> privacy = options[:privacy] || 'public'
> max_age = options[:max_age] || 5.minutes
>
> response['Cache-Control'] = "#{privacy},max-age=#{max_age}"
>
> response['ETag'] = request.path_info.crypt('0E')
> end

And you'll note that I didn't use the etag helper that came with
Sinatra, the reason being that if my page has been updated and
last_modified lets it past then the etag will kill it given the
standard headers that are sent. And I frequently take a read through
an article a day later and have to correct spelling, word form, etc.
I'd rather visitors who return hit the new version of the content, as
you can guess.

> On Fri, Jun 5, 2009 at 2:58 PM, TheWickedFlea<thewickedf...@gmail.com> wrote:
> > I'm using Sinatra 0.9.2 and have a basic website set up, and I want to
> > add the caching in.  My trouble is that I don't understand precisely
> > where, and when, to put my headers and when to generate a response.
> > The documentation is too nebulous about this, and the Sinatra example
> > is illogical to me (why use before-do, when the routes may have
> > convoluted logic?) and is therefore unhelpful.
>
> What Sinatra example are you referring to here? I'd like to take a
> look. I've personally never used before filters to mess with cache
> headers.

I saw it quoted elsewhere and went to the source, but only saw the
date later. It was from way back in February.

http://github.com/rtomayko/rack-cache/blob/42f7266995f54df0c5da54ddea0441ba3b72745f/example/sinatra/app.rb

> > If I do,--
>
> > Obviously I'll still generate a HTML response, which might not even
> > get sent due to caching.
>
> That's not true, actually. The #last_modified methods exits the route
> block immediately with a 304 Not Modified response if the request
> includes an If-Modified-Since header that matches the date specified.
> Both #last_modified and #etag have this behavior. Sinatra has a "halt"
> primitive that uses throw to exit the route block immediate. See the
> #last_modfied source for more:

Yeah, I found that out by wandering around the Sinatra source code, so
I do that directly in my get handler because I need the params hash.


All in all, it wasn't too hard to figure the rest out, but it took
some definite trial and error. Maybe I should write a tutorial about
this or something, seeing that there seems to be a shortage of usage
examples for Rack::Cache for Sinatra.

Thanks for the quick response.

-- Flea
Reply all
Reply to author
Forward
0 new messages