Setting Asset Cache Expiration in Rails / Cloudfront / Heroku - ruby-on-rails

I'm getting errors from the Google pagespeed tool saying that cache expiration is not specified for my assets.
I'm serving asserts through cloudfront, and my site (Ruby on Rails) is hosted on Heroku
https://www.driverhunt.com/…9a42dc64767309bbb76af8a1eaef10b3c0cb.css (expiration not specified)
https://www.driverhunt.com/…74901ff9e954e9fca26f71542e8fc8d807684.js (expiration not specified)
https://www.driverhunt.com/…ad7b9e065ed95559bdf1c426bc94834f09e0.jpg (expiration not specified)
Firstly, it looks like things AREN'T being served by cloudfront. Second:
Is this a cloudfront issue, a heroku issue, or a rails issue? Can it be solved programatically, or do I need to fiddle with the heroku/cloudfront settings?

Set the response header
To enable browser caching you must set the Cache-Control and the Expires response header.
Debugging tip
To see your existing cache settings, use your browser's debugger. For Chrome, while on any page in your app, open the debugger and look under:
It's important to see what effect your code changes make. Run this on your app as is and get a baseline Response header.
Setting the response headers
When you have access to the server config files setting caching on Linux running Apache or Nginx is simple. But it's my experience that Heroku does not permit you to modify server config files directly.
Without the capability to modify .htaccess or nginx.conf on the server you have 2 options:
1- Implement caching for your rails app at the controller level, e.g.:
def show
#company = Company.find(params[:id])
expires_in 3.minutes, :public => true
# ...
end
Which is an admittedly hideous solution.
2- The other option Heroku offers is HTTP caching using Rack::Cache (Memcachier Heroku add-on). I have no experience with this, so no comment.
Conclusion
Heroku provides a wonderful and simple deploy environment, but to offer that simplicity, they have locked down configuration. I used Heroku to develop and deploy an app in test mode, but moved to a VPS where I could implement server side optimizations, such as caching to launch the app.
P.S. To address the CDN part of the question: Since it is your sever setting the response headers, I don't think the CDN config impacts caching.

Your assets are being served through your rails app. The relevant setting is config.action_controller.asset_host which should be set to point to your cloudfront distribution, for example
config.action_controller.asset_host = '//d123467890.cloudfront.net'
This assumes that your cloudfront distribution is already configured to serve your assets (for example from a bucket populated by asset_sync)

Related

Heroku + Rails4.2 : Cloudfront setup

I am trying to setup Cloudfront for my heroku app. The documentation seems to be lacking to stand independently.
Here are the steps I followed:
1. Setup Cloudfront in AWS console
2. Added cloudfront domain name to production.rb `config.action_controller.asset_host = 'XXXX.cloudfront.net'`
3. Set `config.assets.compile = true` in production.rb
4. Verified AWS_SECRET_ACCESS_KEY is correct in heroku config
5. I have added `gem 'rails_12factor', group: :production`
None of assets load anymore. Any step I am missing in the setup?
Update1:
In the chrome debugger the asset is correctly requested from cloudfront from this url: http://XXXXX.cloudfront.net/assets/application-22c7c249df1a24541d86603b0715eefe.css
However in the request header see a Status Code:302 Moved Temporarily. I am wondering if I have a redirect loop and how I can debug it.
Update2
Thanks everyone for the suggestions. Some more info:
When I try to download the asset from my app, I get a redirect to home page on browser but using curl I am able to get the asset. ex: curl 'http: //www.myapp.com/assets/application-c9a778bb55ad4152d956fd34fe6f7839.css'
The app doesnt use SSL. However I have still set Origin Protocol Policy to Match Viewer as per #Omar's suggestions
I tried to download the asset from my app on browser and am able to access the assets. ex: 'http: //www.myapp.com/assets/application-c9a778bb55ad4152d956fd34fe6f7839.css'
However trying to access the assets directly on cloudfront (d1ax5oefcdtdki.cloudfront.net/assets/application-c9a778bb55ad4152d956fd34fe6f7839.css) redirects it to myapp.com
Screenshots for cloudfront DS:
https://www.dropbox.com/s/bkg480d4it6zl2r/Screenshot%202015-12-06%2014.01.28.png?dl=0
http://glui.me/?i=7ah73hffrhvmpt7/2015-12-06_at_2.02_PM.png/
https://www.dropbox.com/s/dd4wwgm3md8w7qn/Screenshot%202015-12-06%2014.05.20.png?dl=0
For anyone else having issues debugging cloudfront.
The problem was Cloudfront had cached redirects (prob bec of wrong setup). After invalidating the cache I was able to force CF to fetch assets from my app and serve them.
When you request the asset for the first time, cloudfront checks whether the file is cached or not so for example you request:
http://XXXXX.cloudfront.net/assets/application-22c7c249df1a24541d86603b0715eefe.css
for the first time cloudfront will give a cache miss and then it will pull the file from it equivalent path from rails. So that the next time you request the same file, it will be already cached.
In order for this to work you need to make sure that you have everything setup correctly.
From rails side there is nothing much to do except setting the assets_host in production.rb. As you already have the rails_12factor gem there is no need to add the config.assets.compile = true. From the documentation of the gem you can see in the how section that it add serving static assets "the documentation".
From cloudfronts side that is where I think you are facing a problem, you need to set some settings to let cloudfront know how it can communicate with your rails app when the cache misses. In the cloudfront setting you need to check the
Origin Domain Name to be the url of your rails app.
Origin Protocol Policy to Match Viewer
Distribution State to Enabled
Also there are some other settings there that can help you optimize your content delivery caching.

Rerouting to AWS hosted assets from a Heroku Rails app

I have a Heroku hosted Rails app that has reached the 300MB limit for the slug size and can no push to Heroku. To fix this issue, I've setup an AWS S3 account and want to redirect the assets being requested from my Rails app to the new S3 location. The Rails app is basically just serving JSON files that point to the static assets using a relative URL. An iOS app using the Rails JSON then has a hardcoded domain URL, and appends the path to the resources to that domain and requests assets it needs.
I want to update my Heroku app and change my asset locations without requiring an update to the iOS app in order to change the asset domain. So, I need to redirect static asset requests from the Rails app to the AWS server.
In my git repo, I've ignored the assets in the public folder that I've moved to the AWS server. The asset files are present on my local machine, but are not part of the git repo when uploaded to Heroku.
So far I've tried changing the config.action_controller.asset_host which does not seem to work since these are static assets and are being returned by the web server before Rails gets it.
I tried using routes rules to redirect to a different domain, but these routes never seem to be captured. The static files appear to be returned before the Rails app has a chance to handle the request.
I've tried using the rack-rewrite gem to try and redirect my assets to a different domain with the following in `initializers/rack_rewrite.rb:
require 'rack/rewrite'
AppNamespace::Application.config.middleware.insert_before(Rack::Lock, Rack::Rewrite) do
r301 %r{/images(.*)}, 'http://my-subdomain.amazonaws.com/images$1'
end
This doesn't seem to work either and it always just returns the static files.
So far I've been trying for hours to figure out the proper way to handle this situation. I'm not a Rails developer by trade, I build iOS apps, so please let me know if I'm going about this the wrong way or completely missed the "right" way of doing this.
I solved this using routes and the 'rails_serve_static_assets'. I had to move the local images out of the public folder in order to avoid Nginx from returning the images before hitting the Rails app. The route ended up like so:
match '/images/(*path)', :to => redirect { |params, request|
"#{Rails.configuration.assets.aws_url}#{request.path}"
}

How do I get my rails assets to be served by cloudflare instead of my server?

I'm running a rails app on heroku and recently switched to cloudflare for CDN for asset serving. My understanding was that once I use cloudflare, my assets (ie js,css,images) would be served from cloudflare and not from my own server but in my heroku logs I still see the requests for assets. Do I need to configure something in my rails app like setting the asset_host or something? Thanks.
It might be helpful to clarify what CloudFlare caches by default as well (just to make sure you know that not every file extension is automatically cached).
Note: Caching also depends on how many requests we get for the resources before it gets cached (minimum of three).

When the protocol is https, Amazon S3 assets won't load, - Rails, Heroku

I set up asset_sync gem on heroku, following this URL: https://github.com/rumblelabs/asset_sync
The settings are working and I uploaded all the static assets at S3.
The problem is, when I open page through https protocol, can't access any of the assets, because the browser returns "This Connection is Untrusted". (same with Chrome & Firefox).
Every assets will be able to use after I admit access to s3 assets url. https://myapp.asset.s3.amazonaws.com/assets
Anyone had same problem? how to fix this problem?
The SSL certificate for s3 is a wildcard certificate, i.e. it is for *.s3.amazonaws.com. However a lot of certificate checking libraries define this to cover foo.s3.amazonaws.com but not foo.bar.amazonaws.com: wildcard certificates only go one level down.
The simplest solution is to pick a bucket name with no dots in it, e.g. myapp-assets.
Another solution is to access the files as https://s3.amazonaws.com/myapp.asset/assets/.... I believe you'd have to set config.assets.prefix to tell rails that the assets aren't in the normal location relative to the asset host.

Rails 3 automatic asset deployment to Amazon CloudFront?

Is there a gem or method available in Rails 3.1 that can upload assets to amazon cloud front automatically and use those instead of serving locally hosted ones? I guess it's easy to upload compiled assets manually and then change the rails app config to use that asset host, but when an asset is modified, the uploads to cloud front would need to be done manually again. Any good ways out there for this?
Definitely check out asset_sync on github. Or our Heroku dev centre article on Using a CDN asset Host with Rails 3.1 on Heroku.
There is quite a big performance improvement in using asset_sync vs a CDN custom origin, letting your application lazily compile assets in production or serving them precompiled directly off your app servers. However I would say that. I wrote it.
With asset_sync and S3 you can precompile assets meaning all the assets are there ready to be served on the asset host / CDN immediately
You can only require the :assets bundle in application.rb on precompile, saving memory in production
Your app servers are NEVER hit for asset requests. You can spend expensive compute time on, you know. Computing.
Best practice HTTP cache headers are all set by default
You can enable automatic gzip compression with one extra config
If you use Cloudfronts “Custom origin” option you do not need to upload anything, Cloudfront will fetch the assets from your server when needed. For details of setting this up see:
http://blog.ertesvag.no/post/10720082458
Take a look at https://github.com/rumblelabs/asset_sync - we're using it just to S3 but I guess the CloudFront part is pretty easy once the assets are on S3.
It's ends up being a rake task that you just add to execute in your deployment process.
another option would be https://github.com/moocode/asset_id, the readme has an example to use it with cloudfront.
It should work with rails 3.1 but I have only used it on 3.0.x.
Ss John said all solutions would end up being a rake task + a bit of logic to change the asset path in rails.

Resources