2

So ActiveStorage works perfectly.

I have now turned on CDN on DigitalOcean Spaces, with a personal subdomain like: https://cdn.my-website.com

I have been following this Rails official doc which states to have the following code in config/routes.rb:

# config/routes.rb
direct :cdn_image do |model, options|
  if model.respond_to?(:signed_id)
    route_for(
  :rails_service_blob_proxy,
  model.signed_id,
  model.filename,
  options.merge(host: ENV['CDN_HOST'])
)
else
  signed_blob_id = model.blob.signed_id
  variation_key  = model.variation.key
  filename       = model.blob.filename

  route_for(
    :rails_blob_representation_proxy,
    signed_blob_id,
    variation_key,
    filename,
    options.merge(host: ENV['CDN_HOST'])
  )
end

end

Now, the problem is: the URL generated by calling cdn_image_url(user.profile_image) looks like the following:

"https://cdn.my-website.com/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0TWpBME9EUmxNaTA0WVRobExUUmxORGd0T0RJellpMWtOMk5rWVRVMlpXTmpaV1VHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--322044941e3418863e6aeff7ce802d16afe7dfb0/profile_image.png"

This URL will send the request the https://cdn.my-website.com/ and there, no Rails app is hosted. Shouldn't we use the original host here which points to the Rails app itself? Or I'm missing something obvious here.

Arslan Ali
  • 17,418
  • 8
  • 58
  • 76

2 Answers2

2

I see two options:

  1. Preferred. Configure your CDN to point to your Rails app (/rails/active_storage/blobs/proxy/ endpoint), not the buckets directly. This approach allows you to cache not only uploads but variants as well. It seems like this is the method that Rails developers prefer, which is probably why they've included that routes-snippet in the documentation.
  2. If you have a specific use case and need to use a CDN over S3 (and not over the app), you can configure your routes in the following way:
direct :cdn_image do |blob|
  File.join(ENV['CDN_HOST'], blob.key)
end

so you will get direct links by using cdn_image_url helper

Eugene
  • 46
  • 4
1

I've been playing around with the Proxy feature in ActiveStorage and love to share what I've found. I used this repo for my experiments: https://github.com/saiqulhaq/rails-cdn-proxy-example. I also struggled to understand why there is .merge(host: ENV['CDN_HOST']) in the cdn_image route. Finally, I found that it's more related to the architecture.

You will get the benefit from the Proxy feature if you use this architecture:

S3 -> Rails -> Cloudflare <- Internet

With this setup, Cloudflare will cache your images at no cost. However, you might be troubled if you add your S3 endpoint directly to Cloudflare's DNS record as a CNAME. Here's an example using hypothetical domains:

Let's say your domain is saiqulhaq.com, your CDN domain is cdn.saiqulhaq.com, and your S3 endpoint is saiqulhaq-cdn.s3-website-ap-southeast.amazonaws.com. If you try to add the S3 endpoint to Cloudflare as a CNAME record to cdn.saiqulhaq.com, S3 will return an error saying "bucket not found".

If we take a look at the original Pull Request by Fleck here: https://github.com/rails/rails/pull/34477 (https://github.com/rails/rails/blob/7d1c4dd7be1b51e2dcbb3614b68dc98c2926d28c/activestorage/app/controllers/concerns/active_storage/streaming.rb#L61). We can find that the Rails app downloads the image from S3 (which incurs charges for S3 storage, data transfer, and one-time request cost), but then Cloudflare caches the image for free.

On the other hand, if you use Cloudfront (S3 -> Cloudfront <- Internet), you will be charged for S3 storage, Cloudfront request cost, and Cloudfront data transfer out cost. The data transfer out cost is incurred each time there is a new request for your CDN object.

So, the Proxy feature of ActiveStorage is a pretty neat tool if you're using Cloudflare. I've focused on Cloudflare and Cloudfront in my response because those are the CDN services I've worked with.

Saiqul Haq
  • 2,287
  • 16
  • 18
  • I'm very keen to try your setup. Let me try that, and then I get back to you here. – Arslan Ali Jun 01 '23 at 11:36
  • It doesn't answer my original question: the generated URL looks like: "cdn.saiqulhas.com/blob/proxy/...." The point is: at cdn.saiqulhaq.com no domain is registered. – Arslan Ali Jun 02 '23 at 10:28
  • "blob/proxy" is how Rails names the route for Active Storage with the Proxy feature, is there any concern for you? What is your expectation? cdn.saiqulhaq.com is just an example – Saiqul Haq Jun 03 '23 at 05:49
  • I know: "cdn.saiqulhaq.com" is just an example. The Rails application isn't hosted at cdn.example.com, so how would it interpret "blob/proxy" URL? Could you please share your final URL with me for a single asset at S3? – Arslan Ali Jun 03 '23 at 10:38