- 概要
如果方法無法按照預期的方式運作的話就會抛出Exception。這些異常可以在捕捉後傳回一個公共頁面(使用者友好的頁面)或者一個開發者檢視頁面(遍布了debug資訊的頁面)。後者已經由Action Controller實作了,前者應該根據應用量身定做。
public exception預設為傳回一個由error code命名的靜态html頁面,如果沒有這個檔案,将傳回一個空的response和這個error code給浏覽器。 可以override local_request?來識别本地請求,然後override rescue_action_in_public和rescue_action_locally方法處理傳回頁面。
- 源代碼
一些常量,包括Error的類别
- LOCALHOST = '127.0.0.1'.freeze
- DEFAULT_RESCUE_RESPONSE = :internal_server_error
- DEFAULT_RESCUE_RESPONSES = {
- 'ActionController::RoutingError' => :not_found,
- 'ActionController::UnknownAction' => :not_found,
- 'ActiveRecord::RecordNotFound' => :not_found,
- 'ActiveRecord::StaleObjectError' => :conflict,
- 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
- 'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
- 'ActionController::NotImplemented' => :not_implemented,
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
- }
- DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
- DEFAULT_RESCUE_TEMPLATES = {
- 'ActionView::MissingTemplate' => 'missing_template',
- 'ActionController::RoutingError' => 'routing_error',
- 'ActionController::UnknownAction' => 'unknown_action',
- 'ActionView::TemplateError' => 'template_error'
- }
導入Controller
- def self.included(base) #:nodoc:
- base.cattr_accessor :rescue_responses # 增加了rescue_responses變量
- base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
- base.rescue_responses.update DEFAULT_RESCUE_RESPONSES
- base.cattr_accessor :rescue_templates # 增加了rescue_templates變量
- base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
- base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
- base.class_inheritable_array :rescue_handlers # 增加了rescue_handlers變量 使用者處理異常
- base.rescue_handlers = []
- base.extend(ClassMethods)
- base.class_eval do
- alias_method_chain :perform_action, :rescue
- end
- end
可以看到入口為perform_action_with_rescue,在這之前看到兩個類public方法:
- module ClassMethods
- def process_with_exception(request, response, exception) #:nodoc:
- new.process(request, response, :rescue_action, exception)
- end
- # Rescue exceptions raised in controller actions.
- #
- # <tt>rescue_from</tt> receives a series of exception classes or class
- # names, and a trailing <tt>:with</tt> option with the name of a method
- # or a Proc object to be called to handle them. Alternatively a block can
- # be given.
- #
- # Handlers that take one argument will be called with the exception, so
- # that the exception can be inspected when dealing with it.
- #
- # Handlers are inherited. They are searched from right to left, from
- # bottom to top, and up the hierarchy. The handler of the first class for
- # which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
- # any.
- #
- # class ApplicationController < ActionController::Base
- # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
- # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
- #
- # rescue_from 'MyAppError::Base' do |exception|
- # render :xml => exception, :status => 500
- # end
- #
- # protected
- # def deny_access
- # ...
- # end
- #
- # def show_errors(exception)
- # exception.record.new_record? ? ...
- # end
- # end
- def rescue_from(*klasses, &block)
- options = klasses.extract_options!
- unless options.has_key?(:with)
- block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.")
- end
- klasses.each do |klass|
- key = if klass.is_a?(Class) && klass <= Exception
- klass.name
- elsif klass.is_a?(String)
- klass
- else
- raise(ArgumentError, "#{klass} is neither an Exception nor a String")
- end
- # Order is important, we put the pair at the end. When dealing with an
- # exception we will follow the documented order going from right to left.
- rescue_handlers << [key, options[:with]]
- end
- end
- end
其中process_with_exception方法是在Dispatcher裡面調用到的, Dispatcher.rb裡面的failsafe_rescue裡面:
- def failsafe_rescue(exception)
- self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
- if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
- @controller.process_with_exception(@request, @response, exception).out(@output)
- else
- raise exception
- end
- end
- end
rescue_action方法如下:
- # Exception handler called when the performance of an action raises an exception.
- def rescue_action(exception)
- log_error(exception) if logger
- erase_results if performed?
- # Let the exception alter the response if it wants.
- # For example, MethodNotAllowed sets the Allow header.
- if exception.respond_to?(:handle_response!)
- exception.handle_response!(response)
- end
- if consider_all_requests_local || local_request?
- rescue_action_locally(exception)
- else
- rescue_action_in_public(exception)
- end
- end
- # Render detailed diagnostics for unhandled exceptions rescued from
- # a controller action.
- def rescue_action_locally(exception)
- add_variables_to_assigns
- @template.instance_variable_set("@exception", exception)
- @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
- @template.send!(:assign_variables_from_controller)
- @template.instance_variable_set
- ("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
- #template_path_for_local_rescue就是include裡的rescue_templates
- response.content_type = Mime::HTML
- render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
- end
- # 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
- def rescue_action_in_public(exception) #:doc:
- render_optional_error_file response_code_for_rescue(exception)
- #根據response_code來查找檔案, response_code_for_rescue就是include裡的rescue_responses,如果有
- end
- # 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
- # 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.
- def render_optional_error_file(status_code)
- status = interpret_status(status_code)
- path = "#{Rails.public_path}/#{status[0,3]}.html"
- if File.exist?(path)
- render :file => path, :status => status
- else
- head status
- end
- end
繼續看 perform_action_with_rescue方法,可以看到核心方法在rescue_action_with_handler和rescue_action裡面
- def perform_action_with_rescue #:nodoc:
- perform_action_without_rescue
- rescue Exception => exception
- rescue_action_with_handler(exception) || rescue_action(exception)
- 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) #用rescue_from設定過後,進行查找handler如下
- if handler.arity != 0
- handler.call(exception)
- else
- handler.call
- end
- true # don't rely on the return value of the handler
- end
- end
- def handler_for_rescue(exception)
- # We go from right to left because pairs are pushed onto rescue_handlers
- # as rescue_from declarations are found.
- #rescue_handlers這個變量就是include裡的 base.class_inheritable_array :rescue_handlers
- _, handler = *rescue_handlers.reverse.detect do |klass_name, handler|
- # The purpose of allowing strings in rescue_from is to support the
- # declaration of handler associations for exception classes whose
- # definition is yet unknown.
- #
- # Since this loop needs the constants it would be inconsistent to
- # assume they should exist at this point. An early raised exception
- # could trigger some other handler and the array could include
- # precisely a string whose corresponding constant has not yet been
- # seen. This is why we are tolerant to unknown constants.
- #
- # Note that this tolerance only matters if the exception was given as
- # a string, otherwise a NameError will be raised by the interpreter
- # itself when rescue_from CONSTANT is executed.
- klass = self.class.const_get(klass_name) rescue nil
- klass ||= klass_name.constantize rescue nil
- exception.is_a?(klass) if klass
- end
- case handler
- when Symbol
- method(handler)
- when Proc
- handler.bind(self)
- end
- end