Using Cloudfront with Active Storage
Asked Answered
H

2

13

I am building a website using Ruby on Rails. To upload images I am using Active Storage and Amazon S3. All's good here. Users can upload images and images are viewable on the website (images are public).

Now, in production, the url for images are: https://example.com/rails/active_storage/representations/1ej21h...

Which return a 302 to the S3 bucket: https://my-bucket.amazonaws.com/variants/9jdh2...

I am not a big fan of:

  • the two roundtrips to get the image ;
  • sending requests for images to the Rails server ;
  • the feeling of sluggishness on these images.

And I would rather like to use Cloudfront to serve these images.

I searched in Rails Guides, on Google and StackOverflow, but didn't find a proper answer so far.

Is there any solution at this time to use Cloudfront with Active Storage?

Edit: More context: Each image will be loaded 1000 times by minute at least under normal traffic and from different countries. I don't want to put the server under this pressure (it has other requests to process). And I want users to load these images as fast as possible. Hence Cloudfront as a the CDN for these images (public images, no need to get a signed url).

Hypoacidity answered 12/9, 2018 at 10:24 Comment(3)
Is what you're referring to the fact that rails will hit the server to let active_job generate a new signed url to your bucket? This causes the images to blink on page load while the front end waits on the server to generate the signed url. There is a patch you can apply to fix this. Can you confirm if this is what youre talking about?Kei
@Kei The image does not blink: its quite slow to load and the requests for images are processed by the rails server (indeed to generate a new signed url). I don't need any signing, images are public. When a user display a page, I want the url for images displayed by the server to be my-distribution.cloudfront.net/my-image-url.jpg. So the rails server does not need to process unnecessary requests for images (eg "bypassing signing").Hypoacidity
Check out my answer below. Does this help?Kei
K
1

Try this...

in controllers/active_storage/representations_controller.rb <--create if it doesnt exist. You should put...

module ActiveStorage
  class RepresentationsController < BaseController
    include ActiveStorage::SetBlob

    def show
      expires_in 1.year, public: true
      variant = @blob.representation(params[:variation_key]).processed
      send_data @blob.service.download(variant.key),
            type: @blob.content_type || DEFAULT_SEND_FILE_TYPE,
            disposition: 'inline'
    end
  end
end

Then when you call an image using @model.image.variant(resize: '250x250') making sure to substitute your desired dimensions. This is a hack for now. This should be fixed by rails 6 release I think.

Kei answered 12/9, 2018 at 10:43 Comment(10)
Thanks @Verty00, but it does not answer my question and does not solve the issue. I don't want images to be requested through the RepresentationsController at all.Hypoacidity
Thiscomes very close. It only signs a url the first time. Then every sunsequent request goes directly through bucketKei
Yes. but it will still put pressure on the server. S3 buckets are not meant for distributing content. If I have 10 images on a page and this page is requested 1k times, I will still get 1k + 10k requests to the server.Hypoacidity
I don’t think that’s accurate. It’s 1k requests with no more than 10 queries to you DB on each request to access the blob and retrieve the already signed url. Maybe try implementing a cacheing strategy.Kei
Yes for the main page. But then each user will also trigger a request for each image with the url "/rails/active_storage/representations/". Which trigger a call to the RepresentationsController action (hence the +10k server requests, I am not talking about DB queries here that can be cached).Hypoacidity
I don’t think you understand what this code does. There are definitely not 10K server requests. This code allows you to retrieve a pre-signed blob without hitting the representations controller on each request. Try it. I think you’ll find it does exactly what you need it to do.Kei
Sorry for the confusion, indeed the Representations Controller will not be called 10k times. Still, I will receive 10k requests to my server that are going to leverage Rack:Cache, right? I could use either memcached or Redis to cache these pages, but I do not want that (as stated in the context of my question, each image should use the Cloudfront url not a rails one). Also, with you answer I am still relying on S3 to serve files, which is not a viable solution.Hypoacidity
Did you get anywhere with this @gpichot? Same situatuon where I have a Rails app with images in ActiveStorage saved to S3 and I want to serve using CloudFront. You are right that nobody should be serving images over S3 to their end users.Pulmonate
@PaulWatson At some point, I looked to the following Github Issue github.com/rails/rails/issues/31419 and started to "patch" ActiveStorage S3 backend using some of gists that people proposed in this thread. I was able to return a cloudfront url for my assets which can be sufficient enough, but those were not "clean" (no extension in the name). But I finally rolled back to nothing, the business value being less important than other features, and preferring to have something not perfect than something hackish...Hypoacidity
gist.github.com/timm-oh/10c4f06effa536ff32c5d038e0dd57e1 Please feel free to leave comments as you see fit :)Bereniceberenson
B
0

I always put my load balancers behind a cloudfront distribution and I put the Route53 domain pointing to the distribution, not to the load balancer. This way you can cache any behavior you want. You can cache of course any request to rails/active_storage/representations/redirect/* and it will preserve any cache headers the application server returns in the first time the image is retrieved.

Battles answered 3/2, 2021 at 12:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.