[ActiveStorage] add option for proxying files to allow CDN caching

ActiveStorage should give users the option to proxy files. This would allow files to be cached by a CDN (image load times for my application went from 500+ms to 50ms) :rocket:. Something along the lines of this is what I’m proposing:

frozen_string_literal: true

module ActiveStorage

class BlobsController < BaseController

include ActiveStorage::SetBlob

def show

  if ActiveStorage.redirect_to_service_url

    expires_in ActiveStorage.service_urls_expire_in

    redirect_to @blob.service_url(disposition: params[:disposition])

  else

    stream_blob

  end

end

def stream_blob

  expires_in ActiveStorage.service_urls_expire_in, public: true

  response.headers['Content-Type'] = params[:content_type]

  response.headers['Content-Disposition'] = params[:disposition]

  @blob.download do |chunk|

    response.stream.write(chunk)

  end

ensure

  response.stream.close

end

end

end

``

Users looking for image loading with a CDN could set this up by setting

app.config.active_storage.redirect_to_service_url = false
app.config.active_storage.service_urls_expire_in = 1.year

``

I just threw this together for a project I’m working on, but I would be happy to clean this up and make a proper pull request if the Rails team is open to the idea. There’s a fair amount of people requesting this feature https://github.com/rails/rails/issues/31419 some of the solutions in this issue are directly linking to the service files, but streaming would keep the service provider abstracted away while also allowing for proper CDN caching.

+1 for supporting publicly-available files. We’re almost certainly going to do that at some point.

We needn’t tie it to proxying downloads, though. For larger apps, it’s no more desirable to take on the substantial cost of proxying for public files any more than for private ones. You can put a CDN in front of a public S3 or GCS bucket.

The large cost really depends on your cache hit rate, for my personal apps and my employer (a high traffic e-commerce site) static assets have low miss rate as they never change and have long expiring headers. How about giving the user the option, while keeping redirect as the default?

ActiveStorage.delivery_method = :redirect # with options to set it to :proxy or :direct

``

redirect:

  • default

  • semi private

  • keeps provider abstraction

  • slow image loading

  • not CDN friendly

  • keep existing service_urls_expire_in
    proxy:

  • CDN friendly

  • no configuration when used with cloudflare and other similar CDNs

  • keeps provider abstraction

  • could increase application server load

  • defaults to a long service_urls_expire_in
    direct:

  • CDN friendly

  • it takes more configuration than the proxy method

  • breaks provider abstraction

  • Changing providers will require a fair amount of config changes to work with CDN

  • less load on application server

  • defaults to a long service_urls_expire_in

I’m okay with adding proxy support in general, but a global setting seems less than ideal (unless it’s paired with a way to override it on a per-file basis).

Global with individual override would be perfect! I’ll try to put this together and make a PR in the next couple days/weeks. Btw, David has been grilling everybody on Twitter lately about working too much, might want to be careful replying to this mailing list on a Friday night…:grinning:

Hello, if George or somebody else has time to review this pr https://github.com/rails/rails/pull/34477 it would be greatly appreciated.