aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/helpers/asset_url_helper.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_view/helpers/asset_url_helper.rb')
-rw-r--r--actionpack/lib/action_view/helpers/asset_url_helper.rb189
1 files changed, 83 insertions, 106 deletions
diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionpack/lib/action_view/helpers/asset_url_helper.rb
index 35d312e61a..8cbcdfe99e 100644
--- a/actionpack/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_url_helper.rb
@@ -1,4 +1,4 @@
-require 'action_view/helpers/asset_tag_helpers/asset_paths'
+require 'zlib'
module ActionView
# = Action View Asset URL Helpers
@@ -104,104 +104,12 @@ module ActionView
# "http://asset%d.example.com", "https://asset1.example.com"
# )
#
- # === Customizing the asset path
- #
- # By default, Rails appends asset's timestamps to all asset paths. This allows
- # you to set a cache-expiration date for the asset far into the future, but
- # still be able to instantly invalidate it by simply updating the file (and
- # hence updating the timestamp, which then updates the URL as the timestamp
- # is part of that, which in turn busts the cache).
- #
- # It's the responsibility of the web server you use to set the far-future
- # expiration date on cache assets that you need to take advantage of this
- # feature. Here's an example for Apache:
- #
- # # Asset Expiration
- # ExpiresActive On
- # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
- # ExpiresDefault "access plus 1 year"
- # </FilesMatch>
- #
- # Also note that in order for this to work, all your application servers must
- # return the same timestamps. This means that they must have their clocks
- # synchronized. If one of them drifts out of sync, you'll see different
- # timestamps at random and the cache won't work. In that case the browser
- # will request the same assets over and over again even thought they didn't
- # change. You can use something like Live HTTP Headers for Firefox to verify
- # that the cache is indeed working.
- #
- # This strategy works well enough for most server setups and requires the
- # least configuration, but if you deploy several application servers at
- # different times - say to handle a temporary spike in load - then the
- # asset time stamps will be out of sync. In a setup like this you may want
- # to set the way that asset paths are generated yourself.
- #
- # Altering the asset paths that Rails generates can be done in two ways.
- # The easiest is to define the RAILS_ASSET_ID environment variable. The
- # contents of this variable will always be used in preference to
- # calculated timestamps. A more complex but flexible way is to set
- # <tt>ActionController::Base.config.asset_path</tt> to a proc
- # that takes the unmodified asset path and returns the path needed for
- # your asset caching to work. Typically you'd do something like this in
- # <tt>config/environments/production.rb</tt>:
- #
- # # Normally you'd calculate RELEASE_NUMBER at startup.
- # RELEASE_NUMBER = 12345
- # config.action_controller.asset_path = proc { |asset_path|
- # "/release-#{RELEASE_NUMBER}#{asset_path}"
- # }
- #
- # This example would cause the following behavior on all servers no
- # matter when they were deployed:
- #
- # image_tag("rails.png")
- # # => <img alt="Rails" src="/release-12345/images/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
- #
- # Changing the asset_path does require that your web servers have
- # knowledge of the asset template paths that you rewrite to so it's not
- # suitable for out-of-the-box use. To use the example given above you
- # could use something like this in your Apache VirtualHost configuration:
- #
- # <LocationMatch "^/release-\d+/(images|javascripts|stylesheets)/.*$">
- # # Some browsers still send conditional-GET requests if there's a
- # # Last-Modified header or an ETag header even if they haven't
- # # reached the expiry date sent in the Expires header.
- # Header unset Last-Modified
- # Header unset ETag
- # FileETag None
- #
- # # Assets requested using a cache-busting filename should be served
- # # only once and then cached for a really long time. The HTTP/1.1
- # # spec frowns on hugely-long expiration times though and suggests
- # # that assets which never expire be served with an expiration date
- # # 1 year from access.
- # ExpiresActive On
- # ExpiresDefault "access plus 1 year"
- # </LocationMatch>
- #
- # # We use cached-busting location names with the far-future expires
- # # headers to ensure that if a file does change it can force a new
- # # request. The actual asset filenames are still the same though so we
- # # need to rewrite the location from the cache-busting location to the
- # # real asset location so that we can serve it.
- # RewriteEngine On
- # RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]
- #
module AssetUrlHelper
- ASSET_EXTENSIONS = {
- javascript: 'js',
- stylesheet: 'css'
- }
+ URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
- ASSET_PUBLIC_DIRECTORIES = {
- audio: '/audios',
- font: '/fonts',
- image: '/images',
- javascript: '/javascripts',
- stylesheet: '/stylesheets',
- video: '/videos'
+ ASSET_EXTENSIONS = {
+ javascript: '.js',
+ stylesheet: '.css'
}
# Computes the path to asset in public directory. If :type
@@ -215,9 +123,28 @@ module ActionView
# asset_path "application", type: :stylesheet # => /stylesheets/application.css
# asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def asset_path(source, options = {})
+ source = source.to_s
return "" unless source.present?
- options[:ext] ||= ASSET_EXTENSIONS[options[:type]] if options[:type]
- asset_paths.compute_public_path(source, ASSET_PUBLIC_DIRECTORIES[options[:type]], options)
+ return source if source =~ URI_REGEXP
+
+ if File.extname(source).empty? && (ext = ASSET_EXTENSIONS[options[:type]])
+ source = "#{source}#{ext}"
+ end
+
+ if source[0] != ?/
+ source = compute_asset_path(source, options)
+ end
+
+ current_request = controller.request if controller.respond_to?(:request)
+ if relative_url_root = config.relative_url_root || current_request.try(:script_name)
+ source = "#{relative_url_root}#{source}" unless source.starts_with?("#{relative_url_root}/")
+ end
+
+ if host = compute_asset_host(source, options)
+ source = "#{host}#{source}"
+ end
+
+ source
end
alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with a asset_path named route
@@ -225,11 +152,66 @@ module ActionView
# will use +asset_path+ internally, so most of their behaviors
# will be the same.
def asset_url(source, options = {})
- host = url_for(:only_path => false)
- URI.join(host, path_to_asset(source, options)).to_s
+ path_to_asset(source, options.merge(:protocol => :request))
end
alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route
+ # Maps asset types to public directory.
+ ASSET_PUBLIC_DIRECTORIES = {
+ audio: '/audios',
+ font: '/fonts',
+ image: '/images',
+ javascript: '/javascripts',
+ stylesheet: '/stylesheets',
+ video: '/videos'
+ }
+
+ # Computes asset path to public directory. Plugins and
+ # extensions can override this method to point to custom assets
+ # or generate digested paths or query strings.
+ def compute_asset_path(source, options = {})
+ dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || ""
+ File.join(dir, source)
+ end
+
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
+ # the host if no wildcard is set, the host interpolated with the
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
+ # or the value returned from invoking call on an object responding to call
+ # (proc or otherwise).
+ def compute_asset_host(source = "", options = {})
+ if controller.respond_to?(:request)
+ request = controller.request
+ end
+
+ host = config.asset_host
+ host ||= request.base_url if request && options[:protocol] == :request
+ return unless host
+
+ if host.respond_to?(:call)
+ arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
+ args = [source]
+ args << request if request && (arity > 1 || arity < 0)
+ host = host.call(*args)
+ elsif host =~ /%d/
+ host = host % (Zlib.crc32(source) % 4)
+ end
+
+ if host =~ URI_REGEXP
+ host
+ else
+ protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative)
+ case protocol
+ when :relative
+ "//#{host}"
+ when :request
+ "#{request.protocol}#{host}"
+ else
+ "#{protocol}://#{host}"
+ end
+ end
+ end
+
# Computes the path to a javascript asset in the public javascripts directory.
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
# Full paths from the document root will be passed through.
@@ -360,11 +342,6 @@ module ActionView
url_to_asset(source, type: :font)
end
alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
-
- private
- def asset_paths
- @asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller)
- end
end
end
end