Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails ActiveStorage: how to avoid one redirect for each image?

If you use ActiveStorage and you have a page with N images you get N additional requests to your Rails app (i.e. N redirects). That means wasting a lot of server resources if you have tens of images on a page.

I know that the redirect is useful for signed URLs. However I wonder why Rails does not precompute the final signed URL and embed that into the HTML page... In this way we could keep the advantages of signed URLs / protected files, without making N additional calls to the Rails server.

Is it possible to include the final URL / pre-signed URL of image variants directly in the HTML (thus avoiding the redirect)? Otherwise, why is that impossible?

like image 756
collimarco Avatar asked Nov 23 '19 22:11

collimarco


2 Answers

After days of reasoning and tests, I am really excited of my final solution, which I explain below. This is an opinionated approach to images and may not represent the current Rails Way™️, however it has incredible advantages for websites that serve many public images, in particular:

  1. When you serve a page with N images you don't get 1 + N requests to your app server, instead you get only 1 request for the page
  2. The images are served through a CDN and this improves the loading time
  3. The bucket is not completely public, instead it is protected by Cloudflare
  4. The images are cached by Cloudflare, which greatly reduce your S3 bill
  5. You greatly reduce the number of API requests (i.e. exists) to S3
  6. This solution does not require large changes to Rails, and thus it is straightforward to switch back to Rails default behavior in case of problems

Here's the solution:

  1. Create an s3 bucket and configure it to host a public website (i.e. call it storage.example.com) - you can even disable the public access at bucket level and allow access only to the Cloudflare ips using a bucket policy
  2. Go to Cloudflare and configure a CNAME for storage.example.com that points to your domain; you need to use Flexible SSL (you can use a page rule for the subdomain); use page rules to set heavy caching: set Cache Everything and set a very long value (e.g. 1 year) for Browser Cache TTL and Edge Cache TTL
  3. In you Rails application you can keep using private storage / acl, which is the default Rails behavior
  4. In your Rails application call @post.variant(...).processed after every update or creation of @post; then in your views use 'https://storage.example.com/' + @post.variant(...).key' (note that we don't call processed here in the views to avoid additional checks in s3); you can also have a rake task that calls processed on each object, in case you need to regenerate the variants; this is works perfectly if you have only a few variants (e.g. 1 image / variant per post) that are changed infrequently

Most of the above steps are optional, so you can combine them based on your needs.

like image 187
collimarco Avatar answered Oct 06 '22 00:10

collimarco


You can use the service_url to create direct links to your resources.

We don't use Rails views in our project so my knowledge about the view layer is rusty. I think you could put it in a dedicated helper and then use it from your views.

like image 26
ekampp Avatar answered Oct 05 '22 23:10

ekampp