lib/rack/static.rb in rack-1.4.1 vs lib/rack/static.rb in rack-1.4.2
- old
+ new
@@ -24,30 +24,81 @@
#
# Serve all requests normally from the folder "public" in the current
# directory but uses index.html as default route for "/"
#
# use Rack::Static, :urls => [""], :root => 'public', :index =>
- # 'public/index.html'
+ # 'index.html'
#
- # Set a fixed Cache-Control header for all served files:
+ # Set custom HTTP Headers for based on rules:
#
- # use Rack::Static, :root => 'public', :cache_control => 'public'
+ # use Rack::Static, :root => 'public',
+ # :header_rules => [
+ # [rule, {header_field => content, header_field => content}],
+ # [rule, {header_field => content}]
+ # ]
#
-
+ # Rules for selecting files:
+ #
+ # 1) All files
+ # Provide the :all symbol
+ # :all => Matches every file
+ #
+ # 2) Folders
+ # Provide the folder path as a string
+ # '/folder' or '/folder/subfolder' => Matches files in a certain folder
+ #
+ # 3) File Extensions
+ # Provide the file extensions as an array
+ # ['css', 'js'] or %w(css js) => Matches files ending in .css or .js
+ #
+ # 4) Regular Expressions / Regexp
+ # Provide a regular expression
+ # %r{\.(?:css|js)\z} => Matches files ending in .css or .js
+ # /\.(?:eot|ttf|otf|woff|svg)\z/ => Matches files ending in
+ # the most common web font formats (.eot, .ttf, .otf, .woff, .svg)
+ # Note: This Regexp is available as a shortcut, using the :fonts rule
+ #
+ # 5) Font Shortcut
+ # Provide the :fonts symbol
+ # :fonts => Uses the Regexp rule stated right above to match all common web font endings
+ #
+ # Rule Ordering:
+ # Rules are applied in the order that they are provided.
+ # List rather general rules above special ones.
+ #
+ # Complete example use case including HTTP header rules:
+ #
+ # use Rack::Static, :root => 'public',
+ # :header_rules => [
+ # # Cache all static files in public caches (e.g. Rack::Cache)
+ # # as well as in the browser
+ # [:all, {'Cache-Control' => 'public, max-age=31536000'}],
+ #
+ # # Provide web fonts with cross-origin access-control-headers
+ # # Firefox requires this when serving assets using a Content Delivery Network
+ # [:fonts, {'Access-Control-Allow-Origin' => '*'}]
+ # ]
+ #
class Static
def initialize(app, options={})
@app = app
@urls = options[:urls] || ["/favicon.ico"]
@index = options[:index]
root = options[:root] || Dir.pwd
- cache_control = options[:cache_control]
- @file_server = Rack::File.new(root, cache_control)
+
+ # HTTP Headers
+ @header_rules = options[:header_rules] || []
+ # Allow for legacy :cache_control option while prioritizing global header_rules setting
+ @header_rules.insert(0, [:all, {'Cache-Control' => options[:cache_control]}]) if options[:cache_control]
+ @headers = {}
+
+ @file_server = Rack::File.new(root, @headers)
end
def overwrite_file_path(path)
- @urls.kind_of?(Hash) && @urls.key?(path) || @index && path == '/'
+ @urls.kind_of?(Hash) && @urls.key?(path) || @index && path =~ /\/$/
end
def route_file(path)
@urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
end
@@ -58,14 +109,45 @@
def call(env)
path = env["PATH_INFO"]
if can_serve(path)
- env["PATH_INFO"] = (path == '/' ? @index : @urls[path]) if overwrite_file_path(path)
+ env["PATH_INFO"] = (path =~ /\/$/ ? path + @index : @urls[path]) if overwrite_file_path(path)
+ @path = env["PATH_INFO"]
+ apply_header_rules
@file_server.call(env)
else
@app.call(env)
end
+ end
+
+ # Convert HTTP header rules to HTTP headers
+ def apply_header_rules
+ @header_rules.each do |rule, headers|
+ apply_rule(rule, headers)
+ end
+ end
+
+ def apply_rule(rule, headers)
+ case rule
+ when :all # All files
+ set_headers(headers)
+ when :fonts # Fonts Shortcut
+ set_headers(headers) if @path.match(/\.(?:ttf|otf|eot|woff|svg)\z/)
+ when String # Folder
+ path = ::Rack::Utils.unescape(@path)
+ set_headers(headers) if (path.start_with?(rule) || path.start_with?('/' + rule))
+ when Array # Extension/Extensions
+ extensions = rule.join('|')
+ set_headers(headers) if @path.match(/\.(#{extensions})\z/)
+ when Regexp # Flexible Regexp
+ set_headers(headers) if @path.match(rule)
+ else
+ end
+ end
+
+ def set_headers(headers)
+ headers.each { |field, content| @headers[field] = content }
end
end
end