diff --git a/README.rdoc b/README.rdoc index 5d3c6698..6beb58c1 100644 --- a/README.rdoc +++ b/README.rdoc @@ -47,6 +47,7 @@ interface: * Rack::Access - Limits access based on IP address * Rack::ResponseHeaders - Manipulates response headers object at runtime * Rack::SimpleEndpoint - Creates simple endpoints with routing rules, similar to Sinatra actions +* Rack::TryStatic - Tries to match request to a static file === Use diff --git a/lib/rack/contrib/try_static.rb b/lib/rack/contrib/try_static.rb new file mode 100644 index 00000000..0f36dd72 --- /dev/null +++ b/lib/rack/contrib/try_static.rb @@ -0,0 +1,36 @@ +module Rack + + # The Rack::TryStatic middleware delegates requests to Rack::Static middleware + # trying to match a static file + # + # Examples + # + # use Rack::TryStatic, + # :root => "public", # static files root dir + # :urls => %w[/], # match all requests + # :try => ['.html', 'index.html', '/index.html'] # try these postfixes sequentially + # + # uses same options as Rack::Static with extra :try option which is an array + # of postfixes to find desired file + + class TryStatic + + def initialize(app, options) + @app = app + @try = ['', *options.delete(:try)] + @static = ::Rack::Static.new( + lambda { [404, {}, []] }, + options) + end + + def call(env) + orig_path = env['PATH_INFO'] + found = nil + @try.each do |path| + resp = @static.call(env.merge!({'PATH_INFO' => orig_path + path})) + break if 404 != resp[0] && found = resp + end + found or @app.call(env.merge!('PATH_INFO' => orig_path)) + end + end +end diff --git a/test/documents/existing.html b/test/documents/existing.html new file mode 100644 index 00000000..1058c016 --- /dev/null +++ b/test/documents/existing.html @@ -0,0 +1 @@ +existing.html diff --git a/test/documents/index.htm b/test/documents/index.htm new file mode 100644 index 00000000..a8632fef --- /dev/null +++ b/test/documents/index.htm @@ -0,0 +1 @@ +index.htm diff --git a/test/documents/index.html b/test/documents/index.html new file mode 100644 index 00000000..dcaf7169 --- /dev/null +++ b/test/documents/index.html @@ -0,0 +1 @@ +index.html diff --git a/test/spec_rack_try_static.rb b/test/spec_rack_try_static.rb new file mode 100644 index 00000000..65835c77 --- /dev/null +++ b/test/spec_rack_try_static.rb @@ -0,0 +1,44 @@ +require 'test/spec' + +require 'rack' +require 'rack/contrib/try_static' +require 'rack/mock' + +def request(options = {}) + options.merge!({ + :urls => %w[/], + :root => ::File.expand_path(::File.dirname(__FILE__)), + }) + + @request = + Rack::MockRequest.new( + Rack::TryStatic.new( + lambda {[200, {}, ["Hello World"]]}, + options)) +end + +describe "Rack::TryStatic" do + context 'when file cannot be found' do + it 'should call call app' do + res = request(:try => ['html']).get('/documents') + res.should.be.ok + res.body.should == "Hello World" + end + end + + context 'when file can be found' do + it 'should serve first found' do + res = request(:try => ['.html', '/index.html', '/index.htm']).get('/documents') + res.should.be.ok + res.body.strip.should == "index.html" + end + end + + context 'when path_info maps directly to file' do + it 'should serve existing' do + res = request(:try => ['/index.html']).get('/documents/existing.html') + res.should.be.ok + res.body.strip.should == "existing.html" + end + end +end