Skip to content

Instantly share code, notes, and snippets.

@timm-oh
Last active December 17, 2023 21:04
Show Gist options
  • Save timm-oh/10c4f06effa536ff32c5d038e0dd57e1 to your computer and use it in GitHub Desktop.
Save timm-oh/10c4f06effa536ff32c5d038e0dd57e1 to your computer and use it in GitHub Desktop.
Rails 5.2.3: Cache Active Storage Blobs and Variants through Cloudfront

Basic Usage

# ENV["ASSETS_HOST"] = 'thing.cloudfront.net'
class Foo < ApplicationRecord
  has_one_attached :thing
end

foo = Foo.first

# https://thing.cloudfront.net/rails/active_storage/blobs/etc..etc
proxy_url(foo.image)

# https://thing.cloudfront.net/rails/active_storage/representations/etc..etc
proxy_url(foo.image.variant(resize: '100x100'))

This is nowhere near perfect. Just gets the job done in a simple manner. Inspiration from https://github.com/rails/rails/pull/34477

# app/helpers/application_helper.rb
module ApplicationHelper
# active_storage_item could be a blob or variant object
def proxy_url(active_storage_item, options = {})
options.merge!(host: ENV['ASSETS_HOST']) if ENV['ASSETS_HOST'].present?
# proxy: 'true' allows you to stil have the original functionality while
# being able to proxy through a CDN. You've got to ensure that your CDN
# forwards this param otherwise active storage will always do the default
# behavior which is a redirect to the service.
# This is also meant to be intended for items that are always going to be
# public. If you have files that might be private then there's no point in
# caching it with cloudfront.
# See lib/core_extensions/active_storage/blob/downloader and
# lib/core_extensions/active_storage/representation/downloader
polymorphic_url(active_storage_item, options.merge(proxy: 'true'))
end
end
# lib/core_extensions/active_storage/blob/downloader.rb
# had to make the file a different name because reasons ¯\_(ツ)_/¯
module CoreExtensions
module ActiveStorage
module Blob
module Downloader
include CoreExtensions::ActiveStorage::Headers
def show
return super unless params[:proxy] == 'true'
set_headers(@blob)
@blob.download do |chunk|
response.stream.write(chunk)
end
ensure
response.stream.close
end
end
end
end
end
# config/initializers/core_extentions.rb
Dir[File.expand_path(File.join(File.dirname(File.absolute_path(__FILE__)), 'lib/core_extensions')) + "/**/*.rb"].each do |file|
require file
end
ActiveStorage::RepresentationsController.prepend CoreExtensions::ActiveStorage::Representation::Downloader
ActiveStorage::BlobsController.prepend CoreExtensions::ActiveStorage::Blob::Downloader
# lib/core_extensions/active_storage/headers.rb
module CoreExtensions
module ActiveStorage
module Headers
extend ActiveSupport::Concern
private
def set_headers(blob)
# Hard coded this to 365, simply because thats what I want tbh
expires_in 365.days, public: true
response.headers["Content-Type"] = blob.content_type
# Commented this out because in Rails 5.2.3 this isn't a thing
# response.headers["Content-Disposition"] = ActionDispatch::Http::ContentDisposition.format(
# disposition: params[:disposition] || "inline",
# filename: blob.filename.sanitized
# )
end
end
end
end
# lib/core_extensions/active_storage/representation/downloader.rb
# had to make the file a different name because reasons ¯\_(ツ)_/¯
module CoreExtensions
module ActiveStorage
module Representation
module Downloader
include CoreExtensions::ActiveStorage::Headers
def show
return super unless params[:proxy] == 'true'
set_headers(@blob)
variant = @blob.representation(params[:variation_key]).processed
@blob.service.download(variant.key) do |chunk|
response.stream.write(chunk)
end
ensure
response.stream.close
end
end
end
end
end
@yhk1038
Copy link

yhk1038 commented Jun 4, 2020

In application_helper.rb, the second parameter options of the method proxy_url should have default value {} like,

# on above
def proxy_url(active_storage_item, options)
end

# should be
def proxy_url(active_storage_item, options = {})
end

To proxy_url(attachable) work.

@timm-oh
Copy link
Author

timm-oh commented Jun 4, 2020

Hey @yhk1038,
You're definitely correct, I'll make the change. 👍

@cyrusstoller
Copy link

cyrusstoller commented Feb 13, 2021

Any tips on how to handle this in rails v6.1?

I added this to my production.rb

  config.action_controller.asset_host = ENV['CLOUDFRONT']
  config.active_storage.service = :amazon
  config.active_storage.delivery_method = :proxy

And then tweaked your helper to

  def proxy_url(active_storage_item, options = {})
    options.merge!(host: ENV['CLOUDFRONT']) if ENV['CLOUDFRONT'].present?
    polymorphic_url(active_storage_item, options)
  end

@siklodi-mariusz
Copy link

I don't think this is needed anymore, at least not from rails 6.1 and up. Proxying was added to ActiveStorage.

More details here:
rails/rails#34477
https://github.com/rails/rails/blob/main/activestorage/README.md#proxying
https://guides.rubyonrails.org/configuring.html#configuring-active-storage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment