Caching Images Using Attachment_Fu and Amazon S3

Caching images improves the user experience and reduces S3 costs.  It improves the user’s experience because web pages load quicker and it reduces S3 costs since you have fewer transfers.

When using the attachment_fu plugin and the AWS::S3 gem, there are two things we need to do to cache images using attachment_fu and Amazon S3.

  1. Set the Cache-Control http header.
  2. Make S3 urls expire at a consistent time.

Setting the Cache-Control header tells our web browser that the cached image is valid and that it doesn’t need to check for an updated copy of the image on the server.  Making the url expire at a consistent time gives our images that are the same a consistent url, so our browser doesn’t get confused and think they are different images.  For example, image.jpg?expires=100 appears to our browser as a different image than image.jpg?expires=101.

Set Cache-Control Header in AWS S3

The technique we use for setting the Cache-Control header was originally posted in this guide. We’ll enhance the AWS::S3 store method to add a Cache-Control header that’s 10 years in the future if there isn’t already a Cache-Conrol header present.  The first step is to create a file in your lib/ folder and name it s3_cache_control.rb.

# Adjusts the cache control to the maximum value of 10 years (315360000 seconds)
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

Make S3 URLs Expire at a Consistent Time

We’re going to make generated S3 URLs expire at a consistent time by overriding attachment_fu’s authenticated_s3_url method.  You can view the original authenticated_s3_url method.  We’re going to override it to add an expires option in there isn’t one present.  Place this code in lib/s3_cache_control.rb.

require 'technoweenie/attachment_fu/backends/s3_backend'
  module Technoweenie
    module AttachmentFu
      module Backends
        module S3Backend
        def authenticated_s3_url(*args)
          options = args.extract_options!
          options[:expires] = Time.now.advance(:minutes => 5).end_of_day.to_i if options[:expires].blank? and options[:expires_on].blank?
          thumbnail = args.shift
          S3Object.url_for(full_filename(thumbnail), bucket_name, options)
        end
      end
    end
  end
end

You can set expires to whatever value you would like, as long as its consistent.  In our case it made since to do it at the end of the day. But it could be at the end of the hour if you didn’t want photos cached long, or at the end of the week if you wanted them cached longer.

Now just add these two lines to your config/environment.rb file and you’re set:

require 'aws/s3'
require 's3_cache_control'

Leave a Reply