When to tell your kids about presentation caching Matthew Deiters www.theAgileDeveloper.com
A practical guide to stuffing your app’s bits into someone else’s browser
Questions: @mdeiters
Rapid Feature Development
Rapid Feature Adoption & Development Growth
Client Caching
client.is_a?(Browser) == true
Browsers & Leveraging HTTP 1.1
Fewer Requests Smaller Responses
80/20 Rule (Pareto Principle)
80% of the wealth owned by 20% of people
80% of your time is with 20% of your acquaintances
80% of the time you wear 20% of your clothing
80% of a request is spent on the wire
Today
Reducing Network Tra � c Last-Modified Header ETag Header GZip Today Minification max-age Header Cookies Expires Header
ME
To illustrate: Scalability
Applicable for?
Applicable for? Enterprises
Applicable for? Enterprises High Traffic Web Sites
Applicable for? Enterprises High Traffic Web Sites Startups
Enterprise
High Traffic Sites
Reduce network traffic Reduce response times Reduce load
Facebook: Bumpersticker
1.4 Million Average Users
1.4 Million Average Users Average 20 page views
“Push everything you possibly can to the client to reduce the amount of traffic going over the network...”
Startups
HTTP 1.1 Enity Tags
www.nextdaypets.com
\puppies\43
HTTP/1.x 200 OK Etag: "8b2242293d5e5b02e99b3be73fc0c9fa"
www.nextdaypets.com
\puppies\43 If-None-Match: "8b2242293d5e5b02e99b3be73fc0c9fa"
HTTP/1.x 304 Not Modified
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT ETag: "10c24bc-4ab-457e1c1f"
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT ETag: "10c24bc-4ab-457e1c1f" If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT If-None-Match: "10c24bc-4ab-457e1c1f"
+ Conditional Get
class PeopleController < ApplicationController def show @person = Person.find(params[:id]) respond_to do |wants| #... end end end
#response.rb def last_modified=(utc_time) def etag=(etag)
#request.rb def fresh?(response) def not_modified?(modified_at) def etag_matches?(etag)
class PeopleController < ApplicationController def show @person = Person.find(params[:id]) respond_to do |wants| #... end end end
class PeopleController < ApplicationController def show @person = Person.find(params[:id]) response.last_modified = @person.updated_at.utc respond_to do |wants| #... end end end
class PeopleController < ApplicationController def show @person = Person.find(params[:id]) response.last_modified = @person.updated_at.utc response.etag = @person respond_to do |wants| #... end end end
class PeopleController < ApplicationController def show @person = Person.find(params[:id]) response.last_modified = @person.updated_at.utc response.etag = @person return head(:not_modified) if request.fresh?(response) respond_to do |wants| #... end end end
response.etag = @person # => “5cb44721b6ce18857ff6900486dc4aba” @person.cache_key # => "people/5-20071224150000"
def fresh_when(options) def stale?(options)
class PeopleController < ApplicationController def show @person = Person.find(params[:id]) response.last_modified = @person.updated_at.utc response.etag = @person return head(:not_modified) if request.fresh?(response) respond_to do |wants| #... end end end
class PeopleController < ApplicationController def show @person = Person.find(params[:id]) if stale?(:etag => @person, :last_modified => @person.updated_at.utc) respond_to do |wants| #... end end end end
Last-Modified vs ETag
Later response.etag = [@admin, @person, flash]
def handle_conditional_get! if nonempty_ok_response? self.etag ||= body if request && request.etag_matches?(etag) self.status = '304 Not Modified' self.body = '' end end set_conditional_cache_control! if etag? || last_modified? end
SInce Feb 2007 self.etag ||= body
http://localhost:3000/people GET /people HTTP/1.1
http://localhost:3000/people GET /people HTTP/1.1 HTTP/1.x 200 OK ... Etag: "94785662c6f60cb96681ed1b09a44783"
http://localhost:3000/people GET /people HTTP/1.1 HTTP/1.x 200 OK ... Etag: "94785662c6f60cb96681ed1b09a44783" http://localhost:3000/people GET /people HTTP/1.1 If-None-Match: "94785662c6f60cb96681ed1b09a44783"
http://localhost:3000/people GET /people HTTP/1.1 HTTP/1.x 200 OK ... Etag: "94785662c6f60cb96681ed1b09a44783" http://localhost:3000/people GET /people HTTP/1.1 If-None-Match: "94785662c6f60cb96681ed1b09a44783" HTTP/1.x 304 Not Modified Etag: "94785662c6f60cb96681ed1b09a44783"
send_file
Assets
Assets
Assets
INODE
/intl/en_ALL/images/logo.gif ETag: "48b6a5bf-47f4-a0757"
/intl/en_ALL/images/logo.gif /intl/en_ALL/images/logo.gif ETag: "48b6a5bf-47f4-a0757" ETag: "48b6a5bf-61a-21c86a4"
/intl/en_ALL/images/logo.gif /intl/en_ALL/images/logo.gif ETag: "48b6a5bf-47f4-a0757" ETag: "48b6a5bf-61a-21c86a4"
Avoid Cache Expiration & Validation
/stylesheets/screen.css?1219926880 CACHE BUSTER!
Now <FilesMatch "\.(pdf|flv|jpg|jpeg|png|gif|js|css|swf)$"> Header set Cache-Control "public" ExpiresActive On ExpiresDefault “access plus 10 years” FileETag None Header unset Last-Modified Header unset ETag </FilesMatch>
Server 1 Server 2 /images/beach.png?12414775 47 /images/beach.png?12414775 54
Option 1 #config/environments/production.rb #Subversion ENV['RAILS_ASSET_ID'] = YAML::load(`svn info $RAILS_ROOT`)["Revision"].to_i #GIT (Check out Grit too) ENV['RAILS_ASSET_ID'] = File.read(RAILS_ROOT + '/.git/refs/heads/deploy').chomp
Option 2 task :finalize_update, :except => { :no_release => true } do stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S") asset_paths = %w(images stylesheets javascripts).map do |asset| "#{latest_release}/public/#{p}" end.join(" ") run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" } end
Option 2 #Capistrano 2.4 set :normalize_asset_timestamps, true
Option 3
Option 3 use-commit-times
Now ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = true
Proxies & Via Header
Recommend
More recommend