From 90c1207c384a39567777bc58f2de38eb7df236d7 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 18 Feb 2007 06:44:20 +0000 Subject: Work around the two connection per host browser limit: use asset%d.myapp.com to distribute asset requests among asset[0123].myapp.com. Use a DNS wildcard or CNAMEs to map these hosts to your asset server. See http://www.die.net/musings/page_load_time/ for background. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6161 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/CHANGELOG | 2 + .../lib/action_view/helpers/asset_tag_helper.rb | 98 +++++++++++++++------- actionpack/test/template/asset_tag_helper_test.rb | 7 ++ 3 files changed, 75 insertions(+), 32 deletions(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 07fc186fb1..2110d223f9 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Work around the two connection per host browser limit: use asset%d.myapp.com to distribute asset requests among asset[0123].myapp.com. Use a DNS wildcard or CNAMEs to map these hosts to your asset server. See http://www.die.net/musings/page_load_time/ for background. [Jeremy Kemper] + * Added default mime type for CSS (Mime::CSS) [DHH] * Added that rendering will automatically insert the etag header on 200 OK responses. The etag is calculated using MD5 of the response body. If a request comes in that has a matching etag, the response will be changed to a 304 Not Modified and the response body will be set to an empty string. [DHH] diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index b2a0d5344a..8d62f07b8c 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -11,13 +11,25 @@ module ActionView # linking to them. # # ActionController::Base.asset_host = "http://assets.example.com" - # image_tag("rails.png") + # image_tag("rails.png") # => Rails # stylesheet_include_tag("application") # => + # + # Since browsers typically open at most two connections to a single host, + # your assets often wait in single file for their turn to load. + # + # Use a %d wildcard in asset_host (asset%d.myapp.com) to automatically + # distribute asset requests among four hosts (asset0-asset3.myapp.com) + # so browsers will open eight connections rather than two. Use wildcard + # DNS to CNAME the wildcard to your real asset host. + # + # Note: this is purely a browser performance optimization and is not meant + # for server load balancing. See http://www.die.net/musings/page_load_time/ + # for background. module AssetTagHelper # Returns a link tag that browsers and news readers can use to auto-detect - # an RSS or ATOM feed. The +type+ can either be :rss (default) or + # an RSS or ATOM feed. The +type+ can either be :rss (default) or # :atom. Control the link options in url_for format using the # +url_options+. You can modify the LINK tag itself in +tag_options+. # @@ -36,7 +48,7 @@ module ActionView # def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) tag( - "link", + "link", "rel" => tag_options[:rel] || "alternate", "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s, "title" => tag_options[:title] || type.to_s.upcase, @@ -53,7 +65,7 @@ module ActionView # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js def javascript_path(source) - compute_public_path(source, 'javascripts', 'js') + compute_public_path(source, 'javascripts', 'js') end JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) @@ -64,10 +76,10 @@ module ActionView # that exist in your public/javascripts directory for inclusion into the # current page or you can pass the full path relative to your document # root. To include the Prototype and Scriptaculous javascript libraries in - # your application, pass :defaults as the source. When using - # :defaults, if an application.js file exists in your public - # javascripts directory, it will be included as well. You can modify the - # html attributes of the script tag by passing a hash as the last argument. + # your application, pass :defaults as the source. When using + # :defaults, if an application.js file exists in your public + # javascripts directory, it will be included as well. You can modify the + # html attributes of the script tag by passing a hash as the last argument. # # javascript_include_tag "xmlhr" # => # @@ -84,29 +96,29 @@ module ActionView def javascript_include_tag(*sources) options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { } - if sources.include?(:defaults) - sources = sources[0..(sources.index(:defaults))] + - @@javascript_default_sources.dup + + if sources.include?(:defaults) + sources = sources[0..(sources.index(:defaults))] + + @@javascript_default_sources.dup + sources[(sources.index(:defaults) + 1)..sources.length] - sources.delete(:defaults) - sources << "application" if defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js") + sources.delete(:defaults) + sources << "application" if defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js") end sources.collect do |source| - source = javascript_path(source) + source = javascript_path(source) content_tag("script", "", { "type" => "text/javascript", "src" => source }.merge(options)) end.join("\n") end - + # Register one or more additional JavaScript files to be included when # javascript_include_tag :defaults is called. This method is - # only intended to be called from plugin initialization to register additional + # only intended to be called from plugin initialization to register additional # .js files that the plugin installed in public/javascripts. def self.register_javascript_include_default(*sources) @@javascript_default_sources.concat(sources) end - + def self.reset_javascript_include_default #:nodoc: @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup end @@ -165,14 +177,14 @@ module ActionView end # Returns an html image tag for the +source+. The +source+ can be a full - # path or a file that exists in your public images directory. Note that + # path or a file that exists in your public images directory. Note that # specifying a filename without the extension is now deprecated in Rails. # You can add html attributes using the +options+. The +options+ supports # two additional keys for convienence and conformance: # - # * :alt - If no alt text is given, the file name part of the + # * :alt - If no alt text is given, the file name part of the # +source+ is used (capitalized and without the extension) - # * :size - Supplied as "{Width}x{Height}", so "30x45" becomes + # * :size - Supplied as "{Width}x{Height}", so "30x45" becomes # width="30" and height="45". :size will be ignored if the # value is not in the correct format. # @@ -184,10 +196,10 @@ module ActionView # Icon def image_tag(source, options = {}) options.symbolize_keys! - + options[:src] = image_path(source) options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize - + if options[:size] options[:width], options[:height] = options[:size].split("x") if options[:size] =~ %r{^\d+x\d+$} options.delete(:size) @@ -195,24 +207,46 @@ module ActionView tag("img", options) end - + private + # Add the .ext if not present. Return full URLs otherwise untouched. + # Prefix with /dir/ if lacking a leading /. Account for relative URL + # roots. Rewrite the asset path for cache-busting asset ids. Include + # a single or wildcarded asset host if configured. def compute_public_path(source, dir, ext) - source = source.dup - source << ".#{ext}" if File.extname(source).blank? - unless source =~ %r{^[-a-z]+://} + source += ".#{ext}" if File.extname(source).blank? + if source =~ %r{^[-a-z]+://} + source + else source = "/#{dir}/#{source}" unless source[0] == ?/ - asset_id = rails_asset_id(source) - source << '?' + asset_id if defined?(RAILS_ROOT) && !asset_id.blank? - source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}" + source = "#{@controller.request.relative_url_root}#{source}" + rewrite_asset_path!(source) + "#{compute_asset_host(source)}#{source}" + end + end + + # Pick an asset host for this source. Returns nil if no host is set, + # the host if no wildcard is set, or the host interpolated with the + # numbers 0-3 if it contains %d. The number is the source hash mod 4. + def compute_asset_host(source) + if host = ActionController::Base.asset_host + host % (source.hash % 4) end - source end - + + # Use the RAILS_ASSET_ID environment variable or the source's + # modification time as its cache-busting asset id. def rails_asset_id(source) - ENV["RAILS_ASSET_ID"] || + ENV["RAILS_ASSET_ID"] || File.mtime("#{RAILS_ROOT}/public/#{source}").to_i.to_s rescue "" end + + # Break out the asset path rewrite so you wish to put the asset id + # someplace other than the query string. + def rewrite_asset_path!(source) + asset_id = rails_asset_id(source) + source << "?#{asset_id}" if defined?(RAILS_ROOT) && !asset_id.blank? + end end end end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index d80019c3f1..5393e30724 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -223,4 +223,11 @@ class AssetTagHelperNonVhostTest < Test::Unit::TestCase ensure ActionController::Base.asset_host = "" end + + def test_should_wildcard_asset_host_between_zero_and_four + ActionController::Base.asset_host = 'http://a%d.example.com' + assert_match %r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png') + ensure + ActionController::Base.asset_host = nil + end end -- cgit v1.2.3