Skip to content
Browse files

Rack::Static: Provide custom HTTP header rules as array only and more…

… concise docs
  • Loading branch information...
1 parent bd02124 commit bb0972768523321f647fa1549987c0964700724a @thomasklemm thomasklemm committed Sep 15, 2012
Showing with 103 additions and 206 deletions.
  1. +41 −70 lib/rack/static.rb
  2. +62 −136 test/spec_static.rb
View
111 lib/rack/static.rb
@@ -31,92 +31,65 @@ module Rack
# Set custom HTTP Headers for based on rules:
#
# use Rack::Static, :root => 'public',
- # :header_rules => {
- # rule => { header_field => content },
- # another_rule => { header_field => content }
- # }
+ # :header_rules => [
+ # [rule_1, { header_field_1 => content, header_field_2 => content }],
+ # [rule_2 => { header_field => content }]
+ # ]
#
- # These rules for generating HTTP Headers are shipped along:
- # 1) Global
- # :global => Matches every file
+ # Rules for selecting files:
#
- # Ex.:
- # :header_rules => {
- # :global => {'Cache-Control' => 'public, max-age=123'}
- # }
+ # 1) All files
+ # Provide the :all symbol
+ # :all => Matches every file
#
# 2) Folders
- # '/folder' => Matches all files in a certain folder
- # '/folder/subfolder' => ...
- # Note: Provide the folder as a string,
- # with or without the starting slash
+ # Provide the folder path as a string
+ # '/folder' or '/folder/subfolder' => Matches files in a certain folder
#
# 3) File Extensions
- # ['css', 'js'] => Will match all files ending in .css or .js
- # %w(css js) => ...
- # Note: Provide the file extensions in an array,
- # use any ruby syntax you like to set that array up
+ # Provide the file extensions as an array
+ # ['css', 'js'] or %w(css js) => Matches files ending in .css or .js
#
# 4) Regular Expressions / Regexp
- # %r{\.(?:css|js)\z} => will match all files ending in .css or .js
- # /\.(?:eot|ttf|otf|woff|svg)\z/ => will match all files ending
- # in the most common web font formats
+ # %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) Shortcuts
- # There is currently only one shortcut defined.
- # :fonts => will match all files ending in eot, ttf, otf, woff, svg
- # using the Regexp stated right above
+ # 5) Font Shortcut
+ # :fonts => Uses the Regexp rule stated right above to match all common web font endings
#
- # Example use:
+ # 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 http caches as well as on the client
- # :global => { 'Cache-Control' => 'public, max-age=31536000' },
- # # Provide Web Fonts with Access-Control-Headers
- # :fonts => { 'Access-Control-Allow-Origin' => '*' }
- # }
- #
- # Note: The rules will be applied in the order they are provided,
- # thus more special rules further down below can override
- # general global HTTP header settings
- #
- # Note: In MRI Ruby 1.8.7, Ruby Enterprise Edition (REE)
- # or certain versions of Rubinius hashes are unordered.
- # This means that when you provide rules using a hash
- # they will be applied in a random, different order
- # to every file served and each time a file is served.
- #
- # You can circumvent this issue by providing an array of arrays
- # as shown here:
- #
- # use Rack::Static, :root => 'public',
- # :header_rules => [
- # [rule, {header_field => field_content}],
- # [:global, { 'Cache-Control' => 'public, max-age=31536000' }],
- # [:fonts, { 'Access-Control-Allow-Origin' => '*' }]
- # ]
+ # :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
+
# HTTP Headers
- @headers = {}
- @header_rules = options[:header_rules] || {}
+ @header_rules = options[:header_rules] || []
# Allow for legacy :cache_control option while prioritizing global header_rules setting
- if options[:cache_control]
- if @header_rules.instance_of? Array
- @header_rules.insert(0, [:global, {'Cache-Control' => options[:cache_control]}])
- else
- @header_rules[:global] ||= {}
- @header_rules[:global]['Cache-Control'] ||= options[:cache_control]
- end
- end
+ @header_rules.insert(0, [:all, {'Cache-Control' => options[:cache_control]}]) if options[:cache_control]
+ @headers = {}
+
@file_server = Rack::File.new(root, @headers)
end
@@ -147,25 +120,23 @@ def call(env)
# Convert HTTP header rules to HTTP headers
def set_headers
- rules = @header_rules.to_a
-
- rules.each do |rule, headers|
+ @header_rules.each do |rule, headers|
apply_rule(rule, headers)
end
end
def apply_rule(rule, headers)
case rule
- when :global # Global
+ when :all # All files
set_header(headers)
when :fonts # Fonts Shortcut
- set_header(headers) if @path.match(%r{\.(?:ttf|otf|eot|woff|svg)\z})
+ set_header(headers) if @path.match(/\.(?:ttf|otf|eot|woff|svg)\z/)
when String # Folder
path = ::Rack::Utils.unescape(@path)
set_header(headers) if (path.start_with?(rule) || path.start_with?('/' + rule))
when Array # Extension/Extensions
extensions = rule.join('|')
- set_header(headers) if @path.match(%r{\.(#{extensions})\z})
+ set_header(headers) if @path.match(/\.(#{extensions})\z/)
when Regexp # Flexible Regexp
set_header(headers) if @path.match(rule)
else
View
198 test/spec_static.rb
@@ -78,142 +78,68 @@ def static(app, *args)
res.headers['Cache-Control'].should == 'public'
end
- it 'should support HTTP header rules, using an array of arrays
- on ruby platforms where hashes are not ordered' do
- # Setup
- ARRAY_HEADER_OPTIONS = {:urls => ["/cgi"], :root => root, :header_rules => [
- [:global, {'Cache-Control' => 'public, max-age=100'}],
- [:fonts, {'Cache-Control' => 'public, max-age=200'}],
- [%w(png jpg), {'Cache-Control' => 'public, max-age=300'}],
- ['/cgi/assets/folder/', {'Cache-Control' => 'public, max-age=400'}],
- ['cgi/assets/javascripts', {'Cache-Control' => 'public, max-age=500'}],
- [/\.(css|erb)\z/, {'Cache-Control' => 'public, max-age=600'}]
- ]}
- @array_header_request = Rack::MockRequest.new(static(DummyApp.new, ARRAY_HEADER_OPTIONS))
-
- it "supports rule :global" do
- # Global Headers via :global shortcut
- res = @array_header_request.get('/cgi/assets/index.html')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=100'
- end
-
- it "supports rule :fonts" do
- # Headers for web fonts via :fonts shortcut
- res = @array_header_request.get('/cgi/assets/fonts/font.eot')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=200'
- end
-
- it "supports file extension rules provided as an Array" do
- # Headers for file extensions via array
- res = @array_header_request.get('/cgi/assets/images/image.png')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=300'
- end
-
- it "supports folder rules provided as a String" do
- # Headers for files in folder via string
- res = @array_header_request.get('/cgi/assets/folder/test.js')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=400'
- end
-
- it "supports folder rules provided as a String not starting with a slash" do
- res = @array_header_request.get('/cgi/assets/javascripts/app.js')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=500'
- end
-
- it "supports flexible rules provided as Regexp" do
- # Flexible Headers via Regexp
- res = @array_header_request.get('/cgi/assets/stylesheets/app.css')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=600'
- end
-
- it "prioritizes header rules over fixed cache-control setting (legacy option)" do
- opts = OPTIONS.merge(
- :cache_control => 'public, max-age=24',
- :header_rules => [
- [:global, {'Cache-Control' => 'public, max-age=42'}]
- ])
-
- request = Rack::MockRequest.new(static(DummyApp.new, opts))
- res = request.get("/cgi/test")
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=42'
- end
- end
-
- # Test header rules provided using an (ordered) hash on Ruby >= 1.9
- if RUBY_VERSION.to_f >= 1.9
- it 'should support HTTP header rules, provided with a hash' do
- # Setup
- HASH_HEADER_OPTIONS = {:urls => ["/cgi"], :root => root, :header_rules => {
- :global => {'Cache-Control' => 'public, max-age=100'},
- :fonts => {'Cache-Control' => 'public, max-age=200'},
- %w(png jpg) => {'Cache-Control' => 'public, max-age=300'},
- '/cgi/assets/folder/' => {'Cache-Control' => 'public, max-age=400'},
- 'cgi/assets/javascripts' => {'Cache-Control' => 'public, max-age=500'},
- /\.(css|erb)\z/ => {'Cache-Control' => 'public, max-age=600'}
- }}
- @hash_header_request = Rack::MockRequest.new(static(DummyApp.new, HASH_HEADER_OPTIONS))
-
- it "supports rule :global (:header_rules HASH)" do
- # Global Headers via :global shortcut
- res = @hash_header_request.get('/cgi/assets/index.html')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=100'
- end
-
- it "supports rule :fonts (:header_rules HASH)" do
- # Headers for web fonts via :fonts shortcut
- res = @hash_header_request.get('/cgi/assets/fonts/font.eot')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=200'
- end
-
- it "supports file extension rules provided as an Array (:header_rules HASH)" do
- # Headers for file extensions via array
- res = @hash_header_request.get('/cgi/assets/images/image.png')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=300'
- end
-
- it "supports folder rules provided as a String (:header_rules HASH)" do
- # Headers for files in folder via string
- res = @hash_header_request.get('/cgi/assets/folder/test.js')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=400'
- end
-
- it "supports folder rules provided as a String not starting with a slash (:header_rules HASH)" do
- res = @hash_header_request.get('/cgi/assets/javascripts/app.js')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=500'
- end
-
- it "supports flexible rules provided as Regexp (:header_rules HASH)" do
- # Flexible Headers via Regexp
- res = @hash_header_request.get('/cgi/assets/stylesheets/app.css')
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=600'
- end
-
- it "prioritizes header rules over fixed cache-control setting (legacy option) (:header_rules HASH)" do
- opts = OPTIONS.merge(
- :cache_control => 'public, max-age=24',
- :header_rules => {
- :global => {'Cache-Control' => 'public, max-age=42'}
- })
-
- request = Rack::MockRequest.new(static(DummyApp.new, opts))
- res = request.get("/cgi/test")
- res.should.be.ok
- res.headers['Cache-Control'].should == 'public, max-age=42'
- end
- end
+ HEADER_OPTIONS = {:urls => ["/cgi"], :root => root, :header_rules => [
+ [:all, {'Cache-Control' => 'public, max-age=100'}],
+ [:fonts, {'Cache-Control' => 'public, max-age=200'}],
+ [%w(png jpg), {'Cache-Control' => 'public, max-age=300'}],
+ ['/cgi/assets/folder/', {'Cache-Control' => 'public, max-age=400'}],
+ ['cgi/assets/javascripts', {'Cache-Control' => 'public, max-age=500'}],
+ [/\.(css|erb)\z/, {'Cache-Control' => 'public, max-age=600'}]
+ ]}
+ @header_request = Rack::MockRequest.new(static(DummyApp.new, HEADER_OPTIONS))
+
+ it "supports header rule :all" do
+ # Headers for all files via :all shortcut
+ res = @header_request.get('/cgi/assets/index.html')
+ res.should.be.ok
+ res.headers['Cache-Control'].should == 'public, max-age=100'
+ end
+
+ it "supports header rule :fonts" do
+ # Headers for web fonts via :fonts shortcut
+ res = @header_request.get('/cgi/assets/fonts/font.eot')
+ res.should.be.ok
+ res.headers['Cache-Control'].should == 'public, max-age=200'
+ end
+
+ it "supports file extension header rules provided as an Array" do
+ # Headers for file extensions via array
+ res = @header_request.get('/cgi/assets/images/image.png')
+ res.should.be.ok
+ res.headers['Cache-Control'].should == 'public, max-age=300'
+ end
+
+ it "supports folder rules provided as a String" do
+ # Headers for files in folder via string
+ res = @header_request.get('/cgi/assets/folder/test.js')
+ res.should.be.ok
+ res.headers['Cache-Control'].should == 'public, max-age=400'
+ end
+ it "supports folder header rules provided as a String not starting with a slash" do
+ res = @header_request.get('/cgi/assets/javascripts/app.js')
+ res.should.be.ok
+ res.headers['Cache-Control'].should == 'public, max-age=500'
+ end
+
+ it "supports flexible header rules provided as Regexp" do
+ # Flexible Headers via Regexp
+ res = @header_request.get('/cgi/assets/stylesheets/app.css')
+ res.should.be.ok
+ res.headers['Cache-Control'].should == 'public, max-age=600'
end
+
+ it "prioritizes header rules over fixed cache-control setting (legacy option)" do
+ opts = OPTIONS.merge(
+ :cache_control => 'public, max-age=24',
+ :header_rules => [
+ [:all, {'Cache-Control' => 'public, max-age=42'}]
+ ])
+
+ request = Rack::MockRequest.new(static(DummyApp.new, opts))
+ res = request.get("/cgi/test")
+ res.should.be.ok
+ res.headers['Cache-Control'].should == 'public, max-age=42'
+ end
+
end

0 comments on commit bb09727

Please sign in to comment.
Something went wrong with that request. Please try again.