CamelCamelCamel
CamelCamelCamel

Reputation: 5200

3rd Party Script Caching in Rails 3.1

I have a script 3rd party websites are using: /assets/script.js. For obvious reasons, I can't ask them to change the link every time I deploy to point to the latest fingerprinted version of the script. I got a few caching issues where users still see old versions of /script.js. Are there any ways to make the cache go away directly for script.js instead of script-9dc5afea3571ba2a883a72b0da0bb623.js?

More Information: Rails on Passenger + Nginx. Looking for ways to serve the script.js file instead if the finger-printed file and invalidate the cache on every deployment.

I thought about adding ETags based on the deployment git revision, but have no idea how to do this. Nginx has no built in ETags support. There are unsupported old third party modules that do this. I can use add_header Etag="something" for this, but how do I add the git version there.

Any other ideas and options?

Thanks!

Upvotes: 11

Views: 690

Answers (8)

Andreyy
Andreyy

Reputation: 501

You want a non fingerprinted asset url for third party websites. For example: assets/public_api.js

There have been plugins or gems that excluded specified assets from fingerprinting. However rails has changed the pre-compilation process in a way that also creates non-fingerprinted files. Therefore this isn't a problem. More info here.

How to make sure your clients are loading the latest deployed script, when the asset is not fingerprinted?

I would suggest the solution youTube uses to expose their API. Basically all your assets/public_api.js does, it injects another script tag into the dom. That injected one loads the actual API code. Now your assets/public_api.js becomes assets/public_api.js.erb and looks something like this:

var tag = document.createElement('script');
tag.src = "<%=asset_path('/assets/javascripts/acctual_api')%>";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

Please note how tag.src is set to the current fingerprinted path to /assets/javascripts/acctual_api. This way your users will always get the latest compiled acctual_api script.

How to update the ETAG for the assets/public_api.js?

I suppose you use Capistrano or similar recipe based deployment solution. Maybe you could add a deployment step that updates the server config file before its restarted. It should just update:

add_header Etag="update_me_on_deploy"

Please note that you should still use versioned (assets/public_api.0.js) public scripts even with this approach.

Upvotes: 1

rbinsztock
rbinsztock

Reputation: 3195

Well you can remove the fingerprint of the file :

asset_path('script.js', :digest => false)

hope it helps

Or you can use this gem if you want : https://github.com/spohlenz/digestion

But : The Rails asset pipeline now compiles asset files both with and without digests.

So after you generate your assets usually you got script.js?xxxxx and script.js into your public/assets folder.

Upvotes: 1

tagCincy
tagCincy

Reputation: 1599

If you are using Capistrano, you could write a task that copies the script from Public/assets to another directory within Public (i.e. Public/scripts) after your assets are precompiled.

Upvotes: 1

WispyCloud
WispyCloud

Reputation: 4230

nginx is able to generate etags in the latest version: http://nginx.org/en/docs/http/ngx_http_core_module.html#etag

I've also seen the configuration below here: https://serverfault.com/questions/426260/nginx-cache-control

location /static {
  alias /opt/static/blog/;
  access_log off;
  etags on;
  etag_hash on;
  etag_hash_method md5;
  expires     1d;
  add_header Pragma "public";
  add_header Cache-Control  "public, must-revalidate, proxy-revalidate";
}

Upvotes: 3

Yuri Golobokov
Yuri Golobokov

Reputation: 1965

What I'm using for updating assets is:

  1. Increment config.assets.version in config/application.rb like

    #Version of your assets, change this if you want to expire all your assets
    config.assets.version = '1.1'
    
  2. bundle exec rake assets:precompile RAILS_ENV=production RAILS_GROUPS=assets

  3. App restart, empty webserver cache if any

Upvotes: 1

Pasha Bitz
Pasha Bitz

Reputation: 787

I recommend using an ETag. Add an ETag header to your response http://en.wikipedia.org/wiki/HTTP_ETag

Set the ETag header to a different, unique string for each version of your script. This will make sure browsers get a new version of the script whenever you deploy a new version .

Upvotes: 3

Alex Ghiculescu
Alex Ghiculescu

Reputation: 7540

Following up on the ETag suggestion, you might find this gem useful: bust_rails_etags. It allows you to set a key on each deployment which is used in the generation of ETags, that way, your ETags will change (and so the cached script will be invalidated) every time your app is deployed. The author uses the example of Heroku release numbers as a key that changes on each deploy.

Upvotes: 1

Tom Fakes
Tom Fakes

Reputation: 955

If you have a script with a name that is part of your public interface then you need to start versioning this script explicitly, and keeping old versions around for older clients.

e.g. /assets/script.1.0.js, /assets/script.1.1.js etc

The key part is that you need to be keeping the old ones around, and the code doesn't change without the name changing explicitly. The Rails asset pipeline can't do this for you, since there's usually only the very latest version of the script kept current.

As with all public interfaces, you will need to spend more time on managing this process than you would for an internal-only script.

Upvotes: 9

Related Questions