This moves the :halt catch out so that all routing code runs within one giant catch block instead of running each type of handler in its own catch block. This required some cleanup in the error handling code, which cleaned things up quite a bit. This corrects two issues: 1. halt with > 1 args causes ArgumentError http://sinatra.lighthouseapp.com/projects/9779/tickets/131 2. halting in a before filter doesn't modify response http://sinatra.lighthouseapp.com/projects/9779/tickets/127 We still need to split up the more epic methods (#route!, #invoke) but the logic is pretty sound at this point.
This commit is contained in:
parent
877bfb379c
commit
0ade0beec8
|
@ -320,7 +320,11 @@ module Sinatra
|
||||||
@request = Request.new(env)
|
@request = Request.new(env)
|
||||||
@response = Response.new
|
@response = Response.new
|
||||||
@params = nil
|
@params = nil
|
||||||
error_detection { dispatch! }
|
|
||||||
|
invoke { dispatch! }
|
||||||
|
invoke { error_block!(response.status) }
|
||||||
|
|
||||||
|
@response.body = [] if @env['REQUEST_METHOD'] == 'HEAD'
|
||||||
@response.finish
|
@response.finish
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -329,7 +333,8 @@ module Sinatra
|
||||||
end
|
end
|
||||||
|
|
||||||
def halt(*response)
|
def halt(*response)
|
||||||
throw :halt, *response
|
response = response.first if response.length == 1
|
||||||
|
throw :halt, response
|
||||||
end
|
end
|
||||||
|
|
||||||
def pass
|
def pass
|
||||||
|
@ -337,18 +342,19 @@ module Sinatra
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def dispatch!
|
# Run before filters and then locate and run a matching route.
|
||||||
@params = original_params = nested_params(@request.params)
|
def route!
|
||||||
|
@params = nested_params(@request.params)
|
||||||
|
|
||||||
self.class.filters.each do |block|
|
# before filters
|
||||||
res = catch(:halt) { instance_eval(&block) ; :continue }
|
self.class.filters.each { |block| instance_eval(&block) }
|
||||||
return unless res == :continue
|
|
||||||
end
|
|
||||||
|
|
||||||
|
# routes
|
||||||
if routes = self.class.routes[@request.request_method]
|
if routes = self.class.routes[@request.request_method]
|
||||||
|
original_params = @params
|
||||||
path = @request.path_info
|
path = @request.path_info
|
||||||
|
|
||||||
routes.each do |pattern, keys, conditions, method_name|
|
routes.each do |pattern, keys, conditions, block|
|
||||||
if match = pattern.match(path)
|
if match = pattern.match(path)
|
||||||
values = match.captures.map{|val| val && unescape(val) }
|
values = match.captures.map{|val| val && unescape(val) }
|
||||||
params =
|
params =
|
||||||
|
@ -368,14 +374,15 @@ module Sinatra
|
||||||
end
|
end
|
||||||
@params = original_params.merge(params)
|
@params = original_params.merge(params)
|
||||||
|
|
||||||
catch(:pass) {
|
catch(:pass) do
|
||||||
conditions.each { |cond|
|
conditions.each { |cond|
|
||||||
throw :pass if instance_eval(&cond) == false }
|
throw :pass if instance_eval(&cond) == false }
|
||||||
return invoke(method_name)
|
throw :halt, instance_eval(&block)
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
raise NotFound
|
raise NotFound
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -397,7 +404,8 @@ module Sinatra
|
||||||
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
||||||
end
|
end
|
||||||
|
|
||||||
def invoke(block)
|
# Run the block with 'throw :halt' support and apply result to the response.
|
||||||
|
def invoke(&block)
|
||||||
res = catch(:halt) { instance_eval(&block) }
|
res = catch(:halt) { instance_eval(&block) }
|
||||||
return if res.nil?
|
return if res.nil?
|
||||||
|
|
||||||
|
@ -429,15 +437,15 @@ module Sinatra
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_detection
|
# Dispatch a request with error handling.
|
||||||
errmap = self.class.errors
|
def dispatch!
|
||||||
yield
|
route!
|
||||||
rescue NotFound => boom
|
rescue NotFound => boom
|
||||||
@env['sinatra.error'] = boom
|
@env['sinatra.error'] = boom
|
||||||
@response.status = 404
|
@response.status = 404
|
||||||
@response.body = ['<h1>Not Found</h1>']
|
@response.body = ['<h1>Not Found</h1>']
|
||||||
handler = errmap[boom.class] || errmap[NotFound]
|
error_block! boom.class, NotFound
|
||||||
invoke handler unless handler.nil?
|
|
||||||
rescue ::Exception => boom
|
rescue ::Exception => boom
|
||||||
@env['sinatra.error'] = boom
|
@env['sinatra.error'] = boom
|
||||||
|
|
||||||
|
@ -449,11 +457,19 @@ module Sinatra
|
||||||
|
|
||||||
raise boom if options.raise_errors?
|
raise boom if options.raise_errors?
|
||||||
@response.status = 500
|
@response.status = 500
|
||||||
invoke errmap[boom.class] || errmap[Exception]
|
error_block! boom.class, Exception
|
||||||
ensure
|
|
||||||
if @response.status >= 400 && errmap.key?(response.status)
|
|
||||||
invoke errmap[response.status]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Find an custom error block for the key(s) specified.
|
||||||
|
def error_block!(*keys)
|
||||||
|
errmap = self.class.errors
|
||||||
|
keys.each do |key|
|
||||||
|
if block = errmap[key]
|
||||||
|
res = instance_eval(&block)
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
end
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def clean_backtrace(trace)
|
def clean_backtrace(trace)
|
||||||
|
@ -592,7 +608,7 @@ module Sinatra
|
||||||
route('GET', path, opts, &block)
|
route('GET', path, opts, &block)
|
||||||
|
|
||||||
@conditions = conditions
|
@conditions = conditions
|
||||||
head(path, opts) { invoke(block) ; [] }
|
route('HEAD', path, opts, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def put(path, opts={}, &bk); route 'PUT', path, opts, &bk; end
|
def put(path, opts={}, &bk); route 'PUT', path, opts, &bk; end
|
||||||
|
|
|
@ -107,7 +107,7 @@ module Sinatra
|
||||||
# Throwing halt with a Symbol and the to_result convention are
|
# Throwing halt with a Symbol and the to_result convention are
|
||||||
# deprecated. Override the invoke method to detect those types of return
|
# deprecated. Override the invoke method to detect those types of return
|
||||||
# values.
|
# values.
|
||||||
def invoke(handler)
|
def invoke(&block)
|
||||||
res = super
|
res = super
|
||||||
case
|
case
|
||||||
when res.kind_of?(Symbol)
|
when res.kind_of?(Symbol)
|
||||||
|
|
|
@ -73,6 +73,19 @@ describe "Filters" do
|
||||||
assert_equal 'cool', body
|
assert_equal 'cool', body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "does modify the response with halt" do
|
||||||
|
mock_app {
|
||||||
|
before { halt 302, 'Hi' }
|
||||||
|
get '/foo' do
|
||||||
|
"should not happen"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
get '/foo'
|
||||||
|
assert_equal 302, response.status
|
||||||
|
assert_equal 'Hi', body
|
||||||
|
end
|
||||||
|
|
||||||
it "gives you access to params" do
|
it "gives you access to params" do
|
||||||
mock_app {
|
mock_app {
|
||||||
before { @foo = params['foo'] }
|
before { @foo = params['foo'] }
|
||||||
|
|
|
@ -6,7 +6,7 @@ def route_def(pattern)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Routing" do
|
describe "Routing" do
|
||||||
%w[get put post delete head].each do |verb|
|
%w[get put post delete].each do |verb|
|
||||||
it "defines #{verb.upcase} request handlers with #{verb}" do
|
it "defines #{verb.upcase} request handlers with #{verb}" do
|
||||||
mock_app {
|
mock_app {
|
||||||
send verb, '/hello' do
|
send verb, '/hello' do
|
||||||
|
@ -21,6 +21,21 @@ describe "Routing" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "defines HEAD request handlers with HEAD" do
|
||||||
|
mock_app {
|
||||||
|
head '/hello' do
|
||||||
|
response['X-Hello'] = 'World!'
|
||||||
|
'remove me'
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
request = Rack::MockRequest.new(@app)
|
||||||
|
response = request.request('HEAD', '/hello', {})
|
||||||
|
assert response.ok?
|
||||||
|
assert_equal 'World!', response['X-Hello']
|
||||||
|
assert_equal '', response.body
|
||||||
|
end
|
||||||
|
|
||||||
it "404s when no route satisfies the request" do
|
it "404s when no route satisfies the request" do
|
||||||
mock_app {
|
mock_app {
|
||||||
get('/foo') { }
|
get('/foo') { }
|
||||||
|
@ -296,6 +311,30 @@ describe "Routing" do
|
||||||
assert_equal 'Hello World', body
|
assert_equal 'Hello World', body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "halts with a response tuple" do
|
||||||
|
mock_app {
|
||||||
|
get '/' do
|
||||||
|
halt 295, {'Content-Type' => 'text/plain'}, 'Hello World'
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
get '/'
|
||||||
|
assert_equal 295, status
|
||||||
|
assert_equal 'text/plain', response['Content-Type']
|
||||||
|
assert_equal 'Hello World', body
|
||||||
|
end
|
||||||
|
|
||||||
|
it "halts with an array of strings" do
|
||||||
|
mock_app {
|
||||||
|
get '/' do
|
||||||
|
halt %w[Hello World How Are You]
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
get '/'
|
||||||
|
assert_equal 'HelloWorldHowAreYou', body
|
||||||
|
end
|
||||||
|
|
||||||
it "transitions to the next matching route on pass" do
|
it "transitions to the next matching route on pass" do
|
||||||
mock_app {
|
mock_app {
|
||||||
get '/:foo' do
|
get '/:foo' do
|
||||||
|
|
Loading…
Reference in New Issue