diff options
Diffstat (limited to 'guides/source/asset_pipeline.md')
-rw-r--r-- | guides/source/asset_pipeline.md | 238 |
1 files changed, 211 insertions, 27 deletions
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 2d1548f252..c19c8e0bec 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -124,19 +124,22 @@ with a built-in helper. In the source the generated code looked like this: The query string strategy has several disadvantages: 1. **Not all caches will reliably cache content where the filename only differs by -query parameters**<br> +query parameters** + [Steve Souders recommends](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/), "...avoiding a querystring for cacheable resources". He found that in this case 5-20% of requests will not be cached. Query strings in particular do not work at all with some CDNs for cache invalidation. -2. **The file name can change between nodes in multi-server environments.**<br> +2. **The file name can change between nodes in multi-server environments.** + The default query string in Rails 2.x is based on the modification time of the files. When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request. -3. **Too much cache invalidation**<br> +3. **Too much cache invalidation** + When static assets are deployed with each new release of code, the mtime (time of last modification) of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed. @@ -163,7 +166,8 @@ pipeline, the preferred location for these assets is now the `app/assets` directory. Files in this directory are served by the Sprockets middleware. Assets can still be placed in the `public` hierarchy. Any assets under `public` -will be served as static files by the application or web server. You should use +will be served as static files by the application or web server when +`config.serve_static_assets` is set to true. You should use `app/assets` for files that must undergo some pre-processing before they are served. @@ -204,9 +208,7 @@ precompiling works. NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript. If you are using Mac OS X or Windows, you have a JavaScript runtime installed in -your operating system. Check -[ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all -supported JavaScript runtimes. +your operating system. Check [ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all supported JavaScript runtimes. You can also disable generation of controller specific asset files by adding the following to your `config/application.rb` configuration: @@ -490,14 +492,13 @@ The directives that work in JavaScript files also work in stylesheets one, requiring all stylesheets from the current directory. In this example, `require_self` is used. This puts the CSS contained within the -file (if any) at the precise location of the `require_self` call. If -`require_self` is called more than once, only the last call is respected. +file (if any) at the precise location of the `require_self` call. NOTE. If you want to use multiple Sass files, you should generally use the [Sass `@import` rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import) -instead of these Sprockets directives. Using Sprockets directives all Sass files exist within +instead of these Sprockets directives. When using Sprockets directives, Sass files exist within their own scope, making variables or mixins only available within the document they were defined in. -You can do file globbing as well using `@import "*"`, and `@import "**/*"` to add the whole tree -equivalent to how `require_tree` works. Check the [sass-rails documentation](https://github.com/rails/sass-rails#features) for more info and important caveats. + +You can do file globbing as well using `@import "*"`, and `@import "**/*"` to add the whole tree which is equivalent to how `require_tree` works. Check the [sass-rails documentation](https://github.com/rails/sass-rails#features) for more info and important caveats. You can have as many manifest files as you need. For example, the `admin.css` and `admin.js` manifest could contain the JS and CSS files that are used for the @@ -734,10 +735,10 @@ Rails.application.config.assets.precompile << Proc.new do |path| full_path = Rails.application.assets.resolve(path).to_path app_assets_path = Rails.root.join('app', 'assets').to_path if full_path.starts_with? app_assets_path - puts "including asset: " + full_path + logger.info "including asset: " + full_path true else - puts "excluding asset: " + full_path + logger.info "excluding asset: " + full_path false end else @@ -760,7 +761,7 @@ typical manifest file looks like: "digest":"12b3c7dd74d2e9df37e7cbb1efa76a6d"},"application-1c5752789588ac18d7e1a50b1f0fd4c2.css":{"logical_path":"application.css","mtime":"2013-07-26T22:56:17-07:00","size":1591, "digest":"1c5752789588ac18d7e1a50b1f0fd4c2"},"favicon-a9c641bf2b81f0476e876f7c5e375969.ico":{"logical_path":"favicon.ico","mtime":"2013-07-26T23:00:10-07:00","size":1406, "digest":"a9c641bf2b81f0476e876f7c5e375969"},"my_image-231a680f23887d9dd70710ea5efd3c62.png":{"logical_path":"my_image.png","mtime":"2013-07-26T23:00:27-07:00","size":6646, -"digest":"231a680f23887d9dd70710ea5efd3c62"}},"assets"{"application.js": +"digest":"231a680f23887d9dd70710ea5efd3c62"}},"assets":{"application.js": "application-723d1be6cc741a3aabb1cec24276d681.js","application.css": "application-1c5752789588ac18d7e1a50b1f0fd4c2.css", "favicon.ico":"favicona9c641bf2b81f0476e876f7c5e375969.ico","my_image.png": @@ -914,24 +915,207 @@ end ### CDNs -If your assets are being served by a CDN, ensure they don't stick around in your -cache forever. This can cause problems. If you use -`config.action_controller.perform_caching = true`, Rack::Cache will use -`Rails.cache` to store assets. This can cause your cache to fill up quickly. +CDN stands for [Content Delivery +Network](http://en.wikipedia.org/wiki/Content_delivery_network), they are +primarily designed to cache assets all over the world so that when a browser +requests the asset, a cached copy will be geographically close to that browser. +If you are serving assets directly from your Rails server in production, the +best practice is to use a CDN in front of your application. + +A common pattern for using a CDN is to set your production application as the +"origin" server. This means when a browser requests an asset from the CDN and +there is a cache miss, it will grab the file from your server on the fly and +then cache it. For example if you are running a Rails application on +`example.com` and have a CDN configured at `mycdnsubdomain.fictional-cdn.com`, +then when a request is made to `mycdnsubdomain.fictional- +cdn.com/assets/smile.png`, the CDN will query your server once at +`example.com/assets/smile.png` and cache the request. The next request to the +CDN that comes in to the same URL will hit the cached copy. When the CDN can +serve an asset directly the request never touches your Rails server. Since the +assets from a CDN are geographically closer to the browser, the request is +faster, and since your server doesn't need to spend time serving assets, it can +focus on serving application code as fast as possible. + +#### Set up a CDN to Serve Static Assets + +To set up your CDN you have to have your application running in production on +the internet at a publically available URL, for example `example.com`. Next +you'll need to sign up for a CDN service from a cloud hosting provider. When you +do this you need to configure the "origin" of the CDN to point back at your +website `example.com`, check your provider for documentation on configuring the +origin server. + +The CDN you provisioned should give you a custom subdomain for your application +such as `mycdnsubdomain.fictional-cdn.com` (note fictional-cdn.com is not a +valid CDN provider at the time of this writing). Now that you have configured +your CDN server, you need to tell browsers to use your CDN to grab assets +instead of your Rails server directly. You can do this by configuring Rails to +set your CDN as the asset host instead of using a relative path. To set your +asset host in Rails, you need to set `config.action_controller.asset_host` in +`config/production.rb`: + +```ruby +config.action_controller.asset_host = 'mycdnsubdomain.fictional-cdn.com' +``` + +NOTE: You only need to provide the "host", this is the subdomain and root +domain, you do not need to specify a protocol or "scheme" such as `http://` or +`https://`. When a web page is requested, the protocol in the link to your asset +that is generated will match how the webpage is accessed by default. + +You can also set this value through an [environment +variable](http://en.wikipedia.org/wiki/Environment_variable) to make running a +staging copy of your site easier: + +``` +config.action_controller.asset_host = ENV['CDN_HOST'] +``` + + -Every cache is different, so evaluate how your CDN handles caching and make sure -that it plays nicely with the pipeline. You may find quirks related to your -specific set up, you may not. The defaults NGINX uses, for example, should give -you no problems when used as an HTTP cache. +Note: You would need to set `CDN_HOST` on your server to `mycdnsubdomain +.fictional-cdn.com` for this to work. -If you want to serve only some assets from your CDN, you can use custom -`:host` option of `asset_url` helper, which overwrites value set in +Once you have configured your server and your CDN when you serve a webpage that +has an asset: + +```erb +<%= asset_path('smile.png') %> +``` + +Instead of returning a path such as `/assets/smile.png` (digests are left out +for readability). The URL generated will have the full path to your CDN. + +``` +http://mycdnsubdomain.fictional-cdn.com/assets/smile.png +``` + +If the CDN has a copy of `smile.png` it will serve it to the browser and your +server doesn't even know it was requested. If the CDN does not have a copy it +will try to find it a the "origin" `example.com/assets/smile.png` and then store +it for future use. + +If you want to serve only some assets from your CDN, you can use custom `:host` +option your asset helper, which overwrites value set in `config.action_controller.asset_host`. -```ruby -asset_url 'image.png', :host => 'http://cdn.example.com' +```erb +<%= asset_path 'image.png', host: 'mycdnsubdomain.fictional-cdn.com' %> ``` +#### Customize CDN Caching Behavior + +A CDN works by caching content. If the CDN has stale or bad content, then it is +hurting rather than helping your application. The purpose of this section is to +describe general caching behavior of most CDNs, your specific provider may +behave slightly differently. + +##### CDN Request Caching + +While a CDN is described as being good for caching assets, in reality caches the +entire request. This includes the body of the asset as well as any headers. The +most important one being `Cache-Control` which tells the CDN (and web browsers) +how to cache contents. This means that if someone requests an asset that does +not exist `/assets/i-dont-exist.png` and your Rails application returns a 404, +then your CDN will likely cache the 404 page if a valid `Cache-Control` header +is present. + +##### CDN Header Debugging + +One way to check the headers are cached properly in your CDN is by using [curl]( +http://explainshell.com/explain?cmd=curl+-I+http%3A%2F%2Fwww.example.com). You +can request the headers from both your server and your CDN to verify they are +the same: + +``` +$ curl -I http://www.example/assets/application- +d0e099e021c95eb0de3615fd1d8c4d83.css +HTTP/1.1 200 OK +Server: Cowboy +Date: Sun, 24 Aug 2014 20:27:50 GMT +Connection: keep-alive +Last-Modified: Thu, 08 May 2014 01:24:14 GMT +Content-Type: text/css +Cache-Control: public, max-age=2592000 +Content-Length: 126560 +Via: 1.1 vegur +``` + +Versus the CDN copy. + +``` +$ curl -I http://mycdnsubdomain.fictional-cdn.com/application- +d0e099e021c95eb0de3615fd1d8c4d83.css +HTTP/1.1 200 OK Server: Cowboy Last- +Modified: Thu, 08 May 2014 01:24:14 GMT Content-Type: text/css +Cache-Control: +public, max-age=2592000 +Via: 1.1 vegur +Content-Length: 126560 +Accept-Ranges: +bytes +Date: Sun, 24 Aug 2014 20:28:45 GMT +Via: 1.1 varnish +Age: 885814 +Connection: keep-alive +X-Served-By: cache-dfw1828-DFW +X-Cache: HIT +X-Cache-Hits: +68 +X-Timer: S1408912125.211638212,VS0,VE0 +``` + +Check your CDN documentation for any additional information they may provide +such as `X-Cache` or for any additional headers they may add. + +##### CDNs and the Cache-Control Header + +The [cache control +header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) is a W3C +specification that describes how a request can be cached. When no CDN is used, a +browser will use this information to cache contents. This is very helpful for +assets that are not modified so that a browser does not need to re-download a +website's CSS or javascript on every request. Generally we want our Rails server +to tell our CDN (and browser) that the asset is "public", that means any cache +can store the request. Also we commonly want to set `max-age` which is how long +the cache will store the object before invalidating the cache. The `max-age` +value is set to seconds with a maximum possible value of `31536000` which is one +year. You can do this in your rails application by setting + +``` +config.static_cache_control = "public, max-age=31536000" +``` + +Now when your application serves an asset in production, the CDN will store the +asset for up to a year. Since most CDNs also cache headers of the request, this +`Cache-Control` will be passed along to all future browsers seeking this asset, +the browser then knows that it can store this asset for a very long time before +needing to re-request it. + +##### CDNs and URL based Cache Invalidation + +Most CDNs will cache contents of an asset based on the complete URL. This means +that a request to + +``` +http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png +``` + +Will be a completely different cache from + +``` +http://mycdnsubdomain.fictional-cdn.com/assets/smile.png +``` + +If you want to set far future `max-age` in your `Cache-Control` (and you do), +then make sure when you change your assets that your cache is invalidated. For +example when changing the smiley face in an image from yellow to blue, you want +all visitors of your site to get the new blue face. When using a CDN with the +Rails asset pipeline `config.assets.digest` is set to true by default so that +each asset will have a different file name when it is changed. This way you +don't have to ever manually invalidate any items in your cache. By using a +different unique asset name instead, your users get the latest asset. + Customizing the Pipeline ------------------------ |