提交 a6f49d9b 编写于 作者: J Jeremy Kemper

Introduce ActionController::Base.rescue_from to declare exception-handling...

Introduce ActionController::Base.rescue_from to declare exception-handling methods. Cleaner style than the case-heavy rescue_action_in_public. Closes #9449.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7597 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 c6190038
*SVN* *SVN*
* Introduce ActionController::Base.rescue_from to declare exception-handling methods. Cleaner style than the case-heavy rescue_action_in_public. #9449 [norbert]
* Rename some RequestForgeryProtection methods. The class method is now #protect_from_forgery, and the default parameter is now 'authenticity_token'. [Rick] * Rename some RequestForgeryProtection methods. The class method is now #protect_from_forgery, and the default parameter is now 'authenticity_token'. [Rick]
* Merge csrf_killer plugin into rails. Adds RequestForgeryProtection model that verifies session-specific _tokens for non-GET requests. [Rick] * Merge csrf_killer plugin into rails. Adds RequestForgeryProtection model that verifies session-specific _tokens for non-GET requests. [Rick]
......
...@@ -41,6 +41,9 @@ def self.included(base) #:nodoc: ...@@ -41,6 +41,9 @@ def self.included(base) #:nodoc:
base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE) base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
base.class_inheritable_hash :rescue_handlers
base.rescue_handlers = {}
base.extend(ClassMethods) base.extend(ClassMethods)
base.class_eval do base.class_eval do
alias_method_chain :perform_action, :rescue alias_method_chain :perform_action, :rescue
...@@ -51,6 +54,33 @@ module ClassMethods #:nodoc: ...@@ -51,6 +54,33 @@ module ClassMethods #:nodoc:
def process_with_exception(request, response, exception) def process_with_exception(request, response, exception)
new.process(request, response, :rescue_action, exception) new.process(request, response, :rescue_action, exception)
end end
# Rescue exceptions raised in controller actions by passing at least one exception class and a :with option that contains the name of the method to be called to respond to the exception.
# Handler methods that take one argument will be called with the exception, so that the exception can be inspected when dealing with it.
#
# class ApplicationController < ActionController::Base
# rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
# rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
#
# protected
# def deny_access
# ...
# end
#
# def show_errors(exception)
# exception.record.new_record? ? ...
# end
# end
def rescue_from(*klasses)
options = klasses.extract_options!
unless options.has_key?(:with) # allow nil
raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
end
klasses.each do |klass|
rescue_handlers[klass.name] = options[:with]
end
end
end end
protected protected
...@@ -59,6 +89,8 @@ def rescue_action(exception) ...@@ -59,6 +89,8 @@ def rescue_action(exception)
log_error(exception) if logger log_error(exception) if logger
erase_results if performed? erase_results if performed?
return if rescue_action_with_handler(exception)
# Let the exception alter the response if it wants. # Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header. # For example, MethodNotAllowed sets the Allow header.
if exception.respond_to?(:handle_response!) if exception.respond_to?(:handle_response!)
...@@ -87,7 +119,6 @@ def log_error(exception) #:doc: ...@@ -87,7 +119,6 @@ def log_error(exception) #:doc:
end end
end end
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.s # default will call render_optional_error_file. Override this method to provide more user friendly error messages.s
def rescue_action_in_public(exception) #:doc: def rescue_action_in_public(exception) #:doc:
...@@ -97,7 +128,7 @@ def rescue_action_in_public(exception) #:doc: ...@@ -97,7 +128,7 @@ def rescue_action_in_public(exception) #:doc:
# Attempts to render a static error page based on the <tt>status_code</tt> thrown, # Attempts to render a static error page based on the <tt>status_code</tt> thrown,
# or just return headers if no such file exists. For example, if a 500 error is # or just return headers if no such file exists. For example, if a 500 error is
# being handled Rails will first attempt to render the file at <tt>public/500.html</tt>. # being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
# If the file doesn't exist, the body of the response will be left empty # If the file doesn't exist, the body of the response will be left empty.
def render_optional_error_file(status_code) def render_optional_error_file(status_code)
status = interpret_status(status_code) status = interpret_status(status_code)
path = "#{RAILS_ROOT}/public/#{status[0,3]}.html" path = "#{RAILS_ROOT}/public/#{status[0,3]}.html"
...@@ -129,6 +160,18 @@ def rescue_action_locally(exception) ...@@ -129,6 +160,18 @@ def rescue_action_locally(exception)
render_for_file(rescues_path("layout"), response_code_for_rescue(exception)) render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
end end
# Tries to rescue the exception by looking up and calling a registered handler.
def rescue_action_with_handler(exception)
if handler = handler_for_rescue(exception)
if handler.arity != 0
handler.call(exception)
else
handler.call
end
true # don't rely on the return value of the handler
end
end
private private
def perform_action_with_rescue #:nodoc: def perform_action_with_rescue #:nodoc:
perform_action_without_rescue perform_action_without_rescue
...@@ -148,6 +191,12 @@ def response_code_for_rescue(exception) ...@@ -148,6 +191,12 @@ def response_code_for_rescue(exception)
rescue_responses[exception.class.name] rescue_responses[exception.class.name]
end end
def handler_for_rescue(exception)
if handler = rescue_handlers[exception.class.name]
method(handler)
end
end
def clean_backtrace(exception) def clean_backtrace(exception)
if backtrace = exception.backtrace if backtrace = exception.backtrace
if defined?(RAILS_ROOT) if defined?(RAILS_ROOT)
......
...@@ -3,6 +3,15 @@ ...@@ -3,6 +3,15 @@
uses_mocha 'rescue' do uses_mocha 'rescue' do
class RescueController < ActionController::Base class RescueController < ActionController::Base
class NotAuthorized < StandardError
end
class RecordInvalid < StandardError
end
rescue_from NotAuthorized, :with => :deny_access
rescue_from RecordInvalid, :with => :show_errors
def raises def raises
render :text => 'already rendered' render :text => 'already rendered'
raise "don't panic!" raise "don't panic!"
...@@ -15,10 +24,27 @@ def method_not_allowed ...@@ -15,10 +24,27 @@ def method_not_allowed
def not_implemented def not_implemented
raise ActionController::NotImplemented.new(:get, :put) raise ActionController::NotImplemented.new(:get, :put)
end end
def not_authorized
raise NotAuthorized
end
def record_invalid
raise RecordInvalid
end
def missing_template; end def missing_template
end end
protected
def deny_access
head :forbidden
end
def show_errors(exception)
head :unprocessable_entity
end
end
class RescueTest < Test::Unit::TestCase class RescueTest < Test::Unit::TestCase
FIXTURE_PUBLIC = "#{File.dirname(__FILE__)}/../fixtures".freeze FIXTURE_PUBLIC = "#{File.dirname(__FILE__)}/../fixtures".freeze
...@@ -38,7 +64,6 @@ def setup ...@@ -38,7 +64,6 @@ def setup
end end
end end
def test_rescue_action_locally_if_all_requests_local def test_rescue_action_locally_if_all_requests_local
@controller.expects(:local_request?).never @controller.expects(:local_request?).never
@controller.expects(:rescue_action_locally).with(@exception) @controller.expects(:rescue_action_locally).with(@exception)
...@@ -69,7 +94,6 @@ def test_rescue_action_in_public_otherwise ...@@ -69,7 +94,6 @@ def test_rescue_action_in_public_otherwise
end end
end end
def test_rescue_action_in_public_with_error_file def test_rescue_action_in_public_with_error_file
with_rails_root FIXTURE_PUBLIC do with_rails_root FIXTURE_PUBLIC do
with_all_requests_local false do with_all_requests_local false do
...@@ -93,7 +117,6 @@ def test_rescue_action_in_public_without_error_file ...@@ -93,7 +117,6 @@ def test_rescue_action_in_public_without_error_file
assert_equal ' ', @response.body assert_equal ' ', @response.body
end end
def test_rescue_unknown_action_in_public_with_error_file def test_rescue_unknown_action_in_public_with_error_file
with_rails_root FIXTURE_PUBLIC do with_rails_root FIXTURE_PUBLIC do
with_all_requests_local false do with_all_requests_local false do
...@@ -117,7 +140,6 @@ def test_rescue_unknown_action_in_public_without_error_file ...@@ -117,7 +140,6 @@ def test_rescue_unknown_action_in_public_without_error_file
assert_equal ' ', @response.body assert_equal ' ', @response.body
end end
def test_rescue_missing_template_in_public def test_rescue_missing_template_in_public
with_rails_root FIXTURE_PUBLIC do with_rails_root FIXTURE_PUBLIC do
with_all_requests_local true do with_all_requests_local true do
...@@ -129,7 +151,6 @@ def test_rescue_missing_template_in_public ...@@ -129,7 +151,6 @@ def test_rescue_missing_template_in_public
assert @response.body.include?('missing_template'), "Response should include the template name." assert @response.body.include?('missing_template'), "Response should include the template name."
end end
def test_rescue_action_locally def test_rescue_action_locally
get :raises get :raises
assert_response :internal_server_error assert_response :internal_server_error
...@@ -138,7 +159,6 @@ def test_rescue_action_locally ...@@ -138,7 +159,6 @@ def test_rescue_action_locally
assert @response.body.include?("don't panic"), "Response should include exception message." assert @response.body.include?("don't panic"), "Response should include exception message."
end end
def test_local_request_when_remote_addr_is_localhost def test_local_request_when_remote_addr_is_localhost
@controller.expects(:request).returns(@request).at_least_once @controller.expects(:request).returns(@request).at_least_once
with_remote_addr '127.0.0.1' do with_remote_addr '127.0.0.1' do
...@@ -153,7 +173,6 @@ def test_local_request_when_remote_addr_isnt_locahost ...@@ -153,7 +173,6 @@ def test_local_request_when_remote_addr_isnt_locahost
end end
end end
def test_rescue_responses def test_rescue_responses
responses = ActionController::Base.rescue_responses responses = ActionController::Base.rescue_responses
...@@ -182,7 +201,6 @@ def test_rescue_templates ...@@ -182,7 +201,6 @@ def test_rescue_templates
assert_equal 'template_error', templates[ActionView::TemplateError.name] assert_equal 'template_error', templates[ActionView::TemplateError.name]
end end
def test_clean_backtrace def test_clean_backtrace
with_rails_root nil do with_rails_root nil do
# No action if RAILS_ROOT isn't set. # No action if RAILS_ROOT isn't set.
...@@ -217,6 +235,16 @@ def test_method_not_allowed ...@@ -217,6 +235,16 @@ def test_method_not_allowed
assert_equal "GET, HEAD, PUT", @response.headers['Allow'] assert_equal "GET, HEAD, PUT", @response.headers['Allow']
end end
def test_rescue_handler
get :not_authorized
assert_response :forbidden
end
def test_rescue_handler_with_argument
@controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) }
get :record_invalid
end
protected protected
def with_all_requests_local(local = true) def with_all_requests_local(local = true)
old_local, ActionController::Base.consider_all_requests_local = old_local, ActionController::Base.consider_all_requests_local =
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册