Cache-Control Header for Amazon S3
Friday, July 27th, 2007Or “How to set a far future Expires header in S3 to appease the YSlow gods”.
I’m working on a Ruby on Rails site that stores images and other static content on Amazon S3. We want Amazon to serve all of our images with a Cache-Control or Expires header set to a point in the very far future. This will avoid unnecessary HTTP requests on subsequent page views, making the site faster for users and consuming less bandwidth.
Amazon provides an option for specifying the Cache-Control header, but we use the AWS::S3 gem and the attachment_fu plugin for uploading our files to S3. The gem and plugin don’t provide a convenient way to set the Cache-Control header. My solution is to enhance the behavior of the store() method within the AWS::S3 gem so that it always specifies a Cache-Control header of 10 years if another value is not specified. Here’s my patch, which I placed in a file called s3_cache_control.rb in the lib directory of my rails project:
module AWS
module S3
class S3Object
class << self
def store_with_cache_control(key, data, bucket = nil, options = {})
if (options['Cache-Control'].blank?)
options['Cache-Control'] = ‘max-age=315360000′
end
store_without_cache_control(key, data, bucket, options)
end
alias_method_chain :store, :cache_control
end
end
end
end
In my config/environment.rb file, I added the following lines to load my patch:
require 'aws/s3'
require 's3_cache_control'
Restart your server, and from now on, anything stored to S3 via the AWS::S3 gem will automatically get a Cache-Control header with max-age set to 10 years. Rockin’ tacos.
But what about all those existing images our users have already uploaded? Those need to be updated too, so I added a method to my Photo model which iterates through all photos and sets the Cache-Control. Here’s the method:
def self.set_cache_control
photos = Photo.find(:all)
photos.each do |photo|
begin
s3_object = AWS::S3::S3Object.find(photo.full_filename,
'your_bucket_name')
s3_object.cache_control = 'max-age=315360000'
s3_object.save({:access => :public_read})
rescue Exception => e
logger.error("Unable to update photo with key " +
"#{photo.full_filename}: #{e}")
end
end
end
You can run the update using script/runner:
$ RAILS_ENV=production ./script/runner Photo.set_cache_control
The set_cache_control() method assumes you have a full_filename() method on your Photo class that provides the S3 key. You’ll already have the full_filename() method if you’re using attachment_fu. You’ll also need to replace your_bucket_name with your Amazon S3 bucket name in the code above.
Now you can sing Cache-Control to Major Tom like I’ve been doing all afternoon. In my head. I’ve only been singing it in my head. Mostly.