ruby-on-railsamazon-s3aws-sdkshrineaws-sdk-ruby

browser caching shrine s3 private attachments


I have just created a private bucket on s3, to contain user profile pictures. With a public bucket, all of the images are properly cached (previous paperclip config with same settings).

I have the following shrine initializer:

s3_options = {
  access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  region: ENV['AWS_REGION'],
  bucket: ENV['S3_BUCKET_NAME']
}

Shrine.storages = {
  cache: Shrine::Storage::FileSystem.new('tmp', prefix: 'uploads/cache'),
  store: Shrine::Storage::S3.new(**s3_options)
}

Shrine.plugin :activerecord
Shrine.plugin :logging
Shrine.plugin :determine_mime_type
Shrine.plugin :cached_attachment_data
Shrine.plugin :restore_cached_data
Shrine.plugin :delete_promoted
Shrine.plugin :delete_raw
Shrine.plugin :remove_invalid

And the following uploader:

class AvatarUploader < Shrine
  plugin :pretty_location
  plugin :processing
  plugin :upload_options, store: {
    acl: 'private',
    cache_control: "max-age=604800",
  }
end

The CacheControl gets properly set to 1 week on the s3 object and the same is visible in the response. I have noticed that on each request the signed url differs regarding the X-Amz-Signature hash, which most likely causes the cache-miss (Etag is the same for each request). I presume this is the reason it isn't working, but I have no clue on how to make the X-Amz-Signature the same while the object isn't expired.


Solution

  • The ruby S3 SDK will generate a new signature every time you generate a signed URL, this is by design.

    You should either

    a) stick with public, non-signed requests

    b) cache the signed url, so that a new signature won't be generated on every call, but expires before the signature expires

    Something like this might do the trick

    def url
      Rails.cache.fetch("url", self, expires_in: 6.days) do
        super(public: false, expires_in: 1.week)
      end
    end